Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add new loadtest type agentconn (#4899)
- Loading branch information
1 parent
56b963a
commit f918977
Showing
5 changed files
with
898 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package agentconn | ||
|
||
import ( | ||
"net/url" | ||
|
||
"github.com/google/uuid" | ||
"golang.org/x/xerrors" | ||
|
||
"github.com/coder/coder/coderd/httpapi" | ||
) | ||
|
||
type ConnectionMode string | ||
|
||
const ( | ||
ConnectionModeDirect ConnectionMode = "direct" | ||
ConnectionModeDerp ConnectionMode = "derp" | ||
) | ||
|
||
type Config struct { | ||
// AgentID is the ID of the agent to connect to. | ||
AgentID uuid.UUID `json:"agent_id"` | ||
// ConnectionMode is the strategy to use when connecting to the agent. | ||
ConnectionMode ConnectionMode `json:"connection_mode"` | ||
// HoldDuration is the duration to hold the connection open for. If set to | ||
// 0, the connection will be closed immediately after making each request | ||
// once. | ||
HoldDuration httpapi.Duration `json:"hold_duration"` | ||
|
||
// Connections is the list of connections to make to services running | ||
// inside the workspace. Only HTTP connections are supported. | ||
Connections []Connection `json:"connections"` | ||
} | ||
|
||
type Connection struct { | ||
// URL is the address to connect to (e.g. "http://127.0.0.1:8080/path"). The | ||
// endpoint must respond with a any response within timeout. The IP address | ||
// is ignored and the connection is made to the agent's WireGuard IP | ||
// instead. | ||
URL string `json:"url"` | ||
// Interval is the duration to wait between connections to this endpoint. If | ||
// set to 0, the connection will only be made once. Must be set to 0 if | ||
// the parent config's hold_duration is set to 0. | ||
Interval httpapi.Duration `json:"interval"` | ||
// Timeout is the duration to wait for a connection to this endpoint to | ||
// succeed. If set to 0, the default timeout will be used. | ||
Timeout httpapi.Duration `json:"timeout"` | ||
} | ||
|
||
func (c Config) Validate() error { | ||
if c.AgentID == uuid.Nil { | ||
return xerrors.New("agent_id must be set") | ||
} | ||
if c.ConnectionMode == "" { | ||
return xerrors.New("connection_mode must be set") | ||
} | ||
switch c.ConnectionMode { | ||
case ConnectionModeDirect: | ||
case ConnectionModeDerp: | ||
default: | ||
return xerrors.Errorf("invalid connection_mode: %q", c.ConnectionMode) | ||
} | ||
if c.HoldDuration < 0 { | ||
return xerrors.New("hold_duration must be a positive value") | ||
} | ||
|
||
for i, conn := range c.Connections { | ||
if conn.URL == "" { | ||
return xerrors.Errorf("connections[%d].url must be set", i) | ||
} | ||
u, err := url.Parse(conn.URL) | ||
if err != nil { | ||
return xerrors.Errorf("connections[%d].url is not a valid URL: %w", i, err) | ||
} | ||
if u.Scheme != "http" { | ||
return xerrors.Errorf("connections[%d].url has an unsupported scheme %q, only http is supported", i, u.Scheme) | ||
} | ||
if conn.Interval < 0 { | ||
return xerrors.Errorf("connections[%d].interval must be a positive value", i) | ||
} | ||
if conn.Interval > 0 && c.HoldDuration == 0 { | ||
return xerrors.Errorf("connections[%d].interval must be 0 if hold_duration is 0", i) | ||
} | ||
if conn.Timeout < 0 { | ||
return xerrors.Errorf("connections[%d].timeout must be a positive value", i) | ||
} | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
package agentconn_test | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/google/uuid" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/coder/coder/coderd/httpapi" | ||
"github.com/coder/coder/loadtest/agentconn" | ||
) | ||
|
||
func Test_Config(t *testing.T) { | ||
t.Parallel() | ||
|
||
id := uuid.New() | ||
cases := []struct { | ||
name string | ||
config agentconn.Config | ||
errContains string | ||
}{ | ||
{ | ||
name: "OK", | ||
config: agentconn.Config{ | ||
AgentID: id, | ||
ConnectionMode: agentconn.ConnectionModeDirect, | ||
HoldDuration: httpapi.Duration(time.Minute), | ||
Connections: []agentconn.Connection{ | ||
{ | ||
URL: "http://localhost:8080/path", | ||
Interval: httpapi.Duration(time.Second), | ||
Timeout: httpapi.Duration(time.Second), | ||
}, | ||
{ | ||
URL: "http://localhost:8000/differentpath", | ||
Interval: httpapi.Duration(2 * time.Second), | ||
Timeout: httpapi.Duration(2 * time.Second), | ||
}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "NoAgentID", | ||
config: agentconn.Config{ | ||
AgentID: uuid.Nil, | ||
ConnectionMode: agentconn.ConnectionModeDirect, | ||
HoldDuration: 0, | ||
Connections: nil, | ||
}, | ||
errContains: "agent_id must be set", | ||
}, | ||
{ | ||
name: "NoConnectionMode", | ||
config: agentconn.Config{ | ||
AgentID: id, | ||
ConnectionMode: "", | ||
HoldDuration: 0, | ||
Connections: nil, | ||
}, | ||
errContains: "connection_mode must be set", | ||
}, | ||
{ | ||
name: "InvalidConnectionMode", | ||
config: agentconn.Config{ | ||
AgentID: id, | ||
ConnectionMode: "blah", | ||
HoldDuration: 0, | ||
Connections: nil, | ||
}, | ||
errContains: "invalid connection_mode", | ||
}, | ||
{ | ||
name: "NegativeHoldDuration", | ||
config: agentconn.Config{ | ||
AgentID: id, | ||
ConnectionMode: agentconn.ConnectionModeDerp, | ||
HoldDuration: -1, | ||
Connections: nil, | ||
}, | ||
errContains: "hold_duration must be a positive value", | ||
}, | ||
{ | ||
name: "ConnectionNoURL", | ||
config: agentconn.Config{ | ||
AgentID: id, | ||
ConnectionMode: agentconn.ConnectionModeDirect, | ||
HoldDuration: 1, | ||
Connections: []agentconn.Connection{{ | ||
URL: "", | ||
Interval: 0, | ||
Timeout: 0, | ||
}}, | ||
}, | ||
errContains: "connections[0].url must be set", | ||
}, | ||
{ | ||
name: "ConnectionInvalidURL", | ||
config: agentconn.Config{ | ||
AgentID: id, | ||
ConnectionMode: agentconn.ConnectionModeDirect, | ||
HoldDuration: 1, | ||
Connections: []agentconn.Connection{{ | ||
URL: string([]byte{0x7f}), | ||
Interval: 0, | ||
Timeout: 0, | ||
}}, | ||
}, | ||
errContains: "connections[0].url is not a valid URL", | ||
}, | ||
{ | ||
name: "ConnectionInvalidURLScheme", | ||
config: agentconn.Config{ | ||
AgentID: id, | ||
ConnectionMode: agentconn.ConnectionModeDirect, | ||
HoldDuration: 1, | ||
Connections: []agentconn.Connection{{ | ||
URL: "blah://localhost:8080", | ||
Interval: 0, | ||
Timeout: 0, | ||
}}, | ||
}, | ||
errContains: "connections[0].url has an unsupported scheme", | ||
}, | ||
{ | ||
name: "ConnectionNegativeInterval", | ||
config: agentconn.Config{ | ||
AgentID: id, | ||
ConnectionMode: agentconn.ConnectionModeDirect, | ||
HoldDuration: 1, | ||
Connections: []agentconn.Connection{{ | ||
URL: "http://localhost:8080", | ||
Interval: -1, | ||
Timeout: 0, | ||
}}, | ||
}, | ||
errContains: "connections[0].interval must be a positive value", | ||
}, | ||
{ | ||
name: "ConnectionIntervalMustBeZero", | ||
config: agentconn.Config{ | ||
AgentID: id, | ||
ConnectionMode: agentconn.ConnectionModeDirect, | ||
HoldDuration: 0, | ||
Connections: []agentconn.Connection{{ | ||
URL: "http://localhost:8080", | ||
Interval: 1, | ||
Timeout: 0, | ||
}}, | ||
}, | ||
errContains: "connections[0].interval must be 0 if hold_duration is 0", | ||
}, | ||
{ | ||
name: "ConnectionNegativeTimeout", | ||
config: agentconn.Config{ | ||
AgentID: id, | ||
ConnectionMode: agentconn.ConnectionModeDirect, | ||
HoldDuration: 1, | ||
Connections: []agentconn.Connection{{ | ||
URL: "http://localhost:8080", | ||
Interval: 0, | ||
Timeout: -1, | ||
}}, | ||
}, | ||
errContains: "connections[0].timeout must be a positive value", | ||
}, | ||
} | ||
|
||
for _, c := range cases { | ||
c := c | ||
|
||
t.Run(c.name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
err := c.config.Validate() | ||
if c.errContains != "" { | ||
require.Error(t, err) | ||
require.Contains(t, err.Error(), c.errContains) | ||
} else { | ||
require.NoError(t, err) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.