Skip to content

Commit 227e632

Browse files
authored
fix: add grace period before showing replicas license error (#12989)
Fixes #8665.
1 parent b85d5d8 commit 227e632

File tree

3 files changed

+63
-4
lines changed

3 files changed

+63
-4
lines changed

enterprise/coderd/coderd.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"time"
1414

1515
"github.com/coder/coder/v2/coderd/appearance"
16+
"github.com/coder/coder/v2/coderd/database"
1617
agplportsharing "github.com/coder/coder/v2/coderd/portsharing"
1718
"github.com/coder/coder/v2/enterprise/coderd/portsharing"
1819

@@ -27,6 +28,7 @@ import (
2728
"github.com/coder/coder/v2/coderd"
2829
agplaudit "github.com/coder/coder/v2/coderd/audit"
2930
agpldbauthz "github.com/coder/coder/v2/coderd/database/dbauthz"
31+
"github.com/coder/coder/v2/coderd/database/dbtime"
3032
"github.com/coder/coder/v2/coderd/healthcheck"
3133
"github.com/coder/coder/v2/coderd/httpapi"
3234
"github.com/coder/coder/v2/coderd/httpmw"
@@ -64,6 +66,11 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
6466
if options.Options.Authorizer == nil {
6567
options.Options.Authorizer = rbac.NewCachingAuthorizer(options.PrometheusRegistry)
6668
}
69+
if options.ReplicaErrorGracePeriod == 0 {
70+
// This will prevent the error from being shown for a minute
71+
// from when an additional replica was started.
72+
options.ReplicaErrorGracePeriod = time.Minute
73+
}
6774

6875
ctx, cancelFunc := context.WithCancel(ctx)
6976

@@ -429,6 +436,7 @@ type Options struct {
429436

430437
// Used for high availability.
431438
ReplicaSyncUpdateInterval time.Duration
439+
ReplicaErrorGracePeriod time.Duration
432440
DERPServerRelayAddress string
433441
DERPServerRegionID int
434442

@@ -525,9 +533,24 @@ func (api *API) updateEntitlements(ctx context.Context) error {
525533
api.entitlementsUpdateMu.Lock()
526534
defer api.entitlementsUpdateMu.Unlock()
527535

536+
replicas := api.replicaManager.AllPrimary()
537+
agedReplicas := make([]database.Replica, 0, len(replicas))
538+
for _, replica := range replicas {
539+
// If a replica is less than the update interval old, we don't
540+
// want to display a warning. In the open-source version of Coder,
541+
// Kubernetes Pods will start up before shutting down the other,
542+
// and we don't want to display a warning in that case.
543+
//
544+
// Only display warnings for long-lived replicas!
545+
if dbtime.Now().Sub(replica.StartedAt) < api.ReplicaErrorGracePeriod {
546+
continue
547+
}
548+
agedReplicas = append(agedReplicas, replica)
549+
}
550+
528551
entitlements, err := license.Entitlements(
529552
ctx, api.Database,
530-
api.Logger, len(api.replicaManager.AllPrimary()), len(api.ExternalAuthConfigs), api.LicenseKeys, map[codersdk.FeatureName]bool{
553+
api.Logger, len(agedReplicas), len(api.ExternalAuthConfigs), api.LicenseKeys, map[codersdk.FeatureName]bool{
531554
codersdk.FeatureAuditLog: api.AuditLogging,
532555
codersdk.FeatureBrowserOnly: api.BrowserOnly,
533556
codersdk.FeatureSCIM: len(api.SCIMAPIKey) != 0,

enterprise/coderd/coderdenttest/coderdenttest.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type Options struct {
5757
DontAddLicense bool
5858
DontAddFirstUser bool
5959
ReplicaSyncUpdateInterval time.Duration
60+
ReplicaErrorGracePeriod time.Duration
6061
ExternalTokenEncryption []dbcrypt.Cipher
6162
ProvisionerDaemonPSK string
6263
}
@@ -93,6 +94,7 @@ func NewWithAPI(t *testing.T, options *Options) (
9394
DERPServerRelayAddress: oop.AccessURL.String(),
9495
DERPServerRegionID: oop.BaseDERPMap.RegionIDs()[0],
9596
ReplicaSyncUpdateInterval: options.ReplicaSyncUpdateInterval,
97+
ReplicaErrorGracePeriod: options.ReplicaErrorGracePeriod,
9698
Options: oop,
9799
EntitlementsUpdateInterval: options.EntitlementsUpdateInterval,
98100
LicenseKeys: Keys,

enterprise/coderd/replicas_test.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"crypto/tls"
66
"testing"
7+
"time"
78

89
"github.com/stretchr/testify/require"
910

@@ -22,9 +23,42 @@ import (
2223
func TestReplicas(t *testing.T) {
2324
t.Parallel()
2425
if !dbtestutil.WillUsePostgres() {
25-
t.Skip("only test with real postgresF")
26+
t.Skip("only test with real postgres")
2627
}
2728
t.Run("ErrorWithoutLicense", func(t *testing.T) {
29+
t.Parallel()
30+
// This will error because replicas are expected to instantly report
31+
// errors when the license is not present.
32+
db, pubsub := dbtestutil.NewDB(t)
33+
firstClient, _ := coderdenttest.New(t, &coderdenttest.Options{
34+
Options: &coderdtest.Options{
35+
IncludeProvisionerDaemon: true,
36+
Database: db,
37+
Pubsub: pubsub,
38+
},
39+
DontAddLicense: true,
40+
ReplicaErrorGracePeriod: time.Nanosecond,
41+
})
42+
secondClient, _, secondAPI, _ := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
43+
Options: &coderdtest.Options{
44+
Database: db,
45+
Pubsub: pubsub,
46+
},
47+
DontAddFirstUser: true,
48+
DontAddLicense: true,
49+
ReplicaErrorGracePeriod: time.Nanosecond,
50+
})
51+
secondClient.SetSessionToken(firstClient.SessionToken())
52+
ents, err := secondClient.Entitlements(context.Background())
53+
require.NoError(t, err)
54+
require.Len(t, ents.Errors, 1)
55+
_ = secondAPI.Close()
56+
57+
ents, err = firstClient.Entitlements(context.Background())
58+
require.NoError(t, err)
59+
require.Len(t, ents.Warnings, 0)
60+
})
61+
t.Run("DoesNotErrorBeforeGrace", func(t *testing.T) {
2862
t.Parallel()
2963
db, pubsub := dbtestutil.NewDB(t)
3064
firstClient, _ := coderdenttest.New(t, &coderdenttest.Options{
@@ -46,12 +80,12 @@ func TestReplicas(t *testing.T) {
4680
secondClient.SetSessionToken(firstClient.SessionToken())
4781
ents, err := secondClient.Entitlements(context.Background())
4882
require.NoError(t, err)
49-
require.Len(t, ents.Errors, 1)
83+
require.Len(t, ents.Errors, 0)
5084
_ = secondAPI.Close()
5185

5286
ents, err = firstClient.Entitlements(context.Background())
5387
require.NoError(t, err)
54-
require.Len(t, ents.Warnings, 0)
88+
require.Len(t, ents.Errors, 0)
5589
})
5690
t.Run("ConnectAcrossMultiple", func(t *testing.T) {
5791
t.Parallel()

0 commit comments

Comments
 (0)