17994: Update comment.
[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 type key int
33
34 const (
35         contextKey key = iota
36 )
37
38 func (*Suite) TestCommand(c *check.C) {
39         cf, err := ioutil.TempFile("", "cmd_test.")
40         c.Assert(err, check.IsNil)
41         defer os.Remove(cf.Name())
42         defer cf.Close()
43         fmt.Fprintf(cf, "Clusters:\n zzzzz:\n  SystemRootToken: abcde\n  NodeProfiles: {\"*\": {\"arvados-controller\": {Listen: \":1234\"}}}")
44
45         healthCheck := make(chan bool, 1)
46         ctx, cancel := context.WithCancel(context.Background())
47         defer cancel()
48
49         cmd := Command(arvados.ServiceNameController, func(ctx context.Context, _ *arvados.Cluster, token string, reg *prometheus.Registry) Handler {
50                 c.Check(ctx.Value(contextKey), check.Equals, "bar")
51                 c.Check(token, check.Equals, "abcde")
52                 return &testHandler{ctx: ctx, healthCheck: healthCheck}
53         })
54         cmd.(*command).ctx = context.WithValue(ctx, contextKey, "bar")
55
56         done := make(chan bool)
57         var stdin, stdout, stderr bytes.Buffer
58
59         go func() {
60                 cmd.RunCommand("arvados-controller", []string{"-config", cf.Name()}, &stdin, &stdout, &stderr)
61                 close(done)
62         }()
63         select {
64         case <-healthCheck:
65         case <-done:
66                 c.Error("command exited without health check")
67         }
68         cancel()
69         c.Check(stdout.String(), check.Equals, "")
70         c.Check(stderr.String(), check.Matches, `(?ms).*"msg":"CheckHealth called".*`)
71 }
72
73 func (*Suite) TestTLS(c *check.C) {
74         cwd, err := os.Getwd()
75         c.Assert(err, check.IsNil)
76
77         stdin := bytes.NewBufferString(`
78 Clusters:
79  zzzzz:
80   SystemRootToken: abcde
81   Services:
82    Controller:
83     ExternalURL: "https://localhost:12345"
84     InternalURLs: {"https://localhost:12345": {}}
85   TLS:
86    Key: file://` + cwd + `/../../services/api/tmp/self-signed.key
87    Certificate: file://` + cwd + `/../../services/api/tmp/self-signed.pem
88 `)
89
90         called := make(chan bool)
91         cmd := Command(arvados.ServiceNameController, func(ctx context.Context, _ *arvados.Cluster, token string, reg *prometheus.Registry) Handler {
92                 return &testHandler{handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
93                         w.Write([]byte("ok"))
94                         close(called)
95                 })}
96         })
97
98         exited := make(chan bool)
99         var stdout, stderr bytes.Buffer
100         go func() {
101                 cmd.RunCommand("arvados-controller", []string{"-config", "-"}, stdin, &stdout, &stderr)
102                 close(exited)
103         }()
104         got := make(chan bool)
105         go func() {
106                 defer close(got)
107                 client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
108                 for range time.NewTicker(time.Millisecond).C {
109                         resp, err := client.Get("https://localhost:12345")
110                         if err != nil {
111                                 c.Log(err)
112                                 continue
113                         }
114                         body, err := ioutil.ReadAll(resp.Body)
115                         c.Check(err, check.IsNil)
116                         c.Logf("status %d, body %s", resp.StatusCode, string(body))
117                         c.Check(resp.StatusCode, check.Equals, http.StatusOK)
118                         break
119                 }
120         }()
121         select {
122         case <-called:
123         case <-exited:
124                 c.Error("command exited without calling handler")
125         case <-time.After(time.Second):
126                 c.Error("timed out")
127         }
128         select {
129         case <-got:
130         case <-exited:
131                 c.Error("command exited before client received response")
132         case <-time.After(time.Second):
133                 c.Error("timed out")
134         }
135         c.Log(stderr.String())
136 }
137
138 type testHandler struct {
139         ctx         context.Context
140         handler     http.Handler
141         healthCheck chan bool
142 }
143
144 func (th *testHandler) Done() <-chan struct{}                            { return nil }
145 func (th *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { th.handler.ServeHTTP(w, r) }
146 func (th *testHandler) CheckHealth() error {
147         ctxlog.FromContext(th.ctx).Info("CheckHealth called")
148         select {
149         case th.healthCheck <- true:
150         default:
151         }
152         return nil
153 }