2 * Copyright (C) 2017-2018 "IoT.bzh"
3 * Author Clément Bénier <clement.benier@iot.bzh>
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
32 common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib"
33 "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1"
34 "github.com/stretchr/testify/assert"
37 func launchSSHd(sshDir string, proc **os.Process, sshdCmd string) (*os.File, string) {
39 argsProcessSSHd := []string{
42 sshDir + "/sshd_config",
45 sshDir + "/ssh_host_rsa_key",
47 "AuthorizedKeysFile=" + sshDir + "/authorized_keys",
51 logFile := logDir + logFileSSHd
52 file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY, 0644)
57 tmpProc, err := os.StartProcess(argsProcessSSHd[0], argsProcessSSHd, &os.ProcAttr{
58 Files: []*os.File{os.Stdin, file, file},
67 func InitSSH(t *testing.T, procSSHd **os.Process) (string, string) {
68 Debug(t, "Initialise ssh with local user")
69 sshDir := path.Join(os.Getenv(envRootCfgDir), "ssh")
70 cmd := exec.Command("cp", "-r", sshFixturesDir, sshDir)
73 assert.Nil(t, cmd.Run())
75 cmd = exec.Command("ls", sshDir)
77 assert.Nil(t, cmd.Run())
79 files := strings.Split(fmt.Sprint(cmd.Stdout), "\n")
81 for _, f := range files {
83 file := sshDir + "/" + f
84 cmd = exec.Command("chmod", "600", file)
86 assert.Nil(t, cmd.Run())
90 var outSSHd bytes.Buffer
92 cmd = exec.Command("which", "sshd")
95 if common.Exists("/usr/sbin/sshd") {
96 sshdCmd = "/usr/sbin/sshd"
97 } else if common.Exists("/usr/bin/sshd") {
98 sshdCmd = "/usr/sbin/sshd"
100 assert.FailNow(t, "Cannot find sshd command, please install it or set in your PATH")
103 sshdCmd = strings.TrimSpace(fmt.Sprint(cmd.Stdout))
107 _, port = launchSSHd(sshDir, procSSHd, sshdCmd)
108 go func(p *os.Process) {
109 Debug(t, "sshd is launching")
110 if status, err := p.Wait(); err != nil {
111 log.Fatalf("status=%v\n err=%v\n", status, err)
117 /*wait for terminal prompt*/
118 func waitForPrompt(t *testing.T, channel chan xsapiv1.TerminalOutMsg, prompt string) string {
119 step := 1 * time.Millisecond
120 timeout := 10 * time.Second
121 current := 0 * time.Second
123 re := regexp.MustCompile("^" + prompt)
127 case outMsg := <-channel:
128 out += string(outMsg.Stdout)
129 if string(outMsg.Stderr) != "" {
130 out += string(outMsg.Stderr)
132 for _, line := range strings.Split(out, "\n") {
133 if re.MatchString(line) {
137 case <-time.After(step):
138 current = current + step
139 if current >= timeout {
140 assert.FailNow(t, "Never received prompt message from terminal (output:"+out+")")
146 func ConnectTargetEvents(t *testing.T, channel chan xsapiv1.TargetConfig) {
147 sCli.Conn.On(xsapiv1.EVTTargetAdd, func(e xsapiv1.EventMsg) {
148 target, _ := e.DecodeTargetEvent()
152 args := xsapiv1.EventRegisterArgs{Name: xsapiv1.EVTTargetAdd}
153 assert.Nil(t, HTTPCli.Post("/events/register", args, nil))
155 sCli.Conn.On(xsapiv1.EVTTargetRemove, func(e xsapiv1.EventMsg) {
156 target, _ := e.DecodeTargetEvent()
160 args = xsapiv1.EventRegisterArgs{Name: xsapiv1.EVTTargetRemove}
161 assert.Nil(t, HTTPCli.Post("/events/register", args, nil))
164 func DisconnectTargetEvents(t *testing.T) {
165 args := xsapiv1.EventRegisterArgs{Name: xsapiv1.EVTTargetAdd}
166 assert.Nil(t, HTTPCli.Post("/events/unregister", args, nil))
167 args = xsapiv1.EventRegisterArgs{Name: xsapiv1.EVTTargetRemove}
168 assert.Nil(t, HTTPCli.Post("/events/unregister", args, nil))
171 func ConnectTermEvents(t *testing.T, channel chan xsapiv1.TerminalConfig) {
172 sCli.Conn.On(xsapiv1.EVTTargetTerminalAdd, func(e xsapiv1.EventMsg) {
173 termEvt, _ := e.DecodeTerminalEvent()
177 args := xsapiv1.EventRegisterArgs{Name: xsapiv1.EVTTargetTerminalAdd}
178 assert.Nil(t, HTTPCli.Post("/events/register", args, nil))
180 sCli.Conn.On(xsapiv1.EVTTargetTerminalStateChange, func(e xsapiv1.EventMsg) {
181 termEvt, _ := e.DecodeTerminalEvent()
185 args = xsapiv1.EventRegisterArgs{Name: xsapiv1.EVTTargetTerminalStateChange}
186 assert.Nil(t, HTTPCli.Post("/events/register", args, nil))
188 sCli.Conn.On(xsapiv1.EVTTargetTerminalRemove, func(e xsapiv1.EventMsg) {
189 termEvt, _ := e.DecodeTerminalEvent()
193 args = xsapiv1.EventRegisterArgs{Name: xsapiv1.EVTTargetTerminalRemove}
194 assert.Nil(t, HTTPCli.Post("/events/register", args, nil))
197 func DisconnectTermEvents(t *testing.T) {
198 args := xsapiv1.EventRegisterArgs{Name: xsapiv1.EVTTargetTerminalAdd}
199 assert.Nil(t, HTTPCli.Post("/events/unregister", args, nil))
200 args = xsapiv1.EventRegisterArgs{Name: xsapiv1.EVTTargetTerminalStateChange}
201 assert.Nil(t, HTTPCli.Post("/events/unregister", args, nil))
202 args = xsapiv1.EventRegisterArgs{Name: xsapiv1.EVTTargetTerminalRemove}
203 assert.Nil(t, HTTPCli.Post("/events/unregister", args, nil))
206 func AddTargets(t *testing.T, nbTargets int, chTarget chan xsapiv1.TargetConfig) []string {
207 listID := make([]string, nbTargets)
208 for i := 0; i < nbTargets; i++ {
210 target := xsapiv1.TargetConfig{
211 Name: "fakeTarget" + strconv.Itoa(i),
212 Type: xsapiv1.TypeTgtStandard,
216 assert.Nil(t, HTTPCli.Post("/targets", target, &target))
217 Debugf(t, "add target %v", target.Name)
218 targetEvt := <-chTarget //waiting for event targetAdd
219 assert.Equal(t, target.ID, targetEvt.ID)
220 listID[i] = target.ID
222 for i := 0; i < nbTargets; i++ {
223 var target xsapiv1.TargetConfig
224 assert.Nil(t, HTTPCli.Get("/targets/"+listID[i], &target))
225 assert.Equal(t, target.Status, "Enable")
230 func AddTerms(t *testing.T, nbTerms int, listID []string, chTermEvt chan xsapiv1.TerminalConfig, sshDir string, port string) {
231 for j := 0; j < len(listID); j++ {
232 listTermsID := make([]string, nbTerms)
233 for i := 0; i < nbTerms; i++ {
234 term := xsapiv1.TerminalConfig{
235 Name: "terminal" + strconv.Itoa(i),
236 Type: xsapiv1.TypeTermSSH,
243 "StrictHostKeyChecking=no",
246 /*add terminal on target*/
247 assert.Nil(t, HTTPCli.Post("/targets/"+listID[j]+"/terminals", term, &term))
248 Debugf(t, "add terminal %v", term.Name)
249 termEvt := <-chTermEvt //waiting for event terminalAdd*/
250 assert.Equal(t, term.ID, termEvt.ID)
251 listTermsID[i] = term.ID
253 assert.Equal(t, len(listTermsID), nbTerms)
254 for i := 0; i < nbTerms; i++ {
255 var term xsapiv1.TerminalConfig
256 assert.Nil(t, HTTPCli.Get("/targets/"+listID[j]+"/terminals/"+listTermsID[i], &term))
257 assert.Equal(t, term.Status, "Close")
262 func PostTerms(t *testing.T, post string, chTermEvt chan xsapiv1.TerminalConfig, chTerm chan xsapiv1.TerminalOutMsg,
271 var targets []xsapiv1.TargetConfig
272 assert.Nil(t, HTTPCli.Get("/targets", &targets))
273 for i := 0; i < len(targets); i++ {
274 var terms []xsapiv1.TerminalConfig
275 assert.Nil(t, HTTPCli.Get("/targets/"+targets[i].ID+"/terminals", &terms))
276 listTermsID := make([]string, len(terms))
277 for j := 0; j < len(terms); j++ {
278 var term xsapiv1.TerminalConfig
279 /*post action on term*/
280 assert.Nil(t, HTTPCli.Post("/targets/"+targets[i].ID+"/terminals/"+terms[j].ID+"/"+post, terms[j], &term))
281 Debugf(t, "%v terminal %v", post, term.Name)
282 termEvt := <-chTermEvt //waiting for event terminalStateChange
284 data := []byte("PS1=" + prompt + " bash -norc\n")
285 assert.Nil(t, sCli.Conn.Emit(xsapiv1.TerminalInEvent, data))
286 waitForPrompt(t, chTerm, prompt)
288 assert.Equal(t, term.ID, termEvt.ID)
289 assert.Equal(t, term.Status, status)
290 assert.Equal(t, termEvt.Status, status)
291 listTermsID[i] = term.ID
293 time.Sleep(10 * time.Millisecond)
294 for j := 0; j < len(listTermsID); j++ {
295 var term xsapiv1.TerminalConfig
296 assert.Nil(t, HTTPCli.Get("/targets/"+targets[i].ID+"/terminals/"+listTermsID[i], &term))
297 assert.True(t, strings.EqualFold(term.Status, post))
298 Debugf(t, "check that term status %v is %v", term.Name, post)
303 func RemoveTermsTargets(t *testing.T, chTarget chan xsapiv1.TargetConfig, chTermEvt chan xsapiv1.TerminalConfig) {
304 var targets []xsapiv1.TargetConfig
305 assert.Nil(t, HTTPCli.Get("/targets", &targets))
306 for i := 0; i < len(targets); i++ {
307 var terms []xsapiv1.TerminalConfig
308 assert.Nil(t, HTTPCli.Get("/targets/"+targets[i].ID+"/terminals", &terms))
309 for j := 0; j < len(terms); j++ {
310 var term xsapiv1.TerminalConfig
311 assert.Nil(t, HTTPCli.Delete("/targets/"+targets[i].ID+"/terminals/"+terms[j].ID, &term))
312 termEvt := <-chTermEvt
313 assert.Equal(t, term.ID, termEvt.ID)
314 assert.NotNil(t, HTTPCli.Delete("/targets/"+targets[i].ID+"/terminals/"+terms[j].ID, &term))
315 Debugf(t, "remove terminal %v", term.Name)
317 var tgtRes xsapiv1.TargetConfig
318 assert.Nil(t, HTTPCli.Delete("/targets/"+targets[i].ID, &tgtRes))
319 targetEvt := <-chTarget //waiting for remove terminal event
320 assert.Equal(t, tgtRes.ID, targetEvt.ID)
321 assert.Equal(t, targets[i].ID, tgtRes.ID)
324 func TestTarget(t *testing.T) {
325 prompt := "--PROMPT--"
326 var procSSHd *os.Process
327 sshDir, port := InitSSH(t, &procSSHd)
328 defer procSSHd.Kill()
332 /*channel for target events*/
333 chTarget := make(chan xsapiv1.TargetConfig)
334 defer close(chTarget)
335 ConnectTargetEvents(t, chTarget)
337 /*channel for terminal events*/
338 chTermEvt := make(chan xsapiv1.TerminalConfig)
339 defer close(chTermEvt)
340 ConnectTermEvents(t, chTermEvt)
342 /*check that targetArray is empty at startup*/
343 var targetArray []xsapiv1.TargetConfig
344 assert.Nil(t, HTTPCli.Get("/targets", &targetArray))
345 assert.Equal(t, len(targetArray), 0)
347 listID := AddTargets(t, nbTargets, chTarget)
348 AddTerms(t, nbTermsByTarget, listID, chTermEvt, sshDir, port)
350 /*channel for TerminalOutMsg*/
351 chTerm := make(chan xsapiv1.TerminalOutMsg)
354 /*connect on terminalOutMsg event*/
355 sCli.Conn.On(xsapiv1.TerminalOutEvent, func(ev xsapiv1.TerminalOutMsg) {
360 PostTerms(t, "open", chTermEvt, chTerm, prompt)
362 /*create toto file through terminals*/
363 rootCfgDir := os.Getenv(envRootCfgDir)
364 totoFile := path.Join(rootCfgDir, "toto")
366 /*test with 2 terminals*/
367 for i := 0; i < 2; i++ {
368 totoFileCurrent := totoFile + strconv.Itoa(i)
369 /*send cmd though term*/
370 data := []byte("echo helloWorld" + strconv.Itoa(i) + " >> " + totoFileCurrent + "\n")
371 Debugf(t, "send following command through terminal: %v", string(data))
372 assert.Nil(t, sCli.Conn.Emit(xsapiv1.TerminalInEvent, data))
373 waitForPrompt(t, chTerm, prompt) //waiting for terminal prompt
375 /*check that toto file is created*/
376 _, err := os.Stat(totoFileCurrent)
379 /*send cmd though term*/
380 data = []byte("cat " + totoFileCurrent + "\n")
381 Debugf(t, "send following command through terminal: %v", string(data))
382 assert.Nil(t, sCli.Conn.Emit(xsapiv1.TerminalInEvent, data))
385 termOut := <-chTerm //result of cat cmd
386 waitForPrompt(t, chTerm, prompt) //wait for terminal prompt
387 /*check that terminal msg is what was written before*/
388 assert.Equal(t, string(termOut.Stdout), "helloWorld"+strconv.Itoa(i)+"\r\n")
389 Debugf(t, "check terminal output msg: %v", string(termOut.Stdout))
392 PostTerms(t, "close", chTermEvt, nil, prompt)
394 /*remove targets and terms*/
395 RemoveTermsTargets(t, chTarget, chTermEvt)
396 DisconnectTargetEvents(t)
397 DisconnectTermEvents(t)
400 func TestTargetErrors(t *testing.T) {
401 /*cannot create empty target*/
402 target := xsapiv1.TargetConfig{}
403 var targetRes xsapiv1.TargetConfig
404 assert.NotNil(t, HTTPCli.Post("/targets", target, &targetRes))
405 Debugf(t, "error while creating empty target")
406 /*check cannot create target with no IP*/
407 target.Type = xsapiv1.TypeTgtStandard
408 assert.NotNil(t, HTTPCli.Post("/targets", target, &targetRes))
409 Debugf(t, "error while creating target without IP")
410 target.IP = "127.0.0.1"
411 assert.Nil(t, HTTPCli.Post("/targets", target, &targetRes))
412 Debugf(t, "create target %v", targetRes.Name)
414 /*cannot create empty terminal*/
415 term := xsapiv1.TerminalConfig{}
416 var termRes xsapiv1.TerminalConfig
417 assert.NotNil(t, HTTPCli.Post("/targets/"+targetRes.ID+"/terminals", term, &termRes))
418 Debugf(t, "error while creating empty terminal")
419 term.Type = xsapiv1.TypeTermSSH
420 assert.NotNil(t, HTTPCli.Post("/targets/"+"1010"+"/terminals", term, &termRes))
421 Debugf(t, "error while creating terminal on an non existing target")
422 assert.Nil(t, HTTPCli.Post("/targets/"+targetRes.ID+"/terminals", term, &termRes))
423 assert.Nil(t, HTTPCli.Post("/targets/"+targetRes.ID+"/terminals", term, &termRes))
424 assert.Nil(t, HTTPCli.Post("/targets/"+targetRes.ID+"/terminals", term, &termRes))
425 assert.Nil(t, HTTPCli.Post("/targets/"+targetRes.ID+"/terminals", term, &termRes))
426 Debugf(t, "create several terminals")
428 /*remove targets and terms*/
429 var targetArray []xsapiv1.TargetConfig
430 assert.Nil(t, HTTPCli.Get("/targets", &targetArray))
431 for i := 0; i < len(targetArray); i++ {
432 var termArray []xsapiv1.TerminalConfig
433 assert.Nil(t, HTTPCli.Get("/targets/"+targetArray[i].ID+"/terminals", &termArray))
434 for j := 0; j < len(termArray); j++ {
435 assert.Nil(t, HTTPCli.Delete("/targets/"+targetArray[i].ID+"/terminals/"+termArray[j].ID, &termRes))
436 Debugf(t, "delete terminal %v", termRes.Name)
437 assert.NotNil(t, HTTPCli.Delete("/targets/"+targetArray[i].ID+"/terminals/"+termArray[j].ID, &termRes))
438 Debugf(t, "error while deleting an already deleted terminal %v", termRes.Name)
440 var tgtRes xsapiv1.TargetConfig
441 assert.Nil(t, HTTPCli.Delete("/targets/"+targetArray[i].ID, &tgtRes))
442 Debugf(t, "delete target %v", tgtRes.Name)
443 assert.Equal(t, targetArray[i].ID, tgtRes.ID)
444 assert.NotNil(t, HTTPCli.Delete("/targets/"+targetArray[i].ID, &tgtRes))
445 Debugf(t, "error while deleting an already deleted target %v", tgtRes.Name)