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 }