Skip to content

Commit a5021da

Browse files
authored
[installer] make sure dashboard is deployed after server and papi-server (#19042)
* [installer] make sure dashboard is deployed after server and papi-server * fix build * Add unit tests * address feedback * wait feature flag until get actual value of timed out * default config cat client nil * log avg fetch time * 1 * mock feature flag hang * Add metric * fixup
1 parent bc8bd53 commit a5021da

File tree

17 files changed

+498
-26
lines changed

17 files changed

+498
-26
lines changed

components/common-go/experiments/configcat.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const (
2020
teamNameAttribute = "team_name"
2121
vscodeClientIDAttribute = "vscode_client_id"
2222
gitpodHost = "gitpod_host"
23+
component = "component"
2324
)
2425

2526
func newConfigCatClient(config configcat.Config) *configCatClient {
@@ -85,6 +86,10 @@ func attributesToUser(attributes Attributes) *configcat.UserData {
8586
custom[gitpodHost] = attributes.GitpodHost
8687
}
8788

89+
if attributes.Component != "" {
90+
custom[component] = attributes.Component
91+
}
92+
8893
return &configcat.UserData{
8994
Identifier: attributes.UserID,
9095
Email: attributes.UserEmail,

components/common-go/experiments/flags.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const (
1111
OIDCServiceEnabledFlag = "oidcServiceEnabled"
1212
SupervisorPersistServerAPIChannelWhenStartFlag = "supervisor_persist_serverapi_channel_when_start"
1313
SupervisorUsePublicAPIFlag = "supervisor_experimental_publicapi"
14+
ServiceWaiterSkipComponentsFlag = "service_waiter_skip_components"
1415
)
1516

1617
func IsPersonalAccessTokensEnabled(ctx context.Context, client Client, attributes Attributes) bool {

components/common-go/experiments/types.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ type Attributes struct {
3333
VSCodeClientID string
3434

3535
GitpodHost string
36+
37+
// Component is using in components/service-waiter
38+
// Feature Flag key is `service_waiter_skip_component`
39+
Component string
3640
}
3741

3842
type ClientOpt func(o *options)
@@ -45,10 +49,25 @@ func WithGitpodProxy(gitpodHost string) ClientOpt {
4549
}
4650
}
4751

52+
func WithPollInterval(interval time.Duration) ClientOpt {
53+
return func(o *options) {
54+
o.pollInterval = interval
55+
}
56+
}
57+
58+
func WithDefaultClient(defaultClient Client) ClientOpt {
59+
return func(o *options) {
60+
o.defaultClient = defaultClient
61+
o.hasDefaultClient = true
62+
}
63+
}
64+
4865
type options struct {
49-
pollInterval time.Duration
50-
baseURL string
51-
sdkKey string
66+
pollInterval time.Duration
67+
baseURL string
68+
sdkKey string
69+
defaultClient Client
70+
hasDefaultClient bool
5271
}
5372

5473
// NewClient constructs a new experiments.Client. This is NOT A SINGLETON.
@@ -66,6 +85,9 @@ func NewClient(opts ...ClientOpt) Client {
6685
}
6786

6887
if opt.sdkKey == "" {
88+
if opt.hasDefaultClient {
89+
return opt.defaultClient
90+
}
6991
return NewAlwaysReturningDefaultValueClient()
7092
}
7193
logger := log.Log.Dup()

components/service-waiter/cmd/component.go

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,31 @@ package cmd
77
import (
88
"context"
99
"fmt"
10+
"strconv"
1011
"time"
1112

1213
"github.com/sirupsen/logrus"
1314
"github.com/spf13/cobra"
1415
"github.com/spf13/viper"
1516

17+
"github.com/gitpod-io/gitpod/common-go/experiments"
1618
"github.com/gitpod-io/gitpod/common-go/log"
19+
"github.com/gitpod-io/gitpod/service-waiter/pkg/metrics"
1720
corev1 "k8s.io/api/core/v1"
1821
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1922
"k8s.io/client-go/kubernetes"
2023
"k8s.io/client-go/rest"
2124
)
2225

2326
var componentCmdOpt struct {
24-
image string
25-
namespace string
26-
component string
27-
labels string
27+
image string
28+
namespace string
29+
component string
30+
labels string
31+
ideMetricsHost string
32+
gitpodHost string
33+
34+
featureFlagTimeout time.Duration
2835
}
2936

3037
var componentCmd = &cobra.Command{
@@ -46,6 +53,8 @@ var componentCmd = &cobra.Command{
4653
ctx, cancel := context.WithTimeout(cmd.Context(), timeout)
4754
defer cancel()
4855

56+
go startWaitFeatureFlag(ctx, componentCmdOpt.featureFlagTimeout)
57+
4958
err := waitPodsImage(ctx)
5059

5160
if err != nil {
@@ -56,6 +65,8 @@ var componentCmd = &cobra.Command{
5665
},
5766
}
5867

68+
var shouldSkipComponentWaiter bool = false
69+
5970
func checkPodsImage(ctx context.Context, k8sClient *kubernetes.Clientset) (bool, error) {
6071
pods, err := k8sClient.CoreV1().Pods(componentCmdOpt.namespace).List(ctx, metav1.ListOptions{
6172
LabelSelector: componentCmdOpt.labels,
@@ -107,6 +118,10 @@ func waitPodsImage(ctx context.Context) error {
107118
}
108119
return ctx.Err()
109120
default:
121+
if shouldSkipComponentWaiter {
122+
log.Infof("skip component waiter %s with Feature Flag", componentCmdOpt.component)
123+
return nil
124+
}
110125
ok, err := checkPodsImage(ctx, k8sClient)
111126
if err != nil {
112127
log.WithError(err).Error("image check failed")
@@ -127,8 +142,75 @@ func init() {
127142
componentCmd.Flags().StringVar(&componentCmdOpt.namespace, "namespace", "", "The namespace of deployment")
128143
componentCmd.Flags().StringVar(&componentCmdOpt.component, "component", "", "Component name of deployment")
129144
componentCmd.Flags().StringVar(&componentCmdOpt.labels, "labels", "", "Labels of deployment")
145+
componentCmd.Flags().StringVar(&componentCmdOpt.ideMetricsHost, "ide-metrics-host", "", "Host of ide metrics")
146+
componentCmd.Flags().DurationVar(&componentCmdOpt.featureFlagTimeout, "feature-flag-timeout", 3*time.Minute, "The maximum time to wait for feature flag")
147+
componentCmd.Flags().StringVar(&componentCmdOpt.gitpodHost, "gitpod-host", "", "Domain of Gitpod installation")
130148

131149
_ = componentCmd.MarkFlagRequired("namespace")
132150
_ = componentCmd.MarkFlagRequired("component")
133151
_ = componentCmd.MarkFlagRequired("labels")
134152
}
153+
154+
func startWaitFeatureFlag(ctx context.Context, timeout time.Duration) {
155+
featureFlagCtx, cancel := context.WithTimeout(ctx, timeout)
156+
defer cancel()
157+
client := experiments.NewClient(experiments.WithDefaultClient(nil), experiments.WithPollInterval(time.Second*3))
158+
defaultSkip := true
159+
if client == nil {
160+
log.Error("failed to create experiments client, skip immediately")
161+
shouldSkipComponentWaiter = defaultSkip
162+
metrics.AddSkipComponentsCounter(componentCmdOpt.ideMetricsHost, strconv.FormatBool(shouldSkipComponentWaiter), false)
163+
return
164+
}
165+
getShouldSkipComponentWaiter := func() {
166+
startTime := time.Now()
167+
value, isActualValue, fetchTimes := ActualWaitFeatureFlag(featureFlagCtx, client, defaultSkip)
168+
avgTime := time.Duration(0)
169+
if fetchTimes > 0 {
170+
avgTime = time.Since(startTime) / time.Duration(fetchTimes)
171+
}
172+
log.WithField("fetchTimes", fetchTimes).WithField("avgTime", avgTime).WithField("isActualValue", isActualValue).WithField("value", value).Info("get final value of feature flag")
173+
shouldSkipComponentWaiter = value
174+
metrics.AddSkipComponentsCounter(componentCmdOpt.ideMetricsHost, strconv.FormatBool(shouldSkipComponentWaiter), isActualValue)
175+
}
176+
for !shouldSkipComponentWaiter {
177+
if featureFlagCtx.Err() != nil {
178+
break
179+
}
180+
getShouldSkipComponentWaiter()
181+
time.Sleep(1 * time.Second)
182+
}
183+
}
184+
185+
var FeatureSleepDuration = 1 * time.Second
186+
187+
func ActualWaitFeatureFlag(ctx context.Context, client experiments.Client, defaultValue bool) (flagValue bool, ok bool, fetchTimes int) {
188+
if client == nil {
189+
return defaultValue, false, fetchTimes
190+
}
191+
for {
192+
select {
193+
case <-ctx.Done():
194+
log.WithField("defaultValue", defaultValue).Info("feature flag timeout with no expected value, fallback")
195+
return defaultValue, false, fetchTimes
196+
default:
197+
stringValue := client.GetStringValue(ctx, experiments.ServiceWaiterSkipComponentsFlag, "NONE", experiments.Attributes{
198+
GitpodHost: componentCmdOpt.gitpodHost,
199+
Component: componentCmdOpt.component,
200+
})
201+
fetchTimes++
202+
if stringValue == "true" {
203+
return true, true, fetchTimes
204+
}
205+
if stringValue == "false" {
206+
return false, true, fetchTimes
207+
}
208+
if stringValue == "NONE" {
209+
time.Sleep(FeatureSleepDuration)
210+
continue
211+
}
212+
log.WithField("value", stringValue).Warn("unexpected value from feature flag")
213+
time.Sleep(FeatureSleepDuration)
214+
}
215+
}
216+
}

0 commit comments

Comments
 (0)