Skip to content

feat: add coder ping #6161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions cli/ping.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package cli

import (
"context"
"fmt"
"time"

"github.com/spf13/cobra"
"golang.org/x/xerrors"

"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"

"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/codersdk"
)

func ping() *cobra.Command {
var (
pingNum int
pingTimeout time.Duration
pingWait time.Duration
verbose bool
)
cmd := &cobra.Command{
Annotations: workspaceCommand,
Use: "ping <workspace>",
Short: "Ping a workspace",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithCancel(cmd.Context())
defer cancel()

client, err := CreateClient(cmd)
if err != nil {
return err
}

workspaceName := args[0]
_, workspaceAgent, err := getWorkspaceAndAgent(ctx, cmd, client, codersdk.Me, workspaceName, false)
if err != nil {
return err
}

var logger slog.Logger
if verbose {
logger = slog.Make(sloghuman.Sink(cmd.OutOrStdout())).Leveled(slog.LevelDebug)
}

conn, err := client.DialWorkspaceAgent(ctx, workspaceAgent.ID, &codersdk.DialWorkspaceAgentOptions{Logger: logger})
if err != nil {
return err
}
defer conn.Close()

derpMap := conn.DERPMap()
_ = derpMap

n := 0
didP2p := false
start := time.Now()
for {
if n > 0 {
time.Sleep(time.Second)
}
n++

ctx, cancel := context.WithTimeout(ctx, pingTimeout)
dur, p2p, pong, err := conn.Ping(ctx)
cancel()
if err != nil {
if xerrors.Is(err, context.DeadlineExceeded) {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "ping to %q timed out \n", workspaceName)
if n == pingNum {
return nil
}
continue
}
if xerrors.Is(err, context.Canceled) {
return nil
}

if err.Error() == "no matching peer" {
continue
}

_, _ = fmt.Fprintf(cmd.OutOrStdout(), "ping to %q failed %s\n", workspaceName, err.Error())
if n == pingNum {
return nil
}
continue
}

dur = dur.Round(time.Millisecond)
var via string
if p2p {
if !didP2p {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "p2p connection established in",
cliui.Styles.DateTimeStamp.Render(time.Since(start).Round(time.Millisecond).String()),
)
}
didP2p = true

via = fmt.Sprintf("%s via %s",
cliui.Styles.Fuchsia.Render("p2p"),
cliui.Styles.Code.Render(pong.Endpoint),
)
} else {
derpName := "unknown"
derpRegion, ok := derpMap.Regions[pong.DERPRegionID]
if ok {
derpName = derpRegion.RegionName
}
via = fmt.Sprintf("%s via %s",
cliui.Styles.Fuchsia.Render("proxied"),
cliui.Styles.Code.Render(fmt.Sprintf("DERP(%s)", derpName)),
)
}

_, _ = fmt.Fprintf(cmd.OutOrStdout(), "pong from %s %s in %s\n",
cliui.Styles.Keyword.Render(workspaceName),
via,
cliui.Styles.DateTimeStamp.Render(dur.String()),
)

if n == pingNum {
return nil
}
}
},
}

cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Enables verbose logging.")
cmd.Flags().DurationVarP(&pingWait, "wait", "", time.Second, "Specifies how long to wait between pings.")
cmd.Flags().DurationVarP(&pingTimeout, "timeout", "t", 5*time.Second, "Specifies how long to wait for a ping to complete.")
cmd.Flags().IntVarP(&pingNum, "num", "n", 10, "Specifies the number of pings to perform.")
return cmd
}
54 changes: 54 additions & 0 deletions cli/ping_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package cli_test

import (
"context"
"testing"

"github.com/stretchr/testify/assert"

"cdr.dev/slog/sloggers/slogtest"

"github.com/coder/coder/agent"
"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/codersdk/agentsdk"
"github.com/coder/coder/pty/ptytest"
"github.com/coder/coder/testutil"
)

func TestPing(t *testing.T) {
t.Parallel()

t.Run("OK", func(t *testing.T) {
t.Parallel()

client, workspace, agentToken := setupWorkspaceForAgent(t, nil)
cmd, root := clitest.New(t, "ping", workspace.Name)
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetErr(pty.Output())
cmd.SetOut(pty.Output())

agentClient := agentsdk.New(client.URL)
agentClient.SetSessionToken(agentToken)
agentCloser := agent.New(agent.Options{
Client: agentClient,
Logger: slogtest.Make(t, nil).Named("agent"),
})
defer func() {
_ = agentCloser.Close()
}()

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()

cmdDone := tGo(t, func() {
err := cmd.ExecuteContext(ctx)
assert.NoError(t, err)
})

pty.ExpectMatch("pong from " + workspace.Name)
cancel()
<-cmdDone
})
}
3 changes: 2 additions & 1 deletion cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,12 @@ func Core() []*cobra.Command {
login(),
logout(),
parameters(),
ping(),
portForward(),
publickey(),
rename(),
resetPassword(),
restart(),
scaletest(),
schedules(),
show(),
Expand All @@ -97,7 +99,6 @@ func Core() []*cobra.Command {
start(),
state(),
stop(),
restart(),
templates(),
tokens(),
update(),
Expand Down
2 changes: 1 addition & 1 deletion cli/speedtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func speedtest() *cobra.Command {
return ctx.Err()
case <-ticker.C:
}
dur, p2p, err := conn.Ping(ctx)
dur, p2p, _, err := conn.Ping(ctx)
if err != nil {
continue
}
Expand Down
1 change: 1 addition & 0 deletions cli/testdata/coder_--help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Workspace Commands:
create Create a workspace
delete Delete a workspace
list List workspaces
ping Ping a workspace
rename Rename a workspace
restart Restart a workspace
schedule Schedule automated start and stop times for workspaces
Expand Down
26 changes: 26 additions & 0 deletions cli/testdata/coder_ping_--help.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Ping a workspace

Usage:
coder ping <workspace> [flags]

Flags:
-h, --help help for ping
-n, --num int Specifies the number of pings to perform. (default 10)
-t, --timeout duration Specifies how long to wait for a ping to complete. (default 5s)
-v, --verbose Enables verbose logging.
--wait duration Specifies how long to wait between pings. (default 1s)

Global Flags:
--global-config coder Path to the global coder config directory.
Consumes $CODER_CONFIG_DIR (default "~/.config/coderv2")
--header stringArray HTTP headers added to all requests. Provide as "Key=Value".
Consumes $CODER_HEADER
--no-feature-warning Suppress warnings about unlicensed features.
Consumes $CODER_NO_FEATURE_WARNING
--no-version-warning Suppress warning when client and server versions do not match.
Consumes $CODER_NO_VERSION_WARNING
--token string Specify an authentication token. For security reasons setting
CODER_SESSION_TOKEN is preferred.
Consumes $CODER_SESSION_TOKEN
--url string URL to a deployment.
Consumes $CODER_URL
2 changes: 1 addition & 1 deletion cli/vscodessh.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ type sshNetworkStats struct {
}

func collectNetworkStats(ctx context.Context, agentConn *codersdk.WorkspaceAgentConn, start, end time.Time, counts map[netlogtype.Connection]netlogtype.Counts) (*sshNetworkStats, error) {
latency, p2p, err := agentConn.Ping(ctx)
latency, p2p, _, err := agentConn.Ping(ctx)
if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion codersdk/workspaceagentconn.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/google/uuid"
"golang.org/x/crypto/ssh"
"golang.org/x/xerrors"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/speedtest"

"github.com/coder/coder/coderd/tracing"
Expand Down Expand Up @@ -136,7 +137,7 @@ func (c *WorkspaceAgentConn) AwaitReachable(ctx context.Context) bool {

// Ping pings the agent and returns the round-trip time.
// The bool returns true if the ping was made P2P.
func (c *WorkspaceAgentConn) Ping(ctx context.Context) (time.Duration, bool, error) {
func (c *WorkspaceAgentConn) Ping(ctx context.Context) (time.Duration, bool, *ipnstate.PingResult, error) {
ctx, span := tracing.StartSpan(ctx)
defer span.End()

Expand Down
1 change: 1 addition & 0 deletions docs/cli/coder.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ coder [flags]
- [coder list](coder_list.md) - List workspaces
- [coder login](coder_login.md) - Authenticate with Coder deployment
- [coder logout](coder_logout.md) - Unauthenticate your local session
- [coder ping](coder_ping.md) - Ping a workspace
- [coder port-forward](coder_port-forward.md) - Forward ports from machine to a workspace
- [coder publickey](coder_publickey.md) - Output your Coder public key used for Git operations
- [coder rename](coder_rename.md) - Rename a workspace
Expand Down
38 changes: 38 additions & 0 deletions docs/cli/coder_ping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
## coder ping

Ping a workspace

```
coder ping <workspace> [flags]
```

### Options

```
-h, --help help for ping
-n, --num int Specifies the number of pings to perform. (default 10)
-t, --timeout duration Specifies how long to wait for a ping to complete. (default 5s)
-v, --verbose Enables verbose logging.
--wait duration Specifies how long to wait between pings. (default 1s)
```

### Options inherited from parent commands

```
--global-config coder Path to the global coder config directory.
Consumes $CODER_CONFIG_DIR (default "~/.config/coderv2")
--header stringArray HTTP headers added to all requests. Provide as "Key=Value".
Consumes $CODER_HEADER
--no-feature-warning Suppress warnings about unlicensed features.
Consumes $CODER_NO_FEATURE_WARNING
--no-version-warning Suppress warning when client and server versions do not match.
Consumes $CODER_NO_VERSION_WARNING
--token string Specify an authentication token. For security reasons setting CODER_SESSION_TOKEN is preferred.
Consumes $CODER_SESSION_TOKEN
--url string URL to a deployment.
Consumes $CODER_URL
```

### SEE ALSO

- [coder](coder.md) -
4 changes: 4 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,10 @@
"title": "logout",
"path": "./cli/coder_logout.md"
},
{
"title": "ping",
"path": "./cli/coder_ping.md"
},
{
"title": "port-forward",
"path": "./cli/coder_port-forward.md"
Expand Down
4 changes: 2 additions & 2 deletions enterprise/coderd/replicas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func TestReplicas(t *testing.T) {
require.Eventually(t, func() bool {
ctx, cancelFunc := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancelFunc()
_, _, err = conn.Ping(ctx)
_, _, _, err = conn.Ping(ctx)
return err == nil
}, testutil.WaitLong, testutil.IntervalFast)
_ = conn.Close()
Expand Down Expand Up @@ -129,7 +129,7 @@ func TestReplicas(t *testing.T) {
require.Eventually(t, func() bool {
ctx, cancelFunc := context.WithTimeout(context.Background(), testutil.IntervalSlow)
defer cancelFunc()
_, _, err = conn.Ping(ctx)
_, _, _, err = conn.Ping(ctx)
return err == nil
}, testutil.WaitLong, testutil.IntervalFast)
_ = conn.Close()
Expand Down
2 changes: 1 addition & 1 deletion scaletest/agentconn/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func waitForDisco(ctx context.Context, logs io.Writer, conn *codersdk.WorkspaceA
for i := 0; i < pingAttempts; i++ {
_, _ = fmt.Fprintf(logs, "\tDisco ping attempt %d/%d...\n", i+1, pingAttempts)
pingCtx, cancel := context.WithTimeout(ctx, defaultRequestTimeout)
_, p2p, err := conn.Ping(pingCtx)
_, p2p, _, err := conn.Ping(pingCtx)
cancel()
if err == nil {
_, _ = fmt.Fprintf(logs, "\tDisco ping succeeded after %d attempts, p2p = %v\n", i+1, p2p)
Expand Down
Loading