f1ad5d8f1ec4f5849af393b218fca68402a7b108
[govpp.git] / cmd / govpp / main.go
1 //  Copyright (c) 2020 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 main
16
17 import (
18         "bytes"
19         "context"
20         "encoding/json"
21         "flag"
22         "fmt"
23         "io"
24         "io/ioutil"
25         "log"
26         "net/http"
27         "os"
28         "strings"
29         "text/tabwriter"
30
31         "git.fd.io/govpp.git"
32         "git.fd.io/govpp.git/adapter/socketclient"
33         "git.fd.io/govpp.git/binapi/vpe"
34         "git.fd.io/govpp.git/binapigen"
35         "git.fd.io/govpp.git/binapigen/vppapi"
36 )
37
38 func main() {
39         flag.Parse()
40
41         apifiles, err := vppapi.Parse()
42         if err != nil {
43                 log.Fatal(err)
44         }
45
46         switch cmd := flag.Arg(0); cmd {
47         case "server":
48                 runServer(apifiles, ":7777")
49         case "vppapi":
50                 showVPPAPI(os.Stdout, apifiles)
51         case "vppapijson":
52                 if flag.NArg() == 1 {
53                         writeAsJSON(os.Stdout, apifiles)
54                 } else {
55                         f := flag.Arg(1)
56                         var found bool
57                         for _, apifile := range apifiles {
58                                 if apifile.Name == f {
59                                         writeAsJSON(os.Stdout, apifile)
60                                         found = true
61                                         break
62                                 }
63                         }
64                         if !found {
65                                 log.Fatalf("VPP API file %q not found", f)
66                         }
67                 }
68         case "rpc":
69                 showRPC(apifiles)
70         case "cli":
71                 args := flag.Args()
72                 if len(args) == 0 {
73                         args = []string{"?"}
74                 }
75                 sendCLI(args[1:])
76         default:
77                 log.Fatalf("invalid command: %q", cmd)
78         }
79
80 }
81
82 func writeAsJSON(w io.Writer, data interface{}) {
83         b, err := json.MarshalIndent(data, "", "  ")
84         if err != nil {
85                 log.Fatal(err)
86                 return
87         }
88         if _, err := w.Write(b); err != nil {
89                 panic(err)
90         }
91 }
92
93 func showRPC(apifiles []*vppapi.File) {
94         for _, apifile := range apifiles {
95                 fmt.Printf("%s.api\n", apifile.Name)
96                 if apifile.Service == nil {
97                         continue
98                 }
99                 for _, rpc := range apifile.Service.RPCs {
100                         req := rpc.Request
101                         reply := rpc.Reply
102                         if rpc.Stream {
103                                 reply = "stream " + reply
104                         }
105                         fmt.Printf(" rpc (%s) --> (%s)\n", req, reply)
106                 }
107         }
108 }
109
110 func showVPPAPI(out io.Writer, apifiles []*vppapi.File) {
111         binapigen.SortFilesByImports(apifiles)
112
113         var buf bytes.Buffer
114         w := tabwriter.NewWriter(&buf, 0, 0, 3, ' ', 0)
115         fmt.Fprintf(w, "API\tOPTIONS\tCRC\tPATH\tIMPORTED\tTYPES\t\n")
116
117         for _, apifile := range apifiles {
118                 importedTypes := binapigen.ListImportedTypes(apifiles, apifile)
119                 var options []string
120                 for k, v := range apifile.Options {
121                         options = append(options, fmt.Sprintf("%s=%v", k, v))
122                 }
123                 imports := fmt.Sprintf("%d apis, %2d types", len(apifile.Imports), len(importedTypes))
124                 path := strings.TrimPrefix(apifile.Path, vppapi.DefaultDir+"/")
125                 types := fmt.Sprintf("%2d enum, %2d alias, %2d struct, %2d union, %2d msg",
126                         len(apifile.EnumTypes), len(apifile.AliasTypes), len(apifile.StructTypes), len(apifile.UnionTypes), len(apifile.Messages))
127                 fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%v\t%s\t\n",
128                         apifile.Name, strings.Join(options, " "), apifile.CRC, path, imports, types)
129         }
130
131         if err := w.Flush(); err != nil {
132                 log.Fatal(err)
133         }
134         fmt.Fprint(out, buf.String())
135 }
136
137 func sendCLI(args []string) {
138         cmd := strings.Join(args, " ")
139         fmt.Printf("# %s\n", cmd)
140
141         conn, err := govpp.Connect("/run/vpp/api.sock")
142         if err != nil {
143                 log.Fatal(err)
144         }
145         defer conn.Disconnect()
146
147         ch, err := conn.NewAPIChannel()
148         if err != nil {
149                 log.Fatal(err)
150         }
151         defer ch.Close()
152
153         if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil {
154                 log.Fatal(err)
155         }
156
157         client := vpe.NewServiceClient(conn)
158         reply, err := client.CliInband(context.Background(), &vpe.CliInband{
159                 Cmd: cmd,
160         })
161         if err != nil {
162                 log.Fatal(err)
163         }
164
165         fmt.Print(reply.Reply)
166 }
167
168 func runServer(apifiles []*vppapi.File, addr string) {
169         apiRoutes(apifiles, http.DefaultServeMux)
170
171         conn, err := govpp.Connect(socketclient.DefaultSocketName)
172         if err != nil {
173                 log.Fatal(err)
174         }
175
176         vpeRPC := vpe.NewServiceClient(conn)
177         c := vpe.RESTHandler(vpeRPC)
178
179         http.Handle("/", c)
180
181         log.Printf("listening on %v", addr)
182
183         if err := http.ListenAndServe(addr, nil); err != nil {
184                 log.Fatal(err)
185         }
186 }
187
188 func apiRoutes(apifiles []*vppapi.File, mux *http.ServeMux) {
189         for _, apifile := range apifiles {
190                 name := apifile.Name
191                 mux.HandleFunc("/vppapi/"+name, apiFileHandler(apifile))
192                 mux.HandleFunc("/raw/"+name, rawHandler(apifile))
193                 mux.HandleFunc("/rpc/"+name, rpcHandler(apifile))
194         }
195         mux.HandleFunc("/vppapi", apiHandler(apifiles))
196 }
197
198 func rpcHandler(apifile *vppapi.File) func(http.ResponseWriter, *http.Request) {
199         return func(w http.ResponseWriter, req *http.Request) {
200                 msgName := strings.TrimPrefix(req.URL.Path, "/rpc/"+apifile.Name+"/")
201                 if msgName == "" {
202                         http.Error(w, "no message name", 500)
203                         return
204                 }
205
206                 input, err := ioutil.ReadAll(req.Body)
207                 if err != nil {
208                         http.Error(w, err.Error(), 500)
209                         return
210                 }
211
212                 msgReq := make(map[string]interface{})
213                 err = json.Unmarshal(input, &msgReq)
214                 if err != nil {
215                         http.Error(w, err.Error(), 500)
216                         return
217                 }
218
219                 var msg *vppapi.Message
220                 for _, m := range apifile.Messages {
221                         if m.Name == msgName {
222                                 msg = &m
223                                 break
224                         }
225                 }
226                 if msg == nil {
227                         http.Error(w, "unknown message name: "+msgName, 500)
228                         return
229                 }
230
231         }
232 }
233
234 func apiHandler(apifiles []*vppapi.File) func(http.ResponseWriter, *http.Request) {
235         return func(w http.ResponseWriter, req *http.Request) {
236                 b, err := json.MarshalIndent(apifiles, "", "  ")
237                 if err != nil {
238                         http.Error(w, err.Error(), 500)
239                         return
240                 }
241                 w.Write(b)
242         }
243 }
244
245 func apiFileHandler(apifile *vppapi.File) func(http.ResponseWriter, *http.Request) {
246         return func(w http.ResponseWriter, req *http.Request) {
247                 b, err := json.MarshalIndent(apifile, "", "  ")
248                 if err != nil {
249                         http.Error(w, err.Error(), 500)
250                         return
251                 }
252                 w.Write(b)
253         }
254 }
255
256 func rawHandler(apifile *vppapi.File) func(http.ResponseWriter, *http.Request) {
257         return func(w http.ResponseWriter, req *http.Request) {
258                 b, err := ioutil.ReadFile(apifile.Path)
259                 if err != nil {
260                         http.Error(w, err.Error(), 500)
261                         return
262                 }
263                 w.Write(b)
264         }
265 }