blob: f3d975e19c0815869f1e8bb6087318051ca17eb1 [file] [log] [blame]
sslobodrd6e07e72019-01-31 16:07:20 -05001/*
2 * Copyright 2018-present Open Networking Foundation
3
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7
8 * http://www.apache.org/licenses/LICENSE-2.0
9
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16// gRPC affinity router with active/active backends
17
18package main
19
sslobodrd6e07e72019-01-31 16:07:20 -050020import (
21 "os"
22 "fmt"
23 "flag"
24 "path"
25 //"bufio"
26 "errors"
27 "os/exec"
28 "strconv"
29 "io/ioutil"
30 "encoding/json"
31 "text/template"
32 "github.com/golang/protobuf/proto"
33 "github.com/opencord/voltha-go/common/log"
34 pb "github.com/golang/protobuf/protoc-gen-go/descriptor"
35)
36
37type TestConfig struct {
38 configFile *string
39 logLevel *int
40 grpcLog *bool
41 Suites []string `json:"suites"`
42}
43
44
45type Connection struct {
46 Name string `json:"name"`
47 Port string `json:"port"`
48}
49
50type ProtoFile struct {
51 ImportPath string `json:"importPath"`
52 Service string `json:"service"`
53 Package string `json:"package"`
54}
55
56type ProtoSubst struct {
57 From string `json:"from"`
58 To string `json:"to"`
59}
60
61type Environment struct {
62 Command string `json:"cmdLine"`
63 ProtoFiles []ProtoFile `json:"protoFiles"`
64 ProtoDesc string `json:"protoDesc"`
65 Clients []Connection `json:"clients"`
66 Servers []Connection `json:"servers"`
67 Imports []string `json:"imports"`
68 ProtoSubsts []ProtoSubst `json:"protoSubst"`
69}
70
71type Rpc struct {
72 Client string `json:"client"`
73 Method string `json:"method"`
74 Param string `json:"param"`
75 Expect string `json:"expect"`
sslobodrd9daabf2019-02-05 13:14:21 -050076 MetaData []MetaD `json:"meta"`
77 ExpectMeta []MetaD `json:"expectMeta"`
sslobodrd6e07e72019-01-31 16:07:20 -050078}
79
80type MetaD struct {
81 Key string `json:"key"`
82 Val string `json:"value"`
83}
84
85type Server struct {
86 Name string `json:"name"`
87 Meta []MetaD `json:"meta"`
88}
89
90type Test struct {
91 Name string `json:"name"`
92 Send Rpc `json:"send"`
93 Servers []Server `json:"servers"`
94}
95
96type TestSuite struct {
97 Env Environment `json:"environment"`
98 Tests []Test `json:"tests"`
99}
100
101type ProtoImport struct {
102 Service string
103 Short string
104 Package string
105}
106
107type SendItem struct {
108 Client string
109 Method string
110 Param string
111 ParamType string
112 Expect string
113 ExpectType string
sslobodrd9daabf2019-02-05 13:14:21 -0500114 MetaData []MetaD
115 ExpectMeta []MetaD
sslobodrd6e07e72019-01-31 16:07:20 -0500116}
117
118type TestCase struct {
119 Name string
120 Send SendItem
121 Srvr []Server
122}
123
124type TestList struct {
125 ProtoImports []ProtoImport
126 Imports []string
127 Tests []TestCase
128}
129
130type ClientConfig struct {
131 Ct int
132 Name string
133 Port string
134 Imports []string
135 Methods map[string]*mthd
136 ProtoImports []ProtoImport
137}
138
139type ServerConfig struct {
140 Ct int
141 Name string
142 Port string
143 Imports []string
144 Methods map[string]*mthd
145 ProtoImports []ProtoImport
146}
147
148type mthd struct {
149 Pkg string
150 Svc string
151 Name string
152 Param string
153 Rtrn string
154 Ss bool // Server streaming
155 Cs bool // Clieent streaming
156}
157
158
159func parseCmd() (*TestConfig, error) {
160 config := &TestConfig{}
161 cmdParse := flag.NewFlagSet(path.Base(os.Args[0]), flag.ContinueOnError);
162 config.configFile = cmdParse.String("config", "suites.json", "The configuration file for the affinity router tester")
163 config.logLevel = cmdParse.Int("logLevel", 0, "The log level for the affinity router tester")
164 config.grpcLog = cmdParse.Bool("grpclog", false, "Enable GRPC logging")
165
166 err := cmdParse.Parse(os.Args[1:]);
167 if err != nil {
168 //return err
169 return nil, errors.New("Error parsing the command line");
170 }
171 return config, nil
172}
173
174func (conf * TestConfig) loadConfig() error {
175
176 configF, err := os.Open(*conf.configFile);
177 log.Info("Loading configuration from: ", *conf.configFile)
178 if err != nil {
179 log.Error(err)
180 return err
181 }
182
183 defer configF.Close()
184
185 if configBytes, err := ioutil.ReadAll(configF); err != nil {
186 log.Error(err)
187 return err
188 } else if err := json.Unmarshal(configBytes, conf); err != nil {
189 log.Error(err)
190 return err
191 }
192
193 return nil
194}
195
sslobodr13182842019-02-08 14:40:30 -0500196func (suite * TestSuite) loadSuite(suiteN string) error {
197 // Check if there's a corresponding go file for the suite.
198 // If it is present then the json test suite file is a
199 // template. Compile the go file and run it to process
200 // the template.
201 if _,err := os.Stat(suiteN+".go"); err == nil {
202 // Compile and run the the go file
203 log.Infof("Suite '%s' is a template, compiling '%s'", suiteN, suiteN+".go")
204 cmd := exec.Command("go", "build", "-o", suiteN+".te", suiteN+".go")
205 cmd.Stdin = os.Stdin
206 cmd.Stdout = os.Stdout
207 cmd.Stderr = os.Stderr
208 if err := cmd.Run(); err != nil {
209 log.Errorf("Error running the compile command:%v", err)
210 }
211 cmd = exec.Command("./"+suiteN+".te")
212 cmd.Stdin = os.Stdin
213 cmd.Stdout = os.Stdout
214 cmd.Stderr = os.Stderr
215 if err := cmd.Run(); err != nil {
216 log.Errorf("Error running the %s command:%v", suiteN+".te", err)
217 }
218 }
219 suiteF, err := os.Open(suiteN+".json");
220 log.Infof("Loading test suite from: %s", suiteN+".json")
sslobodrd6e07e72019-01-31 16:07:20 -0500221 if err != nil {
222 log.Error(err)
223 return err
224 }
225
226 defer suiteF.Close()
227
228 if suiteBytes, err := ioutil.ReadAll(suiteF); err != nil {
229 log.Error(err)
230 return err
231 } else if err := json.Unmarshal(suiteBytes, suite); err != nil {
232 log.Error(err)
233 return err
234 }
235
236 return nil
237}
238
239func loadProtoMap(fileName string, pkg string, svc string, substs []ProtoSubst) (map[string]*mthd, error) {
240 var mthds map[string]*mthd = make(map[string]*mthd)
241 var rtrn_err bool
242
243 // Load the protobuf descriptor file
244 protoDescriptor := &pb.FileDescriptorSet{}
245 fb, err := ioutil.ReadFile(fileName);
246 if err != nil {
247 log.Errorf("Could not open proto file '%s'",fileName)
248 rtrn_err = true
249 }
250 err = proto.Unmarshal(fb, protoDescriptor)
251 if err != nil {
252 log.Errorf("Could not unmarshal %s, %v", "proto.pb", err)
253 rtrn_err = true
254 }
255
256 var substM map[string]string = make(map[string]string)
257 // Create a substitution map
258 log.Debugf("Creating import map")
259 for _,v := range substs {
260 log.Debugf("Mapping from %s to %s", v.From, v.To)
261 substM[v.From] = v.To
262 }
263
264
265 // Build the a map containing the method as the key
266 // and the paramter and return types as the fields
267 for _,f := range protoDescriptor.File {
268 if *f.Package == pkg {
269 for _, s:= range f.Service {
270 if *s.Name == svc {
271 log.Debugf("Loading package data '%s' for service '%s'", *f.Package, *s.Name)
272 // Now create a map keyed by method name with the value being the
273 // field number of the route selector.
274 //var ok bool
275 for _,m := range s.Method {
276 // Find the input type in the messages and extract the
277 // field number and save it for future reference.
278 log.Debugf("Processing method (%s(%s) (%s){}",*m.Name, (*m.InputType)[1:], (*m.OutputType)[1:])
279 mthds[*m.Name] = &mthd{Pkg:pkg, Svc:svc, Name:*m.Name, Param:(*m.InputType)[1:],
280 Rtrn:(*m.OutputType)[1:]}
281 if m.ClientStreaming != nil && *m.ClientStreaming == true {
282 log.Debugf("Method %s is a client streaming method", *m.Name)
283 mthds[*m.Name].Cs = true
284 }
285 if m.ServerStreaming != nil && *m.ServerStreaming == true {
286 log.Debugf("Method %s is a server streaming method", *m.Name)
287 mthds[*m.Name].Ss = true
288 }
289 // Perform the required substitutions
290 if _,ok := substM[mthds[*m.Name].Param]; ok == true {
291 mthds[*m.Name].Param = substM[mthds[*m.Name].Param]
292 }
293 if _,ok := substM[mthds[*m.Name].Rtrn]; ok == true {
294 mthds[*m.Name].Rtrn = substM[mthds[*m.Name].Rtrn]
295 }
296 }
297 }
298 }
299 }
300 }
301 if rtrn_err {
302 return nil,errors.New(fmt.Sprintf("Failed to load protobuf descriptor file '%s'",fileName))
303 }
304 return mthds, nil
305}
306
307// Server source code generation
308func generateServers(conf *TestConfig, suiteDir string, ts * TestSuite,
309 t *template.Template) error {
310 var servers []ServerConfig
311
312 for k,v := range ts.Env.Servers {
313 log.Infof("Generating the code for server[%d]: %s", k, v.Name)
314 sc := &ServerConfig{Name:v.Name, Port:v.Port, Ct:k, Imports:ts.Env.Imports}
315 for k1,v1 := range ts.Env.ProtoFiles {
316 imp := &ProtoImport{Short:"pb"+strconv.Itoa(k1),
317 Package:v1.ImportPath+v1.Package,
318 Service:v1.Service}
319 imp = &ProtoImport{Short:v1.Package,
320 Package:v1.ImportPath+v1.Package,
321 Service:v1.Service}
322 sc.ProtoImports = append(sc.ProtoImports, *imp)
323 // Compile the template from the file
324 log.Debugf("Proto substs: %v", ts.Env.ProtoSubsts)
325 if mthds, err := loadProtoMap(ts.Env.ProtoDesc, v1.Package,
326 v1.Service, ts.Env.ProtoSubsts); err != nil {
327 log.Errorf("Unable to process proto descriptor file %s for package: %s, service: %s",
328 ts.Env.ProtoDesc, v1.Package, v1.Service)
329 return err
330 } else {
331 //Generate all the function calls required by the
332 sc.Methods = mthds
333 }
334 }
335 log.Debugf("Server: %v", *sc)
336 // Save this server for the next steop
337 servers = append(servers, *sc)
338 // Open an output file to put the output in.
339 if f,err := os.Create(suiteDir+"/"+v.Name+".go"); err == nil {
340 defer f.Close()
341 //if err := t.ExecuteTemplate(os.Stdout, "server.go", *sc); err != nil {}
342 if err := t.ExecuteTemplate(f, "server.go", *sc); err != nil {
343 log.Errorf("Unable to execute template for server[%d]: %s: %v", k, v.Name, err)
344 return err
345 }
346 }
347 }
348 // Generate the server initialization code
349 if f,err := os.Create(suiteDir+"/serverInit.go"); err == nil {
350 defer f.Close()
351 //if err := t.ExecuteTemplate(os.Stdout, "server.go", *sc); err != nil {}
352 if err := t.ExecuteTemplate(f, "serverInit.go", servers); err != nil {
353 log.Errorf("Unable to execute template for serverInit.go: %v", err)
354 return err
355 }
356 }
357
358 return nil
359}
360
361func generateClients(conf *TestConfig, suiteDir string, ts * TestSuite,
362 t *template.Template) error {
363 var clients []ClientConfig
364 for k,v := range ts.Env.Clients {
365 log.Infof("Generating the code for client[%d]: %s", k, v.Name)
366 cc := &ClientConfig{Name:v.Name, Port:v.Port, Ct:k, Imports:ts.Env.Imports}
367 // TODO: This loop makes no sense, the only proto map that would remain
368 // after this loop is the last one loaded. Fix this to load the map
369 // for all services not just the last one.
370 for _,v1 := range ts.Env.ProtoFiles {
371 imp := &ProtoImport{Short:v1.Package,
372 Package:v1.ImportPath+v1.Package,
373 Service:v1.Service}
374 cc.ProtoImports = append(cc.ProtoImports, *imp)
375 // Compile the template from the file
376 log.Debugf("Proto substs: %v", ts.Env.ProtoSubsts)
377 if mthds, err := loadProtoMap(ts.Env.ProtoDesc, v1.Package,
378 v1.Service, ts.Env.ProtoSubsts); err != nil {
379 log.Errorf("Unable to process proto descriptor file %s for package: %s, service: %s",
380 ts.Env.ProtoDesc, v1.Package, v1.Service)
381 return err
382 } else {
383 //Generate all the function calls required by the
384 cc.Methods = mthds
385 }
386 }
387 clients = append(clients, *cc)
388 if f,err := os.Create(suiteDir+"/"+v.Name+".go"); err == nil {
389 _=f
390 defer f.Close()
391 if err := t.ExecuteTemplate(f, "client.go", cc); err != nil {
392 log.Errorf("Unable to execute template for client.go: %v", err)
393 return err
394 }
395 } else {
396 log.Errorf("Couldn't create file %s : %v", suiteDir+"/client.go", err)
397 return err
398 }
399 }
400 if f,err := os.Create(suiteDir+"/clientInit.go"); err == nil {
401 defer f.Close()
402 //if err := t.ExecuteTemplate(os.Stdout, "server.go", *sc); err != nil {}
403 if err := t.ExecuteTemplate(f, "clientInit.go", clients); err != nil {
404 log.Errorf("Unable to execute template for clientInit.go: %v", err)
405 return err
406 }
407 }
408 return nil
409}
410
411func serverExists(srvr string, ts * TestSuite) bool {
412 for _,v := range ts.Env.Servers {
413 if v.Name == srvr {
414 return true
415 }
416 }
417 return false
418}
419func generateTestCases(conf *TestConfig, suiteDir string, ts * TestSuite,
420 t *template.Template) error {
421 var mthdMap map[string]*mthd
422 // Generate the test cases
423 log.Info("Generating the test cases: runTests.go")
424 tc := &TestList{Imports:ts.Env.Imports}
425
426 // Load the proto descriptor file
427 // TODO: This loop makes no sense, the only proto map that would remain
428 // after this loop is the last one loaded. Fix this to load the map
429 // for all services not just the last one.
430 for _,v := range ts.Env.ProtoFiles {
431 imp := &ProtoImport{Short:v.Package,
432 Package:v.ImportPath+v.Package,
433 Service:v.Service}
434 tc.ProtoImports = append(tc.ProtoImports, *imp)
435 // Compile the template from the file
436 log.Debugf("Proto substs: %v", ts.Env.ProtoSubsts)
437 if mthds, err := loadProtoMap(ts.Env.ProtoDesc, v.Package,
438 v.Service, ts.Env.ProtoSubsts); err != nil {
439 log.Errorf("Unable to process proto descriptor file %s for package: %s, service: %s",
440 ts.Env.ProtoDesc, v.Package, v.Service)
441 return err
442 } else {
443 mthdMap = mthds
444 }
445 }
446 // Create the test data structure for the template
447 for _,v := range ts.Tests {
448 var test TestCase
449
450 test.Name = v.Name
451 test.Send.Client = v.Send.Client
452 test.Send.Method = v.Send.Method
453 test.Send.Param = v.Send.Param
454 test.Send.ParamType = mthdMap[test.Send.Method].Param
455 test.Send.Expect = v.Send.Expect
456 test.Send.ExpectType = mthdMap[test.Send.Method].Rtrn
sslobodrd9daabf2019-02-05 13:14:21 -0500457 for _,v1 := range v.Send.MetaData {
458 test.Send.MetaData = append(test.Send.MetaData,v1)
459 }
460 for _,v1 := range v.Send.ExpectMeta {
461 test.Send.ExpectMeta = append(test.Send.ExpectMeta,v1)
462 }
sslobodrd6e07e72019-01-31 16:07:20 -0500463 for _,v1 := range v.Servers {
464 var srvr Server
465 if serverExists(v1.Name, ts) == false {
466 log.Errorf("Server '%s' is not defined!!", v1.Name)
467 return errors.New(fmt.Sprintf("Failed to build test case %s", v.Name))
468 }
469 srvr.Name = v1.Name
470 srvr.Meta = v1.Meta
471 test.Srvr = append(test.Srvr, srvr)
472 }
473 tc.Tests = append(tc.Tests, test)
474 }
475 if f,err := os.Create(suiteDir+"/runTests.go"); err == nil {
476 if err := t.ExecuteTemplate(f, "runTests.go", tc); err != nil {
477 log.Errorf("Unable to execute template for runTests.go: %v", err)
478 }
479 f.Close()
480 } else {
481 log.Errorf("Couldn't create file %s : %v", suiteDir+"/runTests.go", err)
482 }
483 return nil
484}
485
486func generateTestSuites(conf *TestConfig, srcDir string, outDir string) error {
487
488 // Create a directory for the tests
489 if err := os.Mkdir(srcDir, 0777); err != nil {
490 log.Errorf("Unable to create directory 'tests':%v\n", err)
491 return err
492 }
493
494 for k,v := range conf.Suites {
495 var suiteDir string = srcDir+"/"+v
496 log.Debugf("Suite[%d] - %s", k, v)
497 ts := &TestSuite{}
498 ts.loadSuite(v)
499 log.Debugf("Suite %s: %v", v, ts)
500 log.Info("Processing test suite %s", v)
501
502 t := template.Must(template.New("").ParseFiles("../templates/server.go",
503 "../templates/serverInit.go",
504 "../templates/client.go",
505 "../templates/clientInit.go",
506 "../templates/runTests.go",
507 "../templates/main.go"))
508 // Create a directory for he source code for this test suite
509 if err := os.Mkdir(suiteDir, 0777); err != nil {
510 log.Errorf("Unable to create directory '%s':%v\n", v, err)
511 return err
512 }
513 // Generate the server source files
514 if err := generateServers(conf, suiteDir, ts, t); err != nil {
515 log.Errorf("Unable to generate server source files: %v", err)
516 return err
517 }
518 // Generate the client source files
519 if err := generateClients(conf, suiteDir, ts, t); err != nil {
520 log.Errorf("Unable to generate client source files: %v", err)
521 return err
522 }
523 // Generate the test case source file
524 if err := generateTestCases(conf, suiteDir, ts, t); err != nil {
525 log.Errorf("Unable to generate test case source file: %v", err)
526 return err
527 }
528
529 // Finally generate the main file
530 log.Info("Generating main.go")
531 if f,err := os.Create(suiteDir+"/main.go"); err == nil {
532 if err := t.ExecuteTemplate(f, "main.go", ts.Env); err != nil {
533 log.Errorf("Unable to execute template for main.go: %v", err)
534 }
535 f.Close()
536 } else {
537 log.Errorf("Couldn't create file %s : %v", suiteDir+"/main.go", err)
538 }
539
540 log.Infof("Compiling test suite: %s in directory %s", v, suiteDir)
541 if err := os.Chdir(suiteDir); err != nil {
542 log.Errorf("Could not change to directory '%s':%v",suiteDir, err)
543 }
sslobodr13182842019-02-08 14:40:30 -0500544 cmd := exec.Command("go", "build", "-o", outDir+"/"+v+".e")
sslobodrd6e07e72019-01-31 16:07:20 -0500545 cmd.Stdin = os.Stdin
546 cmd.Stdout = os.Stdout
547 cmd.Stderr = os.Stderr
548 if err := cmd.Run(); err != nil {
549 log.Errorf("Error running the compile command:%v", err)
550 }
551 if err := os.Chdir("../../suites"); err != nil {
552 log.Errorf("Could not change to directory '%s':%v","../../suites", err)
553 }
sslobodrd6e07e72019-01-31 16:07:20 -0500554 }
555 return nil
556
557}
558
559func generateTestDriver(conf *TestConfig, srcDir string, outDir string) error {
560 // Generate the main test driver file
561 if err := os.Mkdir(srcDir, 0777); err != nil {
562 log.Errorf("Unable to create directory 'driver':%v\n", err)
563 return err
564 }
565 t := template.Must(template.New("").ParseFiles("../templates/runAll.go"))
566 if f,err := os.Create(srcDir+"/runAll.go"); err == nil {
567 if err := t.ExecuteTemplate(f, "runAll.go", conf.Suites); err != nil {
568 log.Errorf("Unable to execute template for main.go: %v", err)
569 }
570 f.Close()
571 } else {
572 log.Errorf("Couldn't create file %s : %v", srcDir+"/runAll.go", err)
573 }
574
575 // Compile the test driver file
576 log.Info("Compiling the test driver")
577 if err := os.Chdir("../tests/driver"); err != nil {
578 log.Errorf("Could not change to directory 'driver':%v", err)
579 }
580 cmd := exec.Command("go", "build", "-o", outDir+"/runAll")
581 cmd.Stdin = os.Stdin
582 cmd.Stdout = os.Stdout
583 cmd.Stderr = os.Stderr
584 if err := cmd.Run(); err != nil {
585 log.Errorf("Error running the compile command:%v", err)
586 }
587 if err := os.Chdir("../../suites"); err != nil {
588 log.Errorf("Could not change to directory 'driver':%v",err)
589 }
590
591 return nil
592}
593
594
595func main() {
596
597 conf,err := parseCmd()
598 if err != nil {
599 fmt.Printf("Error: %v\n", err)
600 return
601 }
602
603 // Setup logging
604 if _, err := log.SetDefaultLogger(log.JSON, *conf.logLevel, nil); err != nil {
605 log.With(log.Fields{"error": err}).Fatal("Cannot setup logging")
606 }
607
608 defer log.CleanUp()
609
610 // Parse the config file
611 if err := conf.loadConfig(); err != nil {
612 log.Error(err)
613 }
614
615 generateTestSuites(conf, "../tests", "/src/tests")
616 generateTestDriver(conf, "../tests/driver", "/src/tests")
617 return
618}
619