feat: move moons experiment to ga (released) (#11285)

* feat: release moons experiment as ga
This commit is contained in:
Steven Masley 2023-12-19 14:40:22 -06:00 committed by GitHub
parent e8be092af0
commit fbda21a9f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 57 additions and 205 deletions

2
coderd/apidoc/docs.go generated
View File

@ -8826,7 +8826,6 @@ const docTemplate = `{
"codersdk.Experiment": {
"type": "string",
"enum": [
"moons",
"workspace_actions",
"tailnet_pg_coordinator",
"single_tailnet",
@ -8834,7 +8833,6 @@ const docTemplate = `{
"template_update_policies"
],
"x-enum-varnames": [
"ExperimentMoons",
"ExperimentWorkspaceActions",
"ExperimentTailnetPGCoordinator",
"ExperimentSingleTailnet",

View File

@ -7904,7 +7904,6 @@
"codersdk.Experiment": {
"type": "string",
"enum": [
"moons",
"workspace_actions",
"tailnet_pg_coordinator",
"single_tailnet",
@ -7912,7 +7911,6 @@
"template_update_policies"
],
"x-enum-varnames": [
"ExperimentMoons",
"ExperimentWorkspaceActions",
"ExperimentTailnetPGCoordinator",
"ExperimentSingleTailnet",

View File

@ -2073,10 +2073,6 @@ func (c *Client) BuildInfo(ctx context.Context) (BuildInfoResponse, error) {
type Experiment string
const (
// ExperimentMoons enabled the workspace proxy endpoints and CRUD. This
// feature is not yet complete in functionality.
ExperimentMoons Experiment = "moons"
// https://github.com/coder/coder/milestone/19
ExperimentWorkspaceActions Experiment = "workspace_actions"

View File

@ -1,13 +1,5 @@
# Workspace Proxies
> Workspace proxies are in an
> [experimental state](../contributing/feature-stages.md#experimental-features)
> and the behavior is subject to change. Use
> [GitHub issues](https://github.com/coder/coder) to leave feedback. This
> experiment must be specifically enabled with the `--experiments="moons"`
> option on both coderd and the workspace proxy. If you have all experiements
> enabled, you have to add moons as well. `--experiments="*,moons"`
Workspace proxies provide low-latency experiences for geo-distributed teams.
Coder's networking does a best effort to make direct connections to a workspace.
@ -130,10 +122,6 @@ coder:
- name: CODER_WILDCARD_ACCESS_URL
value: "*.<app_hostname_of_proxy>"
# enables new paid features that are in alpha state
- name: CODER_EXPERIMENTS
value: "*,moons"
tls:
secretNames:
- kubernetes-wsproxy-secret

4
docs/api/general.md generated
View File

@ -563,7 +563,7 @@ curl -X GET http://coder-server:8080/api/v2/experiments \
> 200 Response
```json
["moons"]
["workspace_actions"]
```
### Responses
@ -600,7 +600,7 @@ curl -X GET http://coder-server:8080/api/v2/experiments/available \
> 200 Response
```json
["moons"]
["workspace_actions"]
```
### Responses

3
docs/api/schemas.md generated
View File

@ -2865,7 +2865,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
## codersdk.Experiment
```json
"moons"
"workspace_actions"
```
### Properties
@ -2874,7 +2874,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
| Value |
| -------------------------- |
| `moons` |
| `workspace_actions` |
| `tailnet_pg_coordinator` |
| `single_tailnet` |

View File

@ -9,7 +9,6 @@ import (
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
"github.com/coder/coder/v2/enterprise/coderd/license"
@ -23,16 +22,7 @@ func Test_ProxyCRUD(t *testing.T) {
t.Run("Create", func(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{
string(codersdk.ExperimentMoons),
"*",
}
client, _ := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: dv,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureWorkspaceProxy: 1,
@ -94,17 +84,7 @@ func Test_ProxyCRUD(t *testing.T) {
t.Run("Delete", func(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{
string(codersdk.ExperimentMoons),
"*",
}
client, _ := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: dv,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureWorkspaceProxy: 1,

View File

@ -362,34 +362,33 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
}
api.derpMesh = derpmesh.New(options.Logger.Named("derpmesh"), api.DERPServer, meshTLSConfig)
if api.AGPL.Experiments.Enabled(codersdk.ExperimentMoons) {
// Proxy health is a moon feature.
api.ProxyHealth, err = proxyhealth.New(&proxyhealth.Options{
Interval: options.ProxyHealthInterval,
DB: api.Database,
Logger: options.Logger.Named("proxyhealth"),
Client: api.HTTPClient,
Prometheus: api.PrometheusRegistry,
})
if err != nil {
return nil, xerrors.Errorf("initialize proxy health: %w", err)
}
go api.ProxyHealth.Run(ctx)
// Force the initial loading of the cache. Do this in a go routine in case
// the calls to the workspace proxies hang and this takes some time.
go api.forceWorkspaceProxyHealthUpdate(ctx)
// Use proxy health to return the healthy workspace proxy hostnames.
f := api.ProxyHealth.ProxyHosts
api.AGPL.WorkspaceProxyHostsFn.Store(&f)
// Wire this up to healthcheck.
var fetchUpdater healthcheck.WorkspaceProxiesFetchUpdater = &workspaceProxiesFetchUpdater{
fetchFunc: api.fetchWorkspaceProxies,
updateFunc: api.ProxyHealth.ForceUpdate,
}
api.AGPL.WorkspaceProxiesFetchUpdater.Store(&fetchUpdater)
// Moon feature init. Proxyhealh is a go routine to periodically check
// the health of all workspace proxies.
api.ProxyHealth, err = proxyhealth.New(&proxyhealth.Options{
Interval: options.ProxyHealthInterval,
DB: api.Database,
Logger: options.Logger.Named("proxyhealth"),
Client: api.HTTPClient,
Prometheus: api.PrometheusRegistry,
})
if err != nil {
return nil, xerrors.Errorf("initialize proxy health: %w", err)
}
go api.ProxyHealth.Run(ctx)
// Force the initial loading of the cache. Do this in a go routine in case
// the calls to the workspace proxies hang and this takes some time.
go api.forceWorkspaceProxyHealthUpdate(ctx)
// Use proxy health to return the healthy workspace proxy hostnames.
f := api.ProxyHealth.ProxyHosts
api.AGPL.WorkspaceProxyHostsFn.Store(&f)
// Wire this up to healthcheck.
var fetchUpdater healthcheck.WorkspaceProxiesFetchUpdater = &workspaceProxiesFetchUpdater{
fetchFunc: api.fetchWorkspaceProxies,
updateFunc: api.ProxyHealth.ForceUpdate,
}
api.AGPL.WorkspaceProxiesFetchUpdater.Store(&fetchUpdater)
err = api.PrometheusRegistry.Register(&api.licenseMetricsCollector)
if err != nil {

View File

@ -344,12 +344,6 @@ func (api *API) templateRBACEnabledMW(next http.Handler) http.Handler {
func (api *API) moonsEnabledMW(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// The experiment must be enabled.
if !api.AGPL.Experiments.Enabled(codersdk.ExperimentMoons) {
httpapi.RouteNotFound(rw)
return
}
// Entitlement must be enabled.
api.entitlementsMu.RLock()
proxy := api.entitlements.Features[codersdk.FeatureWorkspaceProxy].Enabled

View File

@ -36,20 +36,13 @@ func TestRegions(t *testing.T) {
t.Run("OK", func(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{
string(codersdk.ExperimentMoons),
"*",
}
db, pubsub := dbtestutil.NewDB(t)
client, _ := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
AppHostname: appHostname,
Database: db,
Pubsub: pubsub,
DeploymentValues: dv,
AppHostname: appHostname,
Database: db,
Pubsub: pubsub,
},
})
@ -79,20 +72,13 @@ func TestRegions(t *testing.T) {
t.Run("WithProxies", func(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{
string(codersdk.ExperimentMoons),
"*",
}
db, pubsub := dbtestutil.NewDB(t)
client, closer, api, _ := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
Options: &coderdtest.Options{
AppHostname: appHostname,
Database: db,
Pubsub: pubsub,
DeploymentValues: dv,
AppHostname: appHostname,
Database: db,
Pubsub: pubsub,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
@ -173,17 +159,10 @@ func TestRegions(t *testing.T) {
t.Run("RequireAuth", func(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{
string(codersdk.ExperimentMoons),
"*",
}
ctx := testutil.Context(t, testutil.WaitLong)
client, _ := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
AppHostname: appHostname,
DeploymentValues: dv,
AppHostname: appHostname,
},
})
@ -200,15 +179,7 @@ func TestWorkspaceProxyCRUD(t *testing.T) {
t.Run("CreateAndUpdate", func(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{
string(codersdk.ExperimentMoons),
"*",
}
client, _ := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: dv,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureWorkspaceProxy: 1,
@ -251,15 +222,7 @@ func TestWorkspaceProxyCRUD(t *testing.T) {
t.Run("Delete", func(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{
string(codersdk.ExperimentMoons),
"*",
}
client, _ := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: dv,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureWorkspaceProxy: 1,
@ -287,16 +250,9 @@ func TestProxyRegisterDeregister(t *testing.T) {
t.Parallel()
setup := func(t *testing.T) (*codersdk.Client, database.Store) {
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{
string(codersdk.ExperimentMoons),
"*",
}
db, pubsub := dbtestutil.NewDB(t)
client, _ := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: dv,
Database: db,
Pubsub: pubsub,
IncludeProvisionerDaemon: true,
@ -635,16 +591,9 @@ func TestProxyRegisterDeregister(t *testing.T) {
func TestIssueSignedAppToken(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{
string(codersdk.ExperimentMoons),
"*",
}
db, pubsub := dbtestutil.NewDB(t)
client, user := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: dv,
Database: db,
Pubsub: pubsub,
IncludeProvisionerDaemon: true,
@ -732,16 +681,9 @@ func TestIssueSignedAppToken(t *testing.T) {
func TestReconnectingPTYSignedToken(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{
string(codersdk.ExperimentMoons),
"*",
}
db, pubsub := dbtestutil.NewDB(t)
client, closer, api, user := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: dv,
Database: db,
Pubsub: pubsub,
IncludeProvisionerDaemon: true,

View File

@ -31,12 +31,6 @@ func Test_agentIsLegacy(t *testing.T) {
t.Run("Legacy", func(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{
string(codersdk.ExperimentMoons),
"*",
}
var (
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort)
db, pubsub = dbtestutil.NewDB(t)
@ -44,10 +38,9 @@ func Test_agentIsLegacy(t *testing.T) {
coordinator = agpl.NewCoordinator(logger)
client, _ = coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
Database: db,
Pubsub: pubsub,
DeploymentValues: dv,
Coordinator: coordinator,
Database: db,
Pubsub: pubsub,
Coordinator: coordinator,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
@ -98,12 +91,6 @@ func Test_agentIsLegacy(t *testing.T) {
t.Run("NotLegacy", func(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{
string(codersdk.ExperimentMoons),
"*",
}
var (
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort)
db, pubsub = dbtestutil.NewDB(t)
@ -111,10 +98,9 @@ func Test_agentIsLegacy(t *testing.T) {
coordinator = agpl.NewCoordinator(logger)
client, _ = coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
Database: db,
Pubsub: pubsub,
DeploymentValues: dv,
Coordinator: coordinator,
Database: db,
Pubsub: pubsub,
Coordinator: coordinator,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{

View File

@ -34,7 +34,6 @@ func TestDERPOnly(t *testing.T) {
deploymentValues := coderdtest.DeploymentValues(t)
deploymentValues.Experiments = []string{
string(codersdk.ExperimentMoons),
"*",
}
@ -82,7 +81,6 @@ func TestDERP(t *testing.T) {
deploymentValues := coderdtest.DeploymentValues(t)
deploymentValues.Experiments = []string{
string(codersdk.ExperimentMoons),
"*",
}
@ -315,7 +313,6 @@ func TestDERPEndToEnd(t *testing.T) {
deploymentValues := coderdtest.DeploymentValues(t)
deploymentValues.Experiments = []string{
string(codersdk.ExperimentMoons),
"*",
}
@ -443,7 +440,6 @@ func TestWorkspaceProxyWorkspaceApps_Wsconncache(t *testing.T) {
deploymentValues.Dangerous.AllowPathAppSharing = clibase.Bool(opts.DangerousAllowPathAppSharing)
deploymentValues.Dangerous.AllowPathAppSiteOwnerAccess = clibase.Bool(opts.DangerousAllowPathAppSiteOwnerAccess)
deploymentValues.Experiments = []string{
string(codersdk.ExperimentMoons),
"*",
}
@ -501,7 +497,6 @@ func TestWorkspaceProxyWorkspaceApps_SingleTailnet(t *testing.T) {
deploymentValues.Dangerous.AllowPathAppSharing = clibase.Bool(opts.DangerousAllowPathAppSharing)
deploymentValues.Dangerous.AllowPathAppSiteOwnerAccess = clibase.Bool(opts.DangerousAllowPathAppSiteOwnerAccess)
deploymentValues.Experiments = []string{
string(codersdk.ExperimentMoons),
string(codersdk.ExperimentSingleTailnet),
"*",
}

View File

@ -1778,14 +1778,12 @@ export const Entitlements: Entitlement[] = [
// From codersdk/deployment.go
export type Experiment =
| "deployment_health_page"
| "moons"
| "single_tailnet"
| "tailnet_pg_coordinator"
| "template_update_policies"
| "workspace_actions";
export const Experiments: Experiment[] = [
"deployment_health_page",
"moons",
"single_tailnet",
"tailnet_pg_coordinator",
"template_update_policies",

View File

@ -34,9 +34,7 @@ export const Navbar: FC = () => {
canViewDeployment={canViewDeployment}
canViewAllUsers={canViewAllUsers}
canViewHealth={canViewHealth}
proxyContextValue={
dashboard.experiments.includes("moons") ? proxyContextValue : undefined
}
proxyContextValue={proxyContextValue}
/>
);
};

View File

@ -38,11 +38,9 @@ export const Sidebar: FC = () => {
<SidebarNavItem href="network" icon={Globe}>
Network
</SidebarNavItem>
{dashboard.experiments.includes("moons") && (
<SidebarNavItem href="workspace-proxies" icon={HubOutlinedIcon}>
Workspace Proxies
</SidebarNavItem>
)}
<SidebarNavItem href="workspace-proxies" icon={HubOutlinedIcon}>
Workspace Proxies
</SidebarNavItem>
<SidebarNavItem href="security" icon={LockRounded}>
Security
</SidebarNavItem>

View File

@ -94,13 +94,12 @@ describe("optionValue", () => {
option: {
...defaultOption,
name: "Experiments",
value: ["moons"],
value: [],
},
additionalValues: ["single_tailnet", "deployment_health_page"],
expected: {
single_tailnet: false,
deployment_health_page: false,
moons: true,
},
},
{
@ -116,10 +115,10 @@ describe("optionValue", () => {
option: {
...defaultOption,
name: "Experiments",
value: ["*", "moons", "single_tailnet"],
value: ["*", "single_tailnet"],
},
additionalValues: ["single_tailnet"],
expected: { moons: true, single_tailnet: true },
expected: { single_tailnet: true },
},
{
option: {

View File

@ -1,7 +1,6 @@
import { useQuery } from "react-query";
import { getWorkspaceProxies, getWorkspaceProxyRegions } from "api/api";
import { Region, WorkspaceProxy } from "api/typesGenerated";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import {
createContext,
FC,
@ -85,9 +84,6 @@ export const ProxyContext = createContext<ProxyContextValue | undefined>(
* ProxyProvider interacts with local storage to indicate the preferred workspace proxy.
*/
export const ProxyProvider: FC<PropsWithChildren> = ({ children }) => {
const dashboard = useDashboard();
const experimentEnabled = dashboard?.experiments.includes("moons");
// Using a useState so the caller always has the latest user saved
// proxy.
const [userSavedProxy, setUserSavedProxy] = useState(loadUserSelectedProxy());
@ -176,13 +172,7 @@ export const ProxyProvider: FC<PropsWithChildren> = ({ children }) => {
proxyLatencies,
refetchProxyLatencies,
userProxy: userSavedProxy,
proxy: experimentEnabled
? proxy
: {
// If the experiment is disabled, then call 'getPreferredProxy' with the regions from
// the api call. The default behavior is to use the `primary` proxy.
...getPreferredProxy(proxiesResp || []),
},
proxy: proxy,
proxies: proxiesResp,
isLoading: proxiesLoading,
isFetched: proxiesFetched,

View File

@ -17,11 +17,9 @@ import { Suspense } from "react";
import { HealthIcon } from "./Content";
import { HealthSeverity } from "api/typesGenerated";
import NotificationsOffOutlined from "@mui/icons-material/NotificationsOffOutlined";
import { useDashboard } from "components/Dashboard/DashboardProvider";
export function HealthLayout() {
const theme = useTheme();
const dashboard = useDashboard();
const queryClient = useQueryClient();
const { data: healthStatus } = useQuery({
...health(),
@ -35,9 +33,7 @@ export function HealthLayout() {
access_url: "Access URL",
websocket: "Websocket",
database: "Database",
workspace_proxy: dashboard.experiments.includes("moons")
? "Workspace Proxy"
: undefined,
workspace_proxy: "Workspace Proxy",
} as const;
const visibleSections = filterVisibleSections(sections);

View File

@ -13,7 +13,6 @@ import "xterm/css/xterm.css";
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
import { pageTitle } from "utils/page";
import { useProxy } from "contexts/ProxyContext";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import type { Region } from "api/typesGenerated";
import { getLatencyColor } from "utils/latency";
import { ProxyStatusLatency } from "components/ProxyStatusLatency/ProxyStatusLatency";
@ -67,7 +66,6 @@ const TerminalPage: FC = () => {
const workspaceAgent = workspace.data
? getMatchingAgentOrFirst(workspace.data, workspaceNameParts?.[1])
: undefined;
const dashboard = useDashboard();
const selectedProxy = proxy.proxy;
const latency = selectedProxy ? proxyLatencies[selectedProxy.id] : undefined;
@ -312,11 +310,9 @@ const TerminalPage: FC = () => {
prevLifecycleState.current === "starting" && <LoadedScriptsAlert />}
{terminalState === "disconnected" && <DisconnectedAlert />}
<div css={styles.terminal} ref={xtermRef} data-testid="terminal" />
{dashboard.experiments.includes("moons") &&
selectedProxy &&
latency && (
<BottomBar proxy={selectedProxy} latency={latency.latencyMS} />
)}
{selectedProxy && latency && (
<BottomBar proxy={selectedProxy} latency={latency.latencyMS} />
)}
</div>
</>
);

View File

@ -2045,7 +2045,9 @@ export const MockEntitlementsWithUserLimit: TypesGen.Entitlements = {
}),
};
export const MockExperiments: TypesGen.Experiment[] = ["moons"];
export const MockExperiments: TypesGen.Experiment[] = [
"tailnet_pg_coordinator",
];
export const MockAuditLog: TypesGen.AuditLog = {
id: "fbd2116a-8961-4954-87ae-e4575bd29ce0",