go.ligato.io/vpp-agent/v3@v3.5.0/cmd/agentctl/commands/service.go (about)

     1  //  Copyright (c) 2019 Cisco and/or its affiliates.
     2  //
     3  //  Licensed under the Apache License, Version 2.0 (the "License");
     4  //  you may not use this file except in compliance with the License.
     5  //  You may obtain a copy of the License at:
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  //  Unless required by applicable law or agreed to in writing, software
    10  //  distributed under the License is distributed on an "AS IS" BASIS,
    11  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //  See the License for the specific language governing permissions and
    13  //  limitations under the License.
    14  
    15  package commands
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"encoding/json"
    21  	"errors"
    22  	"fmt"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/ghodss/yaml"
    27  	"github.com/jhump/protoreflect/dynamic"
    28  	"github.com/jhump/protoreflect/grpcreflect"
    29  	"github.com/spf13/cobra"
    30  	ref "google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
    31  	"google.golang.org/grpc/status"
    32  
    33  	agentcli "go.ligato.io/vpp-agent/v3/cmd/agentctl/cli"
    34  )
    35  
    36  func NewServiceCommand(cli agentcli.Cli) *cobra.Command {
    37  	cmd := &cobra.Command{
    38  		Use:   "service",
    39  		Short: "Manage agent services",
    40  	}
    41  	cmd.AddCommand(
    42  		NewServiceListCommand(cli),
    43  		NewServiceCallCommand(cli),
    44  	)
    45  	return cmd
    46  }
    47  
    48  func NewServiceListCommand(cli agentcli.Cli) *cobra.Command {
    49  	var (
    50  		opts ServiceListOptions
    51  	)
    52  	cmd := &cobra.Command{
    53  		Use:     "list [SERVICE]",
    54  		Aliases: []string{"ls", "l"},
    55  		Short:   "List remote services",
    56  		Args:    cobra.RangeArgs(0, 1),
    57  		RunE: func(cmd *cobra.Command, args []string) error {
    58  			return runServiceList(cli, opts)
    59  		},
    60  	}
    61  	flags := cmd.Flags()
    62  	flags.BoolVarP(&opts.Methods, "methods", "m", false, "Show service methods")
    63  	return cmd
    64  }
    65  
    66  type ServiceListOptions struct {
    67  	Methods bool
    68  }
    69  
    70  func runServiceList(cli agentcli.Cli, opts ServiceListOptions) error {
    71  	grpcClient, err := cli.Client().GRPCConn()
    72  	if err != nil {
    73  		return err
    74  	}
    75  	ctx := context.Background()
    76  	c := grpcreflect.NewClient(ctx, ref.NewServerReflectionClient(grpcClient))
    77  
    78  	services, err := c.ListServices()
    79  	if err != nil {
    80  		msg := status.Convert(err).Message()
    81  		return fmt.Errorf("listing services failed: %v: %w", msg, err)
    82  	}
    83  
    84  	for _, srv := range services {
    85  		if srv == reflectionServiceName {
    86  			continue
    87  		}
    88  		s, err := c.ResolveService(srv)
    89  		if err != nil {
    90  			return fmt.Errorf("resolving service %s failed: %v", srv, err)
    91  		}
    92  		fmt.Fprintf(cli.Out(), "service %s (%v)\n", s.GetFullyQualifiedName(), s.GetFile().GetName())
    93  		if opts.Methods {
    94  			for _, m := range s.GetMethods() {
    95  				req := m.GetInputType().GetName()
    96  				if m.IsClientStreaming() {
    97  					req = fmt.Sprintf("stream %s", req)
    98  				}
    99  				resp := m.GetOutputType().GetName()
   100  				if m.IsServerStreaming() {
   101  					resp = fmt.Sprintf("stream %s", resp)
   102  				}
   103  				fmt.Fprintf(cli.Out(), " - rpc %s (%v) returns (%v)\n", m.GetName(), req, resp)
   104  			}
   105  			fmt.Fprintln(cli.Out())
   106  		}
   107  	}
   108  
   109  	return nil
   110  }
   111  
   112  const reflectionServiceName = "grpc.reflection.v1alpha.ServerReflection"
   113  
   114  func NewServiceCallCommand(cli agentcli.Cli) *cobra.Command {
   115  	var (
   116  		opts ServiceCallOptions
   117  	)
   118  	cmd := &cobra.Command{
   119  		Use:     "call SERVICE METHOD",
   120  		Aliases: []string{"c"},
   121  		Short:   "Call methods on services",
   122  		Args:    cobra.RangeArgs(2, 3),
   123  		RunE: func(cmd *cobra.Command, args []string) error {
   124  			opts.Service = args[0]
   125  			opts.Method = args[1]
   126  			return runServiceCall(cli, opts)
   127  		},
   128  	}
   129  	return cmd
   130  }
   131  
   132  type ServiceCallOptions struct {
   133  	Service string
   134  	Method  string
   135  }
   136  
   137  func runServiceCall(cli agentcli.Cli, opts ServiceCallOptions) error {
   138  	grpcClient, err := cli.Client().GRPCConn()
   139  	if err != nil {
   140  		return err
   141  	}
   142  	c := grpcreflect.NewClient(context.Background(), ref.NewServerReflectionClient(grpcClient))
   143  
   144  	svc, err := c.ResolveService(opts.Service)
   145  	if err != nil {
   146  		msg := status.Convert(err).Message()
   147  		return fmt.Errorf("resolving service failed: %v: %w", msg, err)
   148  	}
   149  
   150  	m := svc.FindMethodByName(opts.Method)
   151  	if m == nil {
   152  		return fmt.Errorf("method %s not found for service %s", opts.Method, svc.GetName())
   153  	}
   154  
   155  	endpoint, err := fqrnToEndpoint(m.GetFullyQualifiedName())
   156  	if err != nil {
   157  		return err
   158  	}
   159  
   160  	req := dynamic.NewMessage(m.GetInputType())
   161  	reply := dynamic.NewMessage(m.GetOutputType())
   162  
   163  	if len(m.GetInputType().GetFields()) > 0 {
   164  		fmt.Fprintf(cli.Out(), "Enter input request %s:\n", m.GetInputType().GetFullyQualifiedName())
   165  
   166  		var buf bytes.Buffer
   167  		_, err = buf.ReadFrom(cli.In())
   168  		if err != nil {
   169  			return err
   170  		}
   171  		b, err := yaml.YAMLToJSON(buf.Bytes())
   172  		if err != nil {
   173  			return err
   174  		}
   175  		if err = json.Unmarshal(b, req); err != nil {
   176  			return err
   177  		}
   178  		input, err := req.MarshalTextIndent()
   179  		if err != nil {
   180  			return err
   181  		}
   182  		fmt.Fprintf(cli.Out(), "Request (%s):\n%s\n", m.GetInputType().GetName(), input)
   183  	}
   184  
   185  	fmt.Fprintf(cli.Out(), "Calling %s\n", m.GetFullyQualifiedName())
   186  
   187  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*7)
   188  	defer cancel()
   189  	if err := grpcClient.Invoke(ctx, endpoint, req, reply); err != nil {
   190  		return err
   191  	}
   192  
   193  	output, err := reply.MarshalTextIndent()
   194  	if err != nil {
   195  		return err
   196  	}
   197  	fmt.Fprintf(cli.Out(), "Response (%s):\n%s\n", m.GetOutputType().GetName(), output)
   198  
   199  	return nil
   200  }
   201  
   202  // fqrnToEndpoint converts FullQualifiedRPCName to endpoint
   203  //
   204  // e.g.
   205  //	pkg_name.svc_name.rpc_name -> /pkg_name.svc_name/rpc_name
   206  func fqrnToEndpoint(fqrn string) (string, error) {
   207  	sp := strings.Split(fqrn, ".")
   208  	if len(sp) < 3 {
   209  		return "", errors.New("invalid FQRN format")
   210  	}
   211  
   212  	return fmt.Sprintf("/%s/%s", strings.Join(sp[:len(sp)-1], "."), sp[len(sp)-1]), nil
   213  }