[ARVADOS] created: 1.3.0-2172-g1aa9fd7f9
Git user
git at public.arvados.org
Thu Feb 13 19:23:49 UTC 2020
at 1aa9fd7f9acf3f3a799578fb1e6d34cf137324b3 (commit)
commit 1aa9fd7f9acf3f3a799578fb1e6d34cf137324b3
Author: Tom Clegg <tom at tomclegg.ca>
Date: Thu Feb 13 14:22:54 2020 -0500
16152: Fix nil http handler and ignored config args.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at tomclegg.ca>
diff --git a/services/keep-balance/main.go b/services/keep-balance/main.go
index 6e89df9a5..65bd8d4cf 100644
--- a/services/keep-balance/main.go
+++ b/services/keep-balance/main.go
@@ -9,6 +9,7 @@ import (
"flag"
"fmt"
"io"
+ "net/http"
"os"
"git.arvados.org/arvados.git/lib/config"
@@ -50,10 +51,17 @@ func runCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.W
options.Dumper = dumper
}
- // Only pass along the version flag, which gets handled in RunCommand
+ // Drop our custom args that would be rejected by the generic
+ // service.Command
args = nil
+ dropFlag := map[string]bool{
+ "once": true,
+ "commit-pulls": true,
+ "commit-trash": true,
+ "dump": true,
+ }
flags.Visit(func(f *flag.Flag) {
- if f.Name == "version" {
+ if !dropFlag[f.Name] {
args = append(args, "-"+f.Name, f.Value.String())
}
})
@@ -75,6 +83,7 @@ func runCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.W
}
srv := &Server{
+ Handler: http.NotFoundHandler(),
Cluster: cluster,
ArvClient: ac,
RunOptions: options,
diff --git a/services/keep-balance/main_test.go b/services/keep-balance/main_test.go
new file mode 100644
index 000000000..a6445506e
--- /dev/null
+++ b/services/keep-balance/main_test.go
@@ -0,0 +1,84 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package main
+
+import (
+ "bytes"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "time"
+
+ check "gopkg.in/check.v1"
+)
+
+var _ = check.Suite(&mainSuite{})
+
+type mainSuite struct{}
+
+func (s *mainSuite) TestVersionFlag(c *check.C) {
+ var stdout, stderr bytes.Buffer
+ runCommand("keep-balance", []string{"-version"}, nil, &stdout, &stderr)
+ c.Check(stderr.String(), check.Equals, "")
+ c.Log(stdout.String())
+}
+
+func (s *mainSuite) TestHTTPServer(c *check.C) {
+ ln, err := net.Listen("tcp", ":0")
+ if err != nil {
+ c.Fatal(err)
+ }
+ _, p, err := net.SplitHostPort(ln.Addr().String())
+ ln.Close()
+ config := "Clusters:\n zzzzz:\n ManagementToken: abcdefg\n Services: {Keepbalance: {InternalURLs: {'http://localhost:" + p + "/': {}}}}\n"
+
+ var stdout bytes.Buffer
+ go runCommand("keep-balance", []string{"-config", "-"}, bytes.NewBufferString(config), &stdout, &stdout)
+ done := make(chan struct{})
+ go func() {
+ defer close(done)
+ for {
+ time.Sleep(time.Second / 10)
+ req, err := http.NewRequest(http.MethodGet, "http://:"+p+"/metrics", nil)
+ if err != nil {
+ c.Fatal(err)
+ return
+ }
+ req.Header.Set("Authorization", "Bearer abcdefg")
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ c.Logf("error %s", err)
+ continue
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ c.Logf("http status %d", resp.StatusCode)
+ continue
+ }
+ buf, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ c.Logf("read body: %s", err)
+ continue
+ }
+ c.Check(string(buf), check.Matches, `(?ms).*arvados_keepbalance_sweep_seconds_sum.*`)
+ return
+ }
+ }()
+ select {
+ case <-done:
+ case <-time.After(time.Second):
+ c.Log(stdout.String())
+ c.Fatal("timeout")
+ }
+
+ // Check non-metrics URL that gets passed through to us from
+ // service.Command
+ req, err := http.NewRequest(http.MethodGet, "http://:"+p+"/not-metrics", nil)
+ c.Assert(err, check.IsNil)
+ resp, err := http.DefaultClient.Do(req)
+ c.Check(err, check.IsNil)
+ defer resp.Body.Close()
+ c.Check(resp.StatusCode, check.Equals, http.StatusNotFound)
+}
commit dbbb314b189cbb8c1f308db6a951af5d25ed8749
Author: Tom Clegg <tom at tomclegg.ca>
Date: Thu Feb 13 12:05:22 2020 -0500
15954: Change components to tasks.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at tomclegg.ca>
diff --git a/lib/boot/cert.go b/lib/boot/cert.go
index 011f418e9..560579b77 100644
--- a/lib/boot/cert.go
+++ b/lib/boot/cert.go
@@ -10,7 +10,13 @@ import (
"path/filepath"
)
-func createCertificates(ctx context.Context, boot *Booter, ready chan<- bool) error {
+type createCertificates struct{}
+
+func (createCertificates) String() string {
+ return "certificates"
+}
+
+func (createCertificates) Run(ctx context.Context, fail func(error), boot *Booter) error {
// Generate root key
err := boot.RunProgram(ctx, boot.tempdir, nil, nil, "openssl", "genrsa", "-out", "rootCA.key", "4096")
if err != nil {
@@ -48,8 +54,5 @@ subjectAltName=DNS:localhost,DNS:localhost.localdomain
if err != nil {
return err
}
-
- close(ready)
- <-ctx.Done()
return nil
}
diff --git a/lib/boot/cmd.go b/lib/boot/cmd.go
index 93f6ee0a8..05ccb1867 100644
--- a/lib/boot/cmd.go
+++ b/lib/boot/cmd.go
@@ -111,6 +111,7 @@ type Booter struct {
cancel context.CancelFunc
done chan struct{}
healthChecker *health.Aggregator
+ tasksReady map[bootTask]chan bool
tempdir string
configfile string
@@ -215,47 +216,65 @@ func (boot *Booter) run(loader *config.Loader) error {
}
var wg sync.WaitGroup
- components := map[string]*component{
- "certificates": &component{runFunc: createCertificates},
- "database": &component{runFunc: runPostgres, depends: []string{"certificates"}},
- "nginx": &component{runFunc: runNginx},
- "controller": &component{cmdHandler: controller.Command, depends: []string{"database"}},
- "dispatchcloud": &component{cmdHandler: dispatchcloud.Command, notIfTest: true},
- "git-httpd": &component{goProg: "services/arv-git-httpd"},
- "health": &component{goProg: "services/health"},
- "keep-balance": &component{goProg: "services/keep-balance", notIfTest: true},
- "keepproxy": &component{goProg: "services/keepproxy"},
- "keepstore": &component{goProg: "services/keepstore", svc: boot.cluster.Services.Keepstore},
- "keep-web": &component{goProg: "services/keep-web"},
- "railsAPI": &component{svc: boot.cluster.Services.RailsAPI, railsApp: "services/api", depends: []string{"database"}},
- "workbench1": &component{svc: boot.cluster.Services.Workbench1, railsApp: "apps/workbench"},
- "ws": &component{goProg: "services/ws", depends: []string{"database"}},
- }
- for _, cmpt := range components {
- cmpt.ready = make(chan bool)
- }
- for name, cmpt := range components {
- name, cmpt := name, cmpt
- wg.Add(1)
- go func() {
- defer wg.Done()
- defer boot.cancel()
- for _, dep := range cmpt.depends {
- boot.logger.WithField("component", name).WithField("dependency", dep).Info("waiting")
- select {
- case <-components[dep].ready:
- case <-boot.ctx.Done():
- return
- }
+ tasks := []bootTask{
+ createCertificates{},
+ runPostgreSQL{},
+ runNginx{},
+ runServiceCommand{name: "controller", command: controller.Command, depends: []bootTask{runPostgreSQL{}}},
+ runGoProgram{src: "services/arv-git-httpd"},
+ runGoProgram{src: "services/health"},
+ runGoProgram{src: "services/keepproxy"},
+ runGoProgram{src: "services/keepstore", svc: boot.cluster.Services.Keepstore},
+ runGoProgram{src: "services/keep-web"},
+ runGoProgram{src: "services/ws", depends: []bootTask{runPostgreSQL{}}},
+ runPassenger{src: "services/api", svc: boot.cluster.Services.RailsAPI, depends: []bootTask{runPostgreSQL{}}},
+ runPassenger{src: "apps/workbench", svc: boot.cluster.Services.Workbench1},
+ }
+ if boot.ClusterType != "test" {
+ tasks = append(tasks,
+ runServiceCommand{name: "dispatchcloud", command: dispatchcloud.Command},
+ runGoProgram{src: "services/keep-balance"},
+ )
+ }
+ boot.tasksReady = map[bootTask]chan bool{}
+ for _, task := range tasks {
+ boot.tasksReady[task] = make(chan bool)
+ }
+ for _, task := range tasks {
+ task := task
+ fail := func(err error) {
+ if boot.ctx.Err() != nil {
+ return
}
- boot.logger.WithField("component", name).Info("starting")
- err := cmpt.Run(boot.ctx, name, boot)
- if err != nil && err != context.Canceled {
- boot.logger.WithError(err).WithField("component", name).Error("exited")
+ boot.cancel()
+ boot.logger.WithField("task", task).WithError(err).Error("task failed")
+ }
+ go func() {
+ boot.logger.WithField("task", task).Info("starting")
+ err := task.Run(boot.ctx, fail, boot)
+ if err != nil {
+ fail(err)
+ return
}
+ close(boot.tasksReady[task])
}()
}
- wg.Wait()
+ return boot.wait(boot.ctx, tasks...)
+}
+
+func (boot *Booter) wait(ctx context.Context, tasks ...bootTask) error {
+ for _, task := range tasks {
+ ch, ok := boot.tasksReady[task]
+ if !ok {
+ return fmt.Errorf("no such task: %s", task)
+ }
+ boot.logger.WithField("task", task).Info("waiting")
+ select {
+ case <-ch:
+ case <-ctx.Done():
+ return ctx.Err()
+ }
+ }
return nil
}
@@ -395,114 +414,14 @@ func (boot *Booter) RunProgram(ctx context.Context, dir string, output io.Writer
return nil
}
-type component struct {
- name string
- svc arvados.Service
- cmdHandler cmd.Handler
- runFunc func(ctx context.Context, boot *Booter, ready chan<- bool) error
- railsApp string // source dir in arvados tree, e.g., "services/api"
- goProg string // source dir in arvados tree, e.g., "services/keepstore"
- notIfTest bool // don't run this component on a test cluster
- depends []string // don't start until all of these components are ready
-
- ready chan bool
-}
-
-func (cmpt *component) Run(ctx context.Context, name string, boot *Booter) error {
- if cmpt.notIfTest && boot.ClusterType == "test" {
- fmt.Fprintf(boot.Stderr, "skipping component %q in %s mode\n", name, boot.ClusterType)
- <-ctx.Done()
- return nil
- }
- fmt.Fprintf(boot.Stderr, "starting component %q\n", name)
- if cmpt.cmdHandler != nil {
- errs := make(chan error, 1)
- go func() {
- defer close(errs)
- exitcode := cmpt.cmdHandler.RunCommand(name, []string{"-config", boot.configfile}, bytes.NewBuffer(nil), boot.Stderr, boot.Stderr)
- if exitcode != 0 {
- errs <- fmt.Errorf("exit code %d", exitcode)
- }
- }()
- select {
- case err := <-errs:
- return err
- case <-ctx.Done():
- // cmpt.cmdHandler.RunCommand() doesn't have
- // access to our context, so it won't shut
- // down by itself. We just abandon it.
- return nil
- }
- }
- if cmpt.goProg != "" {
- boot.RunProgram(ctx, cmpt.goProg, nil, nil, "go", "install")
- if ctx.Err() != nil {
- return nil
- }
- _, basename := filepath.Split(cmpt.goProg)
- if len(cmpt.svc.InternalURLs) > 0 {
- // Run one for each URL
- var wg sync.WaitGroup
- for u := range cmpt.svc.InternalURLs {
- u := u
- wg.Add(1)
- go func() {
- defer wg.Done()
- boot.RunProgram(ctx, boot.tempdir, nil, []string{"ARVADOS_SERVICE_INTERNAL_URL=" + u.String()}, basename)
- }()
- }
- wg.Wait()
- } else {
- // Just run one
- boot.RunProgram(ctx, boot.tempdir, nil, nil, basename)
- }
- return nil
- }
- if cmpt.runFunc != nil {
- return cmpt.runFunc(ctx, boot, cmpt.ready)
- }
- if cmpt.railsApp != "" {
- port, err := internalPort(cmpt.svc)
- if err != nil {
- return fmt.Errorf("bug: no InternalURLs for component %q: %v", name, cmpt.svc.InternalURLs)
- }
- var buf bytes.Buffer
- err = boot.RunProgram(ctx, cmpt.railsApp, &buf, nil, "gem", "list", "--details", "bundler")
- if err != nil {
- return err
- }
- for _, version := range []string{"1.11.0", "1.17.3", "2.0.2"} {
- if !strings.Contains(buf.String(), "("+version+")") {
- err = boot.RunProgram(ctx, cmpt.railsApp, nil, nil, "gem", "install", "--user", "bundler:1.11", "bundler:1.17.3", "bundler:2.0.2")
- if err != nil {
- return err
- }
- break
- }
- }
- err = boot.RunProgram(ctx, cmpt.railsApp, nil, nil, "bundle", "install", "--jobs", "4", "--path", filepath.Join(os.Getenv("HOME"), ".gem"))
- if err != nil {
- return err
- }
- err = boot.RunProgram(ctx, cmpt.railsApp, nil, nil, "bundle", "exec", "passenger-config", "build-native-support")
- if err != nil {
- return err
- }
- err = boot.RunProgram(ctx, cmpt.railsApp, nil, nil, "bundle", "exec", "passenger-config", "install-standalone-runtime")
- if err != nil {
- return err
- }
- err = boot.RunProgram(ctx, cmpt.railsApp, nil, nil, "bundle", "exec", "passenger-config", "validate-install")
- if err != nil {
- return err
- }
- err = boot.RunProgram(ctx, cmpt.railsApp, nil, nil, "bundle", "exec", "passenger", "start", "-p", port)
- if err != nil {
- return err
- }
- return nil
- }
- return fmt.Errorf("bug: component %q has nothing to run", name)
+type bootTask interface {
+ // Execute the task. Run should return nil when the task is
+ // done enough to satisfy a dependency relationship (e.g., the
+ // service is running and ready). If the task starts a
+ // goroutine that fails after Run returns (e.g., the service
+ // shuts down), it should call cancel.
+ Run(ctx context.Context, fail func(error), boot *Booter) error
+ String() string
}
func (boot *Booter) autofillConfig(cfg *arvados.Config, log logrus.FieldLogger) error {
@@ -662,7 +581,7 @@ func availablePort() (int, error) {
// Try to connect to addr until it works, then close ch. Give up if
// ctx cancels.
-func connectAndClose(ctx context.Context, addr string, ch chan<- bool) {
+func waitForConnect(ctx context.Context, addr string) error {
dialer := net.Dialer{Timeout: time.Second}
for ctx.Err() == nil {
conn, err := dialer.DialContext(ctx, "tcp", addr)
@@ -671,7 +590,7 @@ func connectAndClose(ctx context.Context, addr string, ch chan<- bool) {
continue
}
conn.Close()
- close(ch)
- return
+ return nil
}
+ return ctx.Err()
}
diff --git a/lib/boot/nginx.go b/lib/boot/nginx.go
index 2df5e90b3..b5b712af6 100644
--- a/lib/boot/nginx.go
+++ b/lib/boot/nginx.go
@@ -16,7 +16,13 @@ import (
"git.arvados.org/arvados.git/sdk/go/arvados"
)
-func runNginx(ctx context.Context, boot *Booter, ready chan<- bool) error {
+type runNginx struct{}
+
+func (runNginx) String() string {
+ return "nginx"
+}
+
+func (runNginx) Run(ctx context.Context, fail func(error), boot *Booter) error {
vars := map[string]string{
"SSLCERT": filepath.Join(boot.SourcePath, "services", "api", "tmp", "self-signed.pem"), // TODO: root ca
"SSLKEY": filepath.Join(boot.SourcePath, "services", "api", "tmp", "self-signed.key"), // TODO: root ca
@@ -69,9 +75,11 @@ func runNginx(ctx context.Context, boot *Booter, ready chan<- bool) error {
}
}
}
- go connectAndClose(ctx, boot.cluster.Services.Controller.ExternalURL.Host, ready)
- return boot.RunProgram(ctx, ".", nil, nil, nginx,
- "-g", "error_log stderr info;",
- "-g", "pid "+filepath.Join(boot.tempdir, "nginx.pid")+";",
- "-c", conffile)
+ go func() {
+ fail(boot.RunProgram(ctx, ".", nil, nil, nginx,
+ "-g", "error_log stderr info;",
+ "-g", "pid "+filepath.Join(boot.tempdir, "nginx.pid")+";",
+ "-c", conffile))
+ }()
+ return waitForConnect(ctx, boot.cluster.Services.Controller.ExternalURL.Host)
}
diff --git a/lib/boot/postgresql.go b/lib/boot/postgresql.go
index 86328e110..96ba07cef 100644
--- a/lib/boot/postgresql.go
+++ b/lib/boot/postgresql.go
@@ -18,9 +18,20 @@ import (
"github.com/lib/pq"
)
-func runPostgres(ctx context.Context, boot *Booter, ready chan<- bool) error {
+type runPostgreSQL struct{}
+
+func (runPostgreSQL) String() string {
+ return "postgresql"
+}
+
+func (runPostgreSQL) Run(ctx context.Context, fail func(error), boot *Booter) error {
+ err := boot.wait(ctx, createCertificates{})
+ if err != nil {
+ return err
+ }
+
buf := bytes.NewBuffer(nil)
- err := boot.RunProgram(ctx, boot.tempdir, buf, nil, "pg_config", "--bindir")
+ err = boot.RunProgram(ctx, boot.tempdir, buf, nil, "pg_config", "--bindir")
if err != nil {
return err
}
commit 7abc7ca38954acd4eaa53c9280504e06a76b8d71
Author: Tom Clegg <tom at tomclegg.ca>
Date: Wed Feb 12 09:12:02 2020 -0500
15954: Start own postgresql server.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at tomclegg.ca>
diff --git a/doc/examples/config/zzzzz.yml b/doc/examples/config/zzzzz.yml
index 9e3d718ed..c63550edf 100644
--- a/doc/examples/config/zzzzz.yml
+++ b/doc/examples/config/zzzzz.yml
@@ -1,12 +1,5 @@
Clusters:
zzzzz:
- PostgreSQL:
- Connection:
- client_encoding: utf8
- host: localhost
- dbname: arvados_test
- user: arvados
- password: insecure_arvados_test
ManagementToken: e687950a23c3a9bceec28c6223a06c79
SystemRootToken: systemusertesttoken1234567890aoeuidhtnsqjkxbmwvzpy
API:
diff --git a/go.mod b/go.mod
index 2e16e5a0f..85e5552f6 100644
--- a/go.mod
+++ b/go.mod
@@ -31,7 +31,7 @@ require (
github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff
github.com/julienschmidt/httprouter v1.2.0
github.com/kevinburke/ssh_config v0.0.0-20171013211458-802051befeb5 // indirect
- github.com/lib/pq v0.0.0-20171126050459-83612a56d3dd
+ github.com/lib/pq v1.3.0
github.com/marstr/guid v1.1.1-0.20170427235115-8bdf7d1a087c // indirect
github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747 // indirect
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
diff --git a/go.sum b/go.sum
index d7a022dda..6c2323a31 100644
--- a/go.sum
+++ b/go.sum
@@ -111,6 +111,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/lib/pq v0.0.0-20171126050459-83612a56d3dd h1:2RDaVc4/izhWyAvYxNm8c9saSyCDIxefNwOcqaH7pcU=
github.com/lib/pq v0.0.0-20171126050459-83612a56d3dd/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
+github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/marstr/guid v1.1.1-0.20170427235115-8bdf7d1a087c h1:ouxemItv3B/Zh008HJkEXDYCN3BIRyNHxtUN7ThJ5Js=
github.com/marstr/guid v1.1.1-0.20170427235115-8bdf7d1a087c/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
diff --git a/lib/boot/cert.go b/lib/boot/cert.go
new file mode 100644
index 000000000..011f418e9
--- /dev/null
+++ b/lib/boot/cert.go
@@ -0,0 +1,55 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package boot
+
+import (
+ "context"
+ "io/ioutil"
+ "path/filepath"
+)
+
+func createCertificates(ctx context.Context, boot *Booter, ready chan<- bool) error {
+ // Generate root key
+ err := boot.RunProgram(ctx, boot.tempdir, nil, nil, "openssl", "genrsa", "-out", "rootCA.key", "4096")
+ if err != nil {
+ return err
+ }
+ // Generate a self-signed root certificate
+ err = boot.RunProgram(ctx, boot.tempdir, nil, nil, "openssl", "req", "-x509", "-new", "-nodes", "-key", "rootCA.key", "-sha256", "-days", "3650", "-out", "rootCA.crt", "-subj", "/C=US/ST=MA/O=Example Org/CN=localhost")
+ if err != nil {
+ return err
+ }
+ // Generate server key
+ err = boot.RunProgram(ctx, boot.tempdir, nil, nil, "openssl", "genrsa", "-out", "server.key", "2048")
+ if err != nil {
+ return err
+ }
+ // Build config file for signing request
+ defaultconf, err := ioutil.ReadFile("/etc/ssl/openssl.cnf")
+ if err != nil {
+ return err
+ }
+ err = ioutil.WriteFile(filepath.Join(boot.tempdir, "server.cfg"), append(defaultconf, []byte(`
+[SAN]
+subjectAltName=DNS:localhost,DNS:localhost.localdomain
+`)...), 0777)
+ if err != nil {
+ return err
+ }
+ // Generate signing request
+ err = boot.RunProgram(ctx, boot.tempdir, nil, nil, "openssl", "req", "-new", "-sha256", "-key", "server.key", "-subj", "/C=US/ST=MA/O=Example Org/CN=localhost", "-reqexts", "SAN", "-config", "server.cfg", "-out", "server.csr")
+ if err != nil {
+ return err
+ }
+ // Sign certificate
+ err = boot.RunProgram(ctx, boot.tempdir, nil, nil, "openssl", "x509", "-req", "-in", "server.csr", "-CA", "rootCA.crt", "-CAkey", "rootCA.key", "-CAcreateserial", "-out", "server.crt", "-days", "3650", "-sha256")
+ if err != nil {
+ return err
+ }
+
+ close(ready)
+ <-ctx.Done()
+ return nil
+}
diff --git a/lib/boot/cmd.go b/lib/boot/cmd.go
index 4d2c01f2c..93f6ee0a8 100644
--- a/lib/boot/cmd.go
+++ b/lib/boot/cmd.go
@@ -18,6 +18,7 @@ import (
"os/exec"
"os/signal"
"path/filepath"
+ "strconv"
"strings"
"sync"
"syscall"
@@ -71,6 +72,7 @@ func (bootCommand) RunCommand(prog string, args []string, stdin io.Reader, stdou
flags.StringVar(&boot.SourcePath, "source", ".", "arvados source tree `directory`")
flags.StringVar(&boot.LibPath, "lib", "/var/lib/arvados", "`directory` to install dependencies and library files")
flags.StringVar(&boot.ClusterType, "type", "production", "cluster `type`: development, test, or production")
+ flags.BoolVar(&boot.OwnTemporaryDatabase, "own-temporary-database", false, "bring up a postgres server and create a temporary database")
err = flags.Parse(args)
if err == flag.ErrHelp {
err = nil
@@ -96,10 +98,11 @@ func (bootCommand) RunCommand(prog string, args []string, stdin io.Reader, stdou
}
type Booter struct {
- SourcePath string // e.g., /home/username/src/arvados
- LibPath string // e.g., /var/lib/arvados
- ClusterType string // e.g., production
- Stderr io.Writer
+ SourcePath string // e.g., /home/username/src/arvados
+ LibPath string // e.g., /var/lib/arvados
+ ClusterType string // e.g., production
+ OwnTemporaryDatabase bool
+ Stderr io.Writer
logger logrus.FieldLogger
cluster *arvados.Cluster
@@ -212,29 +215,43 @@ func (boot *Booter) run(loader *config.Loader) error {
}
var wg sync.WaitGroup
- for _, cmpt := range []component{
- {name: "nginx", runFunc: runNginx},
- {name: "controller", cmdHandler: controller.Command},
- {name: "dispatchcloud", cmdHandler: dispatchcloud.Command, notIfTest: true},
- {name: "git-httpd", goProg: "services/arv-git-httpd"},
- {name: "health", goProg: "services/health"},
- {name: "keep-balance", goProg: "services/keep-balance", notIfTest: true},
- {name: "keepproxy", goProg: "services/keepproxy"},
- {name: "keepstore", goProg: "services/keepstore", svc: boot.cluster.Services.Keepstore},
- {name: "keep-web", goProg: "services/keep-web"},
- {name: "railsAPI", svc: boot.cluster.Services.RailsAPI, railsApp: "services/api"},
- {name: "workbench1", svc: boot.cluster.Services.Workbench1, railsApp: "apps/workbench"},
- {name: "ws", goProg: "services/ws"},
- } {
- cmpt := cmpt
+ components := map[string]*component{
+ "certificates": &component{runFunc: createCertificates},
+ "database": &component{runFunc: runPostgres, depends: []string{"certificates"}},
+ "nginx": &component{runFunc: runNginx},
+ "controller": &component{cmdHandler: controller.Command, depends: []string{"database"}},
+ "dispatchcloud": &component{cmdHandler: dispatchcloud.Command, notIfTest: true},
+ "git-httpd": &component{goProg: "services/arv-git-httpd"},
+ "health": &component{goProg: "services/health"},
+ "keep-balance": &component{goProg: "services/keep-balance", notIfTest: true},
+ "keepproxy": &component{goProg: "services/keepproxy"},
+ "keepstore": &component{goProg: "services/keepstore", svc: boot.cluster.Services.Keepstore},
+ "keep-web": &component{goProg: "services/keep-web"},
+ "railsAPI": &component{svc: boot.cluster.Services.RailsAPI, railsApp: "services/api", depends: []string{"database"}},
+ "workbench1": &component{svc: boot.cluster.Services.Workbench1, railsApp: "apps/workbench"},
+ "ws": &component{goProg: "services/ws", depends: []string{"database"}},
+ }
+ for _, cmpt := range components {
+ cmpt.ready = make(chan bool)
+ }
+ for name, cmpt := range components {
+ name, cmpt := name, cmpt
wg.Add(1)
go func() {
defer wg.Done()
defer boot.cancel()
- boot.logger.WithField("component", cmpt.name).Info("starting")
- err := cmpt.Run(boot.ctx, boot)
+ for _, dep := range cmpt.depends {
+ boot.logger.WithField("component", name).WithField("dependency", dep).Info("waiting")
+ select {
+ case <-components[dep].ready:
+ case <-boot.ctx.Done():
+ return
+ }
+ }
+ boot.logger.WithField("component", name).Info("starting")
+ err := cmpt.Run(boot.ctx, name, boot)
if err != nil && err != context.Canceled {
- boot.logger.WithError(err).WithField("component", cmpt.name).Error("exited")
+ boot.logger.WithError(err).WithField("component", name).Error("exited")
}
}()
}
@@ -382,24 +399,27 @@ type component struct {
name string
svc arvados.Service
cmdHandler cmd.Handler
- runFunc func(ctx context.Context, boot *Booter) error
- railsApp string // source dir in arvados tree, e.g., "services/api"
- goProg string // source dir in arvados tree, e.g., "services/keepstore"
- notIfTest bool // don't run this component on a test cluster
+ runFunc func(ctx context.Context, boot *Booter, ready chan<- bool) error
+ railsApp string // source dir in arvados tree, e.g., "services/api"
+ goProg string // source dir in arvados tree, e.g., "services/keepstore"
+ notIfTest bool // don't run this component on a test cluster
+ depends []string // don't start until all of these components are ready
+
+ ready chan bool
}
-func (cmpt *component) Run(ctx context.Context, boot *Booter) error {
+func (cmpt *component) Run(ctx context.Context, name string, boot *Booter) error {
if cmpt.notIfTest && boot.ClusterType == "test" {
- fmt.Fprintf(boot.Stderr, "skipping component %q in %s mode\n", cmpt.name, boot.ClusterType)
+ fmt.Fprintf(boot.Stderr, "skipping component %q in %s mode\n", name, boot.ClusterType)
<-ctx.Done()
return nil
}
- fmt.Fprintf(boot.Stderr, "starting component %q\n", cmpt.name)
+ fmt.Fprintf(boot.Stderr, "starting component %q\n", name)
if cmpt.cmdHandler != nil {
errs := make(chan error, 1)
go func() {
defer close(errs)
- exitcode := cmpt.cmdHandler.RunCommand(cmpt.name, []string{"-config", boot.configfile}, bytes.NewBuffer(nil), boot.Stderr, boot.Stderr)
+ exitcode := cmpt.cmdHandler.RunCommand(name, []string{"-config", boot.configfile}, bytes.NewBuffer(nil), boot.Stderr, boot.Stderr)
if exitcode != 0 {
errs <- fmt.Errorf("exit code %d", exitcode)
}
@@ -439,12 +459,12 @@ func (cmpt *component) Run(ctx context.Context, boot *Booter) error {
return nil
}
if cmpt.runFunc != nil {
- return cmpt.runFunc(ctx, boot)
+ return cmpt.runFunc(ctx, boot, cmpt.ready)
}
if cmpt.railsApp != "" {
port, err := internalPort(cmpt.svc)
if err != nil {
- return fmt.Errorf("bug: no InternalURLs for component %q: %v", cmpt.name, cmpt.svc.InternalURLs)
+ return fmt.Errorf("bug: no InternalURLs for component %q: %v", name, cmpt.svc.InternalURLs)
}
var buf bytes.Buffer
err = boot.RunProgram(ctx, cmpt.railsApp, &buf, nil, "gem", "list", "--details", "bundler")
@@ -482,7 +502,7 @@ func (cmpt *component) Run(ctx context.Context, boot *Booter) error {
}
return nil
}
- return fmt.Errorf("bug: component %q has nothing to run", cmpt.name)
+ return fmt.Errorf("bug: component %q has nothing to run", name)
}
func (boot *Booter) autofillConfig(cfg *arvados.Config, log logrus.FieldLogger) error {
@@ -572,6 +592,21 @@ func (boot *Booter) autofillConfig(cfg *arvados.Config, log logrus.FieldLogger)
}
}
}
+ if boot.OwnTemporaryDatabase {
+ p, err := availablePort()
+ if err != nil {
+ return err
+ }
+ cluster.PostgreSQL.Connection = arvados.PostgreSQLConnection{
+ "client_encoding": "utf8",
+ "host": "localhost",
+ "port": strconv.Itoa(p),
+ "dbname": "arvados_test",
+ "user": "arvados",
+ "password": "insecure_arvados_test",
+ }
+ }
+
cfg.Clusters[cluster.ClusterID] = *cluster
return nil
}
@@ -611,3 +646,32 @@ func externalPort(svc arvados.Service) (string, error) {
return "80", nil
}
}
+
+func availablePort() (int, error) {
+ ln, err := net.Listen("tcp", ":0")
+ if err != nil {
+ return 0, err
+ }
+ defer ln.Close()
+ _, p, err := net.SplitHostPort(ln.Addr().String())
+ if err != nil {
+ return 0, err
+ }
+ return strconv.Atoi(p)
+}
+
+// Try to connect to addr until it works, then close ch. Give up if
+// ctx cancels.
+func connectAndClose(ctx context.Context, addr string, ch chan<- bool) {
+ dialer := net.Dialer{Timeout: time.Second}
+ for ctx.Err() == nil {
+ conn, err := dialer.DialContext(ctx, "tcp", addr)
+ if err != nil {
+ time.Sleep(time.Second / 10)
+ continue
+ }
+ conn.Close()
+ close(ch)
+ return
+ }
+}
diff --git a/lib/boot/nginx.go b/lib/boot/nginx.go
index 1b361dd9c..2df5e90b3 100644
--- a/lib/boot/nginx.go
+++ b/lib/boot/nginx.go
@@ -16,7 +16,7 @@ import (
"git.arvados.org/arvados.git/sdk/go/arvados"
)
-func runNginx(ctx context.Context, boot *Booter) error {
+func runNginx(ctx context.Context, boot *Booter, ready chan<- bool) error {
vars := map[string]string{
"SSLCERT": filepath.Join(boot.SourcePath, "services", "api", "tmp", "self-signed.pem"), // TODO: root ca
"SSLKEY": filepath.Join(boot.SourcePath, "services", "api", "tmp", "self-signed.key"), // TODO: root ca
@@ -69,6 +69,7 @@ func runNginx(ctx context.Context, boot *Booter) error {
}
}
}
+ go connectAndClose(ctx, boot.cluster.Services.Controller.ExternalURL.Host, ready)
return boot.RunProgram(ctx, ".", nil, nil, nginx,
"-g", "error_log stderr info;",
"-g", "pid "+filepath.Join(boot.tempdir, "nginx.pid")+";",
diff --git a/lib/boot/postgresql.go b/lib/boot/postgresql.go
new file mode 100644
index 000000000..86328e110
--- /dev/null
+++ b/lib/boot/postgresql.go
@@ -0,0 +1,100 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package boot
+
+import (
+ "bytes"
+ "context"
+ "database/sql"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "git.arvados.org/arvados.git/sdk/go/arvados"
+ "github.com/lib/pq"
+)
+
+func runPostgres(ctx context.Context, boot *Booter, ready chan<- bool) error {
+ buf := bytes.NewBuffer(nil)
+ err := boot.RunProgram(ctx, boot.tempdir, buf, nil, "pg_config", "--bindir")
+ if err != nil {
+ return err
+ }
+ datadir := filepath.Join(boot.tempdir, "pgdata")
+
+ err = os.Mkdir(datadir, 0755)
+ if err != nil {
+ return err
+ }
+ bindir := strings.TrimSpace(buf.String())
+
+ err = boot.RunProgram(ctx, boot.tempdir, nil, nil, filepath.Join(bindir, "initdb"), "-D", datadir)
+ if err != nil {
+ return err
+ }
+
+ err = boot.RunProgram(ctx, boot.tempdir, nil, nil, "cp", "server.crt", "server.key", datadir)
+ if err != nil {
+ return err
+ }
+
+ port := boot.cluster.PostgreSQL.Connection["port"]
+
+ ctx, cancel := context.WithCancel(ctx)
+ defer cancel()
+
+ go func() {
+ for {
+ if ctx.Err() != nil {
+ return
+ }
+ if exec.CommandContext(ctx, "pg_isready", "--timeout=10", "--host="+boot.cluster.PostgreSQL.Connection["host"], "--port="+port).Run() == nil {
+ break
+ }
+ time.Sleep(time.Second / 2)
+ }
+ db, err := sql.Open("postgres", arvados.PostgreSQLConnection{
+ "host": datadir,
+ "port": port,
+ "dbname": "postgres",
+ }.String())
+ if err != nil {
+ boot.logger.WithError(err).Error("db open failed")
+ cancel()
+ return
+ }
+ defer db.Close()
+ conn, err := db.Conn(ctx)
+ if err != nil {
+ boot.logger.WithError(err).Error("db conn failed")
+ cancel()
+ return
+ }
+ defer conn.Close()
+ _, err = conn.ExecContext(ctx, `CREATE USER `+pq.QuoteIdentifier(boot.cluster.PostgreSQL.Connection["user"])+` WITH SUPERUSER ENCRYPTED PASSWORD `+pq.QuoteLiteral(boot.cluster.PostgreSQL.Connection["password"]))
+ if err != nil {
+ boot.logger.WithError(err).Error("createuser failed")
+ cancel()
+ return
+ }
+ _, err = conn.ExecContext(ctx, `CREATE DATABASE `+pq.QuoteIdentifier(boot.cluster.PostgreSQL.Connection["dbname"]))
+ if err != nil {
+ boot.logger.WithError(err).Error("createdb failed")
+ cancel()
+ return
+ }
+ close(ready)
+ return
+ }()
+
+ return boot.RunProgram(ctx, boot.tempdir, nil, nil, filepath.Join(bindir, "postgres"),
+ "-l", // enable ssl
+ "-D", datadir, // data dir
+ "-k", datadir, // socket dir
+ "-p", boot.cluster.PostgreSQL.Connection["port"],
+ )
+}
commit b9fd7e3f374248a61159e4750a84e38d1c48d5dd
Merge: 0446c0a3a b01c43723
Author: Tom Clegg <tom at tomclegg.ca>
Date: Tue Feb 11 11:39:44 2020 -0500
15954: Merge branch 'master'
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at tomclegg.ca>
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list