go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/internal/integrationtests/testapp_test.go (about) 1 // Copyright 2020 The LUCI Authors. 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 integrationtests 16 17 import ( 18 "context" 19 "fmt" 20 "io/ioutil" 21 "log" 22 "net/http" 23 "net/url" 24 "os" 25 "path/filepath" 26 "sync" 27 "time" 28 29 "golang.org/x/sync/errgroup" 30 31 "go.chromium.org/luci/common/errors" 32 "go.chromium.org/luci/grpc/prpc" 33 "go.chromium.org/luci/hardcoded/chromeinfra" 34 "go.chromium.org/luci/server" 35 36 "go.chromium.org/luci/resultdb/internal/services/deadlineenforcer" 37 "go.chromium.org/luci/resultdb/internal/services/finalizer" 38 "go.chromium.org/luci/resultdb/internal/services/purger" 39 "go.chromium.org/luci/resultdb/internal/services/recorder" 40 "go.chromium.org/luci/resultdb/internal/services/resultdb" 41 "go.chromium.org/luci/resultdb/internal/services/testmetadataupdator" 42 pb "go.chromium.org/luci/resultdb/proto/v1" 43 ) 44 45 // testApp runs all resultdb servers in one process. 46 type testApp struct { 47 ResultDB pb.ResultDBClient 48 Recorder pb.RecorderClient 49 50 servers []*server.Server 51 shutdownOnce sync.Once 52 53 tempDir string 54 authDBPath string 55 } 56 57 func startTestApp(ctx context.Context) (*testApp, error) { 58 app, err := newTestApp(ctx) 59 if err != nil { 60 return nil, err 61 } 62 if err := app.Start(ctx); err != nil { 63 return nil, err 64 } 65 return app, nil 66 } 67 68 func newTestApp(ctx context.Context) (t *testApp, err error) { 69 tempDir, err := ioutil.TempDir("", "resultdb-integration-test") 70 if err != nil { 71 return nil, err 72 } 73 defer func() { 74 if err != nil { 75 os.RemoveAll(tempDir) 76 } 77 }() 78 79 const authDBTextProto = ` 80 groups: { 81 name: "luci-resultdb-access" 82 members: "anonymous:anonymous" 83 } 84 realms: { 85 api_version: 1 86 permissions: { 87 name: "resultdb.invocations.create" 88 } 89 permissions: { 90 name: "resultdb.invocations.get" 91 } 92 permissions: { 93 name: "resultdb.invocations.include" 94 } 95 realms: { 96 name: "testproject:testrealm" 97 bindings: { 98 permissions: 0 99 permissions: 1 100 permissions: 2 101 principals: "anonymous:anonymous" 102 } 103 } 104 } 105 ` 106 authDBPath := filepath.Join(tempDir, "authdb.txt") 107 if err := os.WriteFile(authDBPath, []byte(authDBTextProto), 0666); err != nil { 108 return nil, err 109 } 110 111 t = &testApp{ 112 tempDir: tempDir, 113 authDBPath: authDBPath, 114 } 115 if err := t.initServers(ctx); err != nil { 116 return nil, err 117 } 118 return t, nil 119 } 120 121 func (t *testApp) Shutdown() { 122 t.shutdownOnce.Do(func() { 123 var wg sync.WaitGroup 124 for _, s := range t.servers { 125 s := s 126 wg.Add(1) 127 go func() { 128 defer wg.Done() 129 s.Shutdown() 130 }() 131 } 132 wg.Wait() 133 134 if err := os.RemoveAll(t.tempDir); err != nil && !os.IsNotExist(err) { 135 log.Printf("failed to remove %q: %s", t.tempDir, err) 136 } 137 }) 138 } 139 140 func (t *testApp) serverClientPair(ctx context.Context, httpPort, adminPort int) (*server.Server, *prpc.Client, error) { 141 srvOpts := server.Options{ 142 AuthDBPath: t.authDBPath, 143 HTTPAddr: fmt.Sprintf("127.0.0.1:%d", httpPort), 144 AdminAddr: fmt.Sprintf("127.0.0.1:%d", adminPort), 145 ClientAuth: chromeinfra.DefaultAuthOptions(), 146 } 147 srv, err := server.New(ctx, srvOpts, nil) 148 if err != nil { 149 return nil, nil, err 150 } 151 152 client := &prpc.Client{ 153 Host: srvOpts.HTTPAddr, 154 Options: &prpc.Options{ 155 Insecure: true, 156 }, 157 } 158 return srv, client, nil 159 } 160 161 func (t *testApp) initServers(ctx context.Context) error { 162 // TODO(nodir): use port 0 to let OS choose an available port. 163 // This is blocked on server.Server exposing the chosen port. 164 165 // Init resultdb server. 166 resultdbServer, resultdbPRPCClient, err := t.serverClientPair(ctx, 8000, 8001) 167 if err != nil { 168 return err 169 } 170 err = resultdb.InitServer(resultdbServer, resultdb.Options{ 171 ArtifactRBEInstance: "projects/luci-resultdb-dev/instances/artifacts", 172 InsecureSelfURLs: true, 173 ContentHostnameMap: map[string]string{"*": "localhost"}, 174 }) 175 if err != nil { 176 return err 177 } 178 179 // Init recorder server. 180 recorderServer, recorderPRPCClient, err := t.serverClientPair(ctx, 8010, 8011) 181 if err != nil { 182 return err 183 } 184 err = recorder.InitServer(recorderServer, recorder.Options{ 185 ArtifactRBEInstance: "projects/luci-resultdb-dev/instances/artifacts", 186 ExpectedResultsExpiration: time.Hour, 187 }) 188 if err != nil { 189 return err 190 } 191 192 // Init finalizer server. 193 finalizerServer, _, err := t.serverClientPair(ctx, 8020, 8021) 194 if err != nil { 195 return err 196 } 197 finalizer.InitServer(finalizerServer) 198 199 // bqexporter is not needed. 200 201 // Init purger server. 202 purgerServer, _, err := t.serverClientPair(ctx, 8030, 8031) 203 if err != nil { 204 return err 205 } 206 purger.InitServer(purgerServer, purger.Options{ 207 ForceCronInterval: 100 * time.Millisecond, 208 }) 209 210 // Init deadlineenforcer server. 211 deadlineEnforcerServer, _, err := t.serverClientPair(ctx, 8040, 8041) 212 if err != nil { 213 return err 214 } 215 deadlineenforcer.InitServer(deadlineEnforcerServer, deadlineenforcer.Options{ 216 ForceCronInterval: 100 * time.Millisecond, 217 }) 218 219 // Init update testmetadataupdator server. 220 testMetadataUpdatorServer, _, err := t.serverClientPair(ctx, 8050, 8051) 221 if err != nil { 222 return err 223 } 224 testmetadataupdator.InitServer(testMetadataUpdatorServer) 225 226 t.ResultDB = pb.NewResultDBPRPCClient(resultdbPRPCClient) 227 t.Recorder = pb.NewRecorderPRPCClient(recorderPRPCClient) 228 t.servers = []*server.Server{resultdbServer, recorderServer, finalizerServer, purgerServer, deadlineEnforcerServer, testMetadataUpdatorServer} 229 return nil 230 } 231 232 func (t *testApp) Serve() error { 233 eg := errgroup.Group{} 234 for _, s := range t.servers { 235 s := s 236 eg.Go(func() error { 237 defer t.Shutdown() 238 return s.Serve() 239 }) 240 } 241 return eg.Wait() 242 } 243 244 // Start starts listening and returns when the server is ready to accept 245 // requests. 246 func (t *testApp) Start(ctx context.Context) error { 247 errC := make(chan error, 1) 248 go func() { 249 errC <- t.Serve() 250 }() 251 252 // Give servers 5s to start. 253 ctx, cancel := context.WithTimeout(ctx, 5*time.Second) 254 defer cancel() 255 256 outer: 257 for { 258 select { 259 case <-ctx.Done(): 260 return ctx.Err() 261 case err := <-errC: 262 if err == nil { 263 err = errors.Reason("failed to start").Err() 264 } 265 return err 266 default: 267 // OK, see if we are serving. 268 } 269 270 for _, s := range t.servers { 271 req := &http.Request{ 272 URL: &url.URL{ 273 Scheme: "http", 274 Host: s.Options.HTTPAddr, 275 Path: "/healthz", 276 }, 277 } 278 req = req.WithContext(ctx) 279 switch res, err := http.DefaultClient.Do(req); { 280 case err != nil: 281 continue outer 282 case res.StatusCode != http.StatusOK: 283 continue outer 284 } 285 } 286 287 // All servers are healthy! 288 return nil 289 } 290 }