15954: Avoid modifying caller's global environment.
[arvados.git] / lib / service / cmd_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 // package service provides a cmd.Handler that brings up a system service.
6 package service
7
8 import (
9         "bytes"
10         "context"
11         "crypto/tls"
12         "fmt"
13         "io/ioutil"
14         "net/http"
15         "os"
16         "testing"
17         "time"
18
19         "git.arvados.org/arvados.git/sdk/go/arvados"
20         "git.arvados.org/arvados.git/sdk/go/ctxlog"
21         "github.com/prometheus/client_golang/prometheus"
22         check "gopkg.in/check.v1"
23 )
24
25 func Test(t *testing.T) {
26         check.TestingT(t)
27 }
28
29 var _ = check.Suite(&Suite{})
30
31 type Suite struct{}
32
33 func (*Suite) TestCommand(c *check.C) {
34         cf, err := ioutil.TempFile("", "cmd_test.")
35         c.Assert(err, check.IsNil)
36         defer os.Remove(cf.Name())
37         defer cf.Close()
38         fmt.Fprintf(cf, "Clusters:\n zzzzz:\n  SystemRootToken: abcde\n  NodeProfiles: {\"*\": {\"arvados-controller\": {Listen: \":1234\"}}}")
39
40         healthCheck := make(chan bool, 1)
41         ctx, cancel := context.WithCancel(context.Background())
42         defer cancel()
43
44         cmd := Command(arvados.ServiceNameController, func(ctx context.Context, _ *arvados.Cluster, token string, reg *prometheus.Registry) Handler {
45                 c.Check(ctx.Value("foo"), check.Equals, "bar")
46                 c.Check(token, check.Equals, "abcde")
47                 return &testHandler{ctx: ctx, healthCheck: healthCheck}
48         })
49         cmd.(*command).ctx = context.WithValue(ctx, "foo", "bar")
50
51         done := make(chan bool)
52         var stdin, stdout, stderr bytes.Buffer
53
54         go func() {
55                 cmd.RunCommand("arvados-controller", []string{"-config", cf.Name()}, &stdin, &stdout, &stderr)
56                 close(done)
57         }()
58         select {
59         case <-healthCheck:
60         case <-done:
61                 c.Error("command exited without health check")
62         }
63         cancel()
64         c.Check(stdout.String(), check.Equals, "")
65         c.Check(stderr.String(), check.Matches, `(?ms).*"msg":"CheckHealth called".*`)
66 }
67
68 func (*Suite) TestTLS(c *check.C) {
69         cwd, err := os.Getwd()
70         c.Assert(err, check.IsNil)
71
72         stdin := bytes.NewBufferString(`
73 Clusters:
74  zzzzz:
75   SystemRootToken: abcde
76   Services:
77    Controller:
78     ExternalURL: "https://localhost:12345"
79     InternalURLs: {"https://localhost:12345": {}}
80   TLS:
81    Key: file://` + cwd + `/../../services/api/tmp/self-signed.key
82    Certificate: file://` + cwd + `/../../services/api/tmp/self-signed.pem
83 `)
84
85         called := make(chan bool)
86         cmd := Command(arvados.ServiceNameController, func(ctx context.Context, _ *arvados.Cluster, token string, reg *prometheus.Registry) Handler {
87                 return &testHandler{handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
88                         w.Write([]byte("ok"))
89                         close(called)
90                 })}
91         })
92
93         exited := make(chan bool)
94         var stdout, stderr bytes.Buffer
95         go func() {
96                 cmd.RunCommand("arvados-controller", []string{"-config", "-"}, stdin, &stdout, &stderr)
97                 close(exited)
98         }()
99         got := make(chan bool)
100         go func() {
101                 defer close(got)
102                 client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
103                 for range time.NewTicker(time.Millisecond).C {
104                         resp, err := client.Get("https://localhost:12345")
105                         if err != nil {
106                                 c.Log(err)
107                                 continue
108                         }
109                         body, err := ioutil.ReadAll(resp.Body)
110                         c.Logf("status %d, body %s", resp.StatusCode, string(body))
111                         c.Check(resp.StatusCode, check.Equals, http.StatusOK)
112                         break
113                 }
114         }()
115         select {
116         case <-called:
117         case <-exited:
118                 c.Error("command exited without calling handler")
119         case <-time.After(time.Second):
120                 c.Error("timed out")
121         }
122         select {
123         case <-got:
124         case <-exited:
125                 c.Error("command exited before client received response")
126         case <-time.After(time.Second):
127                 c.Error("timed out")
128         }
129         c.Log(stderr.String())
130 }
131
132 type testHandler struct {
133         ctx         context.Context
134         handler     http.Handler
135         healthCheck chan bool
136 }
137
138 func (th *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { th.handler.ServeHTTP(w, r) }
139 func (th *testHandler) CheckHealth() error {
140         ctxlog.FromContext(th.ctx).Info("CheckHealth called")
141         select {
142         case th.healthCheck <- true:
143         default:
144         }
145         return nil
146 }