diff --git a/client.go b/client.go index ebe11efd6..d0a57199c 100644 --- a/client.go +++ b/client.go @@ -104,10 +104,14 @@ type Client struct { runtime string } -// IsServing returns true if the client can successfully connect to the containerd daemon -// and the healthcheck service returns the SERVING response +// IsServing returns true if the client can successfully connect to the +// containerd daemon and the healthcheck service returns the SERVING +// response. +// This call will block if a transient error is encountered during +// connection. A timeout can be set in the context to ensure it returns +// early. func (c *Client) IsServing(ctx context.Context) (bool, error) { - r, err := c.HealthService().Check(ctx, &grpc_health_v1.HealthCheckRequest{}) + r, err := c.HealthService().Check(ctx, &grpc_health_v1.HealthCheckRequest{}, grpc.FailFast(false)) if err != nil { return false, err } diff --git a/client_test.go b/client_test.go index 23e99875c..4fd72652c 100644 --- a/client_test.go +++ b/client_test.go @@ -11,6 +11,7 @@ import ( "os/exec" "runtime" "testing" + "time" "google.golang.org/grpc/grpclog" @@ -73,18 +74,20 @@ func TestMain(m *testing.M) { } } - client, err := ctrd.waitForStart(ctx) + waitCtx, waitCancel := context.WithTimeout(ctx, 2*time.Second) + client, err := ctrd.waitForStart(waitCtx) + waitCancel() if err != nil { ctrd.Kill() ctrd.Wait() - fmt.Fprintf(os.Stderr, "%s: %s", err, buf.String()) + fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String()) os.Exit(1) } // print out the version in information version, err := client.Version(ctx) if err != nil { - fmt.Fprintf(os.Stderr, "error getting version: %s", err) + fmt.Fprintf(os.Stderr, "error getting version: %s\n", err) os.Exit(1) } @@ -99,7 +102,7 @@ func TestMain(m *testing.M) { if _, err = client.Pull(ctx, testImage, WithPullUnpack); err != nil { ctrd.Stop() ctrd.Wait() - fmt.Fprintf(os.Stderr, "%s: %s", err, buf.String()) + fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String()) os.Exit(1) } } diff --git a/client_windows.go b/client_windows.go deleted file mode 100644 index 7f15adbd6..000000000 --- a/client_windows.go +++ /dev/null @@ -1,16 +0,0 @@ -package containerd - -import ( - "net" - "time" - - winio "github.com/Microsoft/go-winio" -) - -func Dialer(address string, timeout time.Duration) (net.Conn, error) { - return winio.DialPipe(address, &timeout) -} - -func DialAddress(address string) string { - return address -} diff --git a/container_linux_test.go b/container_linux_test.go index 2744f7e99..5d6270707 100644 --- a/container_linux_test.go +++ b/container_linux_test.go @@ -240,14 +240,9 @@ func TestDaemonRestart(t *testing.T) { <-statusC - serving := false - for i := 0; i < 20; i++ { - serving, err = client.IsServing(ctx) - if serving { - break - } - time.Sleep(100 * time.Millisecond) - } + waitCtx, waitCancel := context.WithTimeout(ctx, 2*time.Second) + serving, err := client.IsServing(waitCtx) + waitCancel() if !serving { t.Fatalf("containerd did not start within 2s: %v", err) } diff --git a/daemon_test.go b/daemon_test.go index de57b9fd9..577d4550e 100644 --- a/daemon_test.go +++ b/daemon_test.go @@ -2,7 +2,6 @@ package containerd import ( "context" - "fmt" "io" "os/exec" "sync" @@ -44,19 +43,19 @@ func (d *daemon) waitForStart(ctx context.Context) (*Client, error) { err error ) - for i := 0; i < 20; i++ { - if client == nil { - client, err = New(d.addr) - } - if err == nil { - serving, err = client.IsServing(ctx) - if serving { - return client, nil - } - } - time.Sleep(100 * time.Millisecond) + client, err = New(address) + if err != nil { + return nil, err } - return nil, fmt.Errorf("containerd did not start within 2s: %v", err) + serving, err = client.IsServing(ctx) + if !serving { + client.Close() + if err == nil { + err = errors.New("connection was successful but service is not available") + } + return nil, err + } + return client, err } func (d *daemon) Stop() error { diff --git a/client_unix.go b/dialer.go similarity index 66% rename from client_unix.go rename to dialer.go index e3715571f..0e54ebae1 100644 --- a/client_unix.go +++ b/dialer.go @@ -1,31 +1,13 @@ -// +build !windows - package containerd import ( - "fmt" "net" - "os" "strings" - "syscall" "time" "github.com/pkg/errors" ) -func isNoent(err error) bool { - if err != nil { - if nerr, ok := err.(*net.OpError); ok { - if serr, ok := nerr.Err.(*os.SyscallError); ok { - if serr.Err == syscall.ENOENT { - return true - } - } - } - } - return false -} - type dialResult struct { c net.Conn err error @@ -44,7 +26,7 @@ func Dialer(address string, timeout time.Duration) (net.Conn, error) { case <-stopC: return default: - c, err := net.DialTimeout("unix", address, timeout) + c, err := dialer(address, timeout) if isNoent(err) { <-time.After(10 * time.Millisecond) continue @@ -68,7 +50,3 @@ func Dialer(address string, timeout time.Duration) (net.Conn, error) { return nil, errors.Errorf("dial %s: no such file or directory", address) } } - -func DialAddress(address string) string { - return fmt.Sprintf("unix://%s", address) -} diff --git a/dialer_unix.go b/dialer_unix.go new file mode 100644 index 000000000..3c4689986 --- /dev/null +++ b/dialer_unix.go @@ -0,0 +1,32 @@ +// +build !windows + +package containerd + +import ( + "fmt" + "net" + "os" + "syscall" + "time" +) + +func isNoent(err error) bool { + if err != nil { + if nerr, ok := err.(*net.OpError); ok { + if serr, ok := nerr.Err.(*os.SyscallError); ok { + if serr.Err == syscall.ENOENT { + return true + } + } + } + } + return false +} + +func dialer(address string, timeout time.Duration) (net.Conn, error) { + return net.DialTimeout("unix", address, timeout) +} + +func DialAddress(address string) string { + return fmt.Sprintf("unix://%s", address) +} diff --git a/dialer_windows.go b/dialer_windows.go new file mode 100644 index 000000000..c91a32617 --- /dev/null +++ b/dialer_windows.go @@ -0,0 +1,29 @@ +package containerd + +import ( + "net" + "os" + "syscall" + "time" + + winio "github.com/Microsoft/go-winio" +) + +func isNoent(err error) bool { + if err != nil { + if oerr, ok := err.(*os.PathError); ok { + if oerr.Err == syscall.ENOENT { + return true + } + } + } + return false +} + +func dialer(address string, timeout time.Duration) (net.Conn, error) { + return winio.DialPipe(address, &timeout) +} + +func DialAddress(address string) string { + return address +}