diff --git a/api/operator/v1alpha1/vmdistributed_types.go b/api/operator/v1alpha1/vmdistributed_types.go
index e6669b2e8..55760fc0b 100644
--- a/api/operator/v1alpha1/vmdistributed_types.go
+++ b/api/operator/v1alpha1/vmdistributed_types.go
@@ -84,11 +84,23 @@ type VMDistributedZoneCommon struct {
UpdatePause *metav1.Duration `json:"updatePause,omitempty"`
}
+type VMDistributedTrafficMode string
+
+const (
+ VMDistributedTrafficModeReadWrite VMDistributedTrafficMode = "read-write"
+ VMDistributedTrafficModeReadOnly VMDistributedTrafficMode = "read-only"
+ VMDistributedTrafficModeWriteOnly VMDistributedTrafficMode = "write-only"
+ VMDistributedTrafficModeMaintenance VMDistributedTrafficMode = "maintenance"
+)
+
// +k8s:openapi-gen=true
// VMDistributedZone defines items within a single zone to update.
type VMDistributedZone struct {
// Name defines a name of zone, which can be used in zoneCommon spec as %ZONE%
Name string `json:"name"`
+ // TrafficMode defines allowed traffic mode for a zone: read-only, write-only, read-write, maintenance
+ // +kubebuilder:validation:Enum=read-only;write-only;read-write;maintenance
+ TrafficMode VMDistributedTrafficMode `json:"trafficMode,omitempty"`
// VMCluster defines a new inline or referencing existing one VMCluster
// +optional
VMCluster VMDistributedZoneCluster `json:"vmcluster,omitempty"`
diff --git a/config/crd/overlay/crd.descriptionless.yaml b/config/crd/overlay/crd.descriptionless.yaml
index fbab3c958..76881ae54 100644
--- a/config/crd/overlay/crd.descriptionless.yaml
+++ b/config/crd/overlay/crd.descriptionless.yaml
@@ -27510,6 +27510,13 @@ spec:
type: string
remoteWrite:
x-kubernetes-preserve-unknown-fields: true
+ trafficMode:
+ enum:
+ - read-only
+ - write-only
+ - read-write
+ - maintenance
+ type: string
vmagent:
properties:
name:
diff --git a/config/crd/overlay/crd.yaml b/config/crd/overlay/crd.yaml
index d20af526f..38ebffb0a 100644
--- a/config/crd/overlay/crd.yaml
+++ b/config/crd/overlay/crd.yaml
@@ -55810,6 +55810,15 @@ spec:
description: RemoteWrite defines VMAgent remote write settings
for given zone
x-kubernetes-preserve-unknown-fields: true
+ trafficMode:
+ description: 'TrafficMode defines allowed traffic mode for a
+ zone: read-only, write-only, read-write, maintenance'
+ enum:
+ - read-only
+ - write-only
+ - read-write
+ - maintenance
+ type: string
vmagent:
description: VMAgent defines VMAgent to balance incoming traffic
between VMClusters.
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 53ce0a760..e6a4ee304 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -14,6 +14,7 @@ aliases:
## tip
* FEATURE: [vmauth](https://docs.victoriametrics.com/operator/resources/vmauth): previously VMAuth could read configuration only from predefined locations; now VMAuth supports arbitrary filesystem access configuration, allowing users to reference required files directly and reducing configuration workarounds. See [#899](https://github.com/VictoriaMetrics/operator/issues/899).
+* FEATURE: [vmdistributed](https://docs.victoriametrics.com/operator/resources/vmdistributed): introduce `spec.zones[*].trafficMode` property, which allows disable read, write or whole traffic to a zone. See [#1995](https://github.com/VictoriaMetrics/operator/issues/1995).
* BUGFIX: [converter](https://docs.victoriametrics.com/operator/integrations/prometheus/#objects-conversion): disable all prometheus controllers if CRD group was not found. See [#2838](https://github.com/VictoriaMetrics/helm-charts/issues/2838).
diff --git a/docs/api.md b/docs/api.md
index 63b7790e5..b3e699e6f 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -1145,6 +1145,12 @@ Appears in: [VMDistributed](#vmdistributed)
| zoneCommon#
_[VMDistributedZoneCommon](#vmdistributedzonecommon)_ | _(Optional)_
ZoneCommon defines common properties for all zones |
| zones#
_[VMDistributedZone](#vmdistributedzone) array_ | _(Required)_
Zones is a list of zones to update. Each item in the list represents a "zone" within the distributed setup. |
+#### VMDistributedTrafficMode
+
+_Underlying type:_ _string_
+
+Appears in: [VMDistributedZone](#vmdistributedzone)
+
#### VMDistributedZone
VMDistributedZone defines items within a single zone to update.
@@ -1155,6 +1161,7 @@ Appears in: [VMDistributedSpec](#vmdistributedspec)
| --- | --- |
| name#
_string_ | _(Required)_
Name defines a name of zone, which can be used in zoneCommon spec as %ZONE% |
| remoteWrite#
_[VMDistributedZoneRemoteWriteSpec](#vmdistributedzoneremotewritespec)_ | _(Optional)_
RemoteWrite defines VMAgent remote write settings for given zone |
+| trafficMode#
_[VMDistributedTrafficMode](#vmdistributedtrafficmode)_ | _(Required)_
TrafficMode defines allowed traffic mode for a zone: read-only, write-only, read-write, maintenance |
| vmagent#
_[VMDistributedZoneAgent](#vmdistributedzoneagent)_ | _(Optional)_
VMAgent defines VMAgent to balance incoming traffic between VMClusters. |
| vmcluster#
_[VMDistributedZoneCluster](#vmdistributedzonecluster)_ | _(Optional)_
VMCluster defines a new inline or referencing existing one VMCluster |
diff --git a/internal/controller/operator/factory/build/defaults.go b/internal/controller/operator/factory/build/defaults.go
index cc70e86ad..5f30b526d 100644
--- a/internal/controller/operator/factory/build/defaults.go
+++ b/internal/controller/operator/factory/build/defaults.go
@@ -52,7 +52,6 @@ func AddDefaults(scheme *runtime.Scheme) {
func addVMDistributedDefaults(objI any) {
cr := objI.(*vmv1alpha1.VMDistributed)
-
if cr.Spec.ZoneCommon.ReadyTimeout == nil {
cr.Spec.ZoneCommon.ReadyTimeout = &metav1.Duration{
Duration: 5 * time.Minute,
@@ -63,6 +62,12 @@ func addVMDistributedDefaults(objI any) {
Duration: 1 * time.Minute,
}
}
+ for i := range cr.Spec.Zones {
+ z := &cr.Spec.Zones[i]
+ if len(z.TrafficMode) == 0 {
+ z.TrafficMode = vmv1alpha1.VMDistributedTrafficModeReadWrite
+ }
+ }
if cr.Spec.License.IsProvided() {
if !cr.Spec.VMAuth.Spec.License.IsProvided() {
cr.Spec.VMAuth.Spec.License = cr.Spec.License.DeepCopy()
diff --git a/internal/controller/operator/factory/vmdistributed/vmauth.go b/internal/controller/operator/factory/vmdistributed/vmauth.go
index 16b7bbf06..70e044b8d 100644
--- a/internal/controller/operator/factory/vmdistributed/vmauth.go
+++ b/internal/controller/operator/factory/vmdistributed/vmauth.go
@@ -96,7 +96,7 @@ func vmAgentTargetRef(vmAgents []*vmv1beta1.VMAgent, owner *metav1.OwnerReferenc
return ref
}
-func buildVMAuthLB(cr *vmv1alpha1.VMDistributed, vmAgents []*vmv1beta1.VMAgent, vmClusters []*vmv1beta1.VMCluster, excludeIds ...int) *vmv1beta1.VMAuth {
+func buildVMAuthLB(cr *vmv1alpha1.VMDistributed, vmAgents []*vmv1beta1.VMAgent, vmClusters []*vmv1beta1.VMCluster, trafficModes []vmv1alpha1.VMDistributedTrafficMode, excludeIds ...int) *vmv1beta1.VMAuth {
if !ptr.Deref(cr.Spec.VMAuth.Enabled, true) || build.IsControllerDisabled("VMAuth") {
return nil
}
@@ -108,10 +108,23 @@ func buildVMAuthLB(cr *vmv1alpha1.VMDistributed, vmAgents []*vmv1beta1.VMAgent,
},
Spec: *cr.Spec.VMAuth.Spec.DeepCopy(),
}
+ writeExcludeIds := slices.Clone(excludeIds)
+ readExcludeIds := slices.Clone(excludeIds)
+ for i, mode := range trafficModes {
+ switch mode {
+ case vmv1alpha1.VMDistributedTrafficModeReadOnly:
+ writeExcludeIds = append(writeExcludeIds, i)
+ case vmv1alpha1.VMDistributedTrafficModeWriteOnly:
+ readExcludeIds = append(readExcludeIds, i)
+ case vmv1alpha1.VMDistributedTrafficModeMaintenance:
+ writeExcludeIds = append(writeExcludeIds, i)
+ readExcludeIds = append(readExcludeIds, i)
+ }
+ }
var targetRefs []vmv1beta1.TargetRef
owner := cr.AsOwner()
- targetRefs = append(targetRefs, vmAgentTargetRef(vmAgents, &owner, excludeIds...))
- targetRefs = append(targetRefs, vmClusterTargetRef(vmClusters, &owner, excludeIds...))
+ targetRefs = append(targetRefs, vmAgentTargetRef(vmAgents, &owner, writeExcludeIds...))
+ targetRefs = append(targetRefs, vmClusterTargetRef(vmClusters, &owner, readExcludeIds...))
vmAuth.Spec.DefaultTargetRefs = targetRefs
return &vmAuth
}
diff --git a/internal/controller/operator/factory/vmdistributed/vmauth_test.go b/internal/controller/operator/factory/vmdistributed/vmauth_test.go
index e2caf3245..4211b519c 100644
--- a/internal/controller/operator/factory/vmdistributed/vmauth_test.go
+++ b/internal/controller/operator/factory/vmdistributed/vmauth_test.go
@@ -160,7 +160,7 @@ func TestBuildVMAuthLBZoneOrder(t *testing.T) {
f := func(o opts) {
t.Helper()
- vmAuth := buildVMAuthLB(cr, o.agents, o.clusters, o.excludeIds...)
+ vmAuth := buildVMAuthLB(cr, o.agents, o.clusters, nil, o.excludeIds...)
assert.NotNil(t, vmAuth)
assert.Len(t, vmAuth.Spec.DefaultTargetRefs, 2)
diff --git a/internal/controller/operator/factory/vmdistributed/vmdistributed_test.go b/internal/controller/operator/factory/vmdistributed/vmdistributed_test.go
index df2fcc073..61c3c3102 100644
--- a/internal/controller/operator/factory/vmdistributed/vmdistributed_test.go
+++ b/internal/controller/operator/factory/vmdistributed/vmdistributed_test.go
@@ -276,7 +276,7 @@ func TestCreateOrUpdate(t *testing.T) {
}
},
validate: func(ctx context.Context, rclient client.Client, d *testData) {
- vmAuth := buildVMAuthLB(d.cr, d.zones.vmagents, d.zones.vmclusters)
+ vmAuth := buildVMAuthLB(d.cr, d.zones.vmagents, d.zones.vmclusters, d.zones.trafficModes)
owner := d.cr.AsOwner()
assert.NoError(t, reconcile.VMAuth(ctx, rclient, vmAuth, nil, &owner))
},
@@ -291,7 +291,7 @@ func TestCreateOrUpdate(t *testing.T) {
},
preRun: func(c client.Client, d *testData) {
clusters := []*vmv1beta1.VMCluster{d.zones.vmclusters[0]}
- lb := buildVMAuthLB(d.cr, d.zones.vmagents, clusters)
+ lb := buildVMAuthLB(d.cr, d.zones.vmagents, clusters, nil)
c.Scheme().Default(lb)
assert.NoError(t, c.Create(context.TODO(), lb))
},
@@ -316,7 +316,7 @@ func TestCreateOrUpdate(t *testing.T) {
LogLevel: "INFO",
}
clusters := []*vmv1beta1.VMCluster{d.zones.vmclusters[0]}
- vmAuth := buildVMAuthLB(d.cr, d.zones.vmagents, clusters)
+ vmAuth := buildVMAuthLB(d.cr, d.zones.vmagents, clusters, nil)
owner := d.cr.AsOwner()
assert.NoError(t, reconcile.VMAuth(ctx, rclient, vmAuth, nil, &owner))
},
@@ -333,7 +333,7 @@ func TestCreateOrUpdate(t *testing.T) {
},
preRun: func(c client.Client, d *testData) {
clusters := []*vmv1beta1.VMCluster{d.zones.vmclusters[0]}
- lb := buildVMAuthLB(d.cr, d.zones.vmagents, clusters)
+ lb := buildVMAuthLB(d.cr, d.zones.vmagents, clusters, nil)
c.Scheme().Default(lb)
assert.NoError(t, c.Create(context.TODO(), lb))
},
@@ -351,7 +351,7 @@ func TestCreateOrUpdate(t *testing.T) {
},
validate: func(ctx context.Context, rclient client.Client, d *testData) {
clusters := []*vmv1beta1.VMCluster{d.zones.vmclusters[0]}
- vmAuth := buildVMAuthLB(d.cr, d.zones.vmagents, clusters)
+ vmAuth := buildVMAuthLB(d.cr, d.zones.vmagents, clusters, nil)
owner := d.cr.AsOwner()
assert.NoError(t, reconcile.VMAuth(ctx, rclient, vmAuth, nil, &owner))
},
@@ -389,7 +389,7 @@ func TestCreateOrUpdate(t *testing.T) {
}
},
validate: func(ctx context.Context, rclient client.Client, d *testData) {
- vmAuth := buildVMAuthLB(d.cr, d.zones.vmagents, d.zones.vmclusters)
+ vmAuth := buildVMAuthLB(d.cr, d.zones.vmagents, d.zones.vmclusters, d.zones.trafficModes)
owner := d.cr.AsOwner()
assert.NoError(t, reconcile.VMAuth(ctx, rclient, vmAuth, nil, &owner))
var vmClusterObjs, vmAgentObjs []vmv1beta1.NamespacedName
@@ -451,7 +451,7 @@ func TestCreateOrUpdate(t *testing.T) {
},
preRun: func(c client.Client, d *testData) {
clusters := []*vmv1beta1.VMCluster{d.zones.vmclusters[0]}
- lb := buildVMAuthLB(d.cr, d.zones.vmagents, clusters)
+ lb := buildVMAuthLB(d.cr, d.zones.vmagents, clusters, nil)
c.Scheme().Default(lb)
lb.OwnerReferences = nil
assert.NoError(t, c.Create(context.TODO(), lb))
@@ -478,7 +478,7 @@ func TestCreateOrUpdate(t *testing.T) {
},
validate: func(ctx context.Context, rclient client.Client, d *testData) {
clusters := []*vmv1beta1.VMCluster{d.zones.vmclusters[0]}
- vmAuth := buildVMAuthLB(d.cr, d.zones.vmagents, clusters)
+ vmAuth := buildVMAuthLB(d.cr, d.zones.vmagents, clusters, nil)
owner := d.cr.AsOwner()
assert.NoError(t, reconcile.VMAuth(ctx, rclient, vmAuth, nil, &owner))
var got vmv1beta1.VMAuth
@@ -524,7 +524,7 @@ func TestCreateOrUpdate(t *testing.T) {
}
},
validate: func(ctx context.Context, rclient client.Client, d *testData) {
- vmAuth := buildVMAuthLB(d.cr, d.zones.vmagents, d.zones.vmclusters)
+ vmAuth := buildVMAuthLB(d.cr, d.zones.vmagents, d.zones.vmclusters, d.zones.trafficModes)
owner := d.cr.AsOwner()
assert.NoError(t, reconcile.VMAuth(ctx, rclient, vmAuth, nil, &owner))
var vmClusterObjs, vmAgentObjs []vmv1beta1.NamespacedName
@@ -582,7 +582,7 @@ func TestCreateOrUpdate(t *testing.T) {
},
preRun: func(c client.Client, d *testData) {
// Create initial VMAuth backed by CRD targets
- lb := buildVMAuthLB(d.cr, d.zones.vmagents, d.zones.vmclusters)
+ lb := buildVMAuthLB(d.cr, d.zones.vmagents, d.zones.vmclusters, d.zones.trafficModes)
c.Scheme().Default(lb)
assert.NoError(t, c.Create(context.TODO(), lb))
},
@@ -608,7 +608,7 @@ func TestCreateOrUpdate(t *testing.T) {
},
validate: func(ctx context.Context, rclient client.Client, d *testData) {
// Simulate removing both backends by passing empty slices
- vmAuth := buildVMAuthLB(d.cr, nil, nil)
+ vmAuth := buildVMAuthLB(d.cr, nil, nil, nil)
owner := d.cr.AsOwner()
assert.NoError(t, reconcile.VMAuth(ctx, rclient, vmAuth, nil, &owner))
diff --git a/internal/controller/operator/factory/vmdistributed/zone.go b/internal/controller/operator/factory/vmdistributed/zone.go
index 46406cee5..c7d4ab774 100644
--- a/internal/controller/operator/factory/vmdistributed/zone.go
+++ b/internal/controller/operator/factory/vmdistributed/zone.go
@@ -30,10 +30,11 @@ import (
)
type zones struct {
- httpClient *http.Client
- vmagents []*vmv1beta1.VMAgent
- vmclusters []*vmv1beta1.VMCluster
- hasChanges []bool
+ httpClient *http.Client
+ vmagents []*vmv1beta1.VMAgent
+ vmclusters []*vmv1beta1.VMCluster
+ hasChanges []bool
+ trafficModes []vmv1alpha1.VMDistributedTrafficMode
}
func (zs *zones) Len() int {
@@ -66,6 +67,7 @@ func (zs *zones) Swap(i, j int) {
zs.vmagents[i], zs.vmagents[j] = zs.vmagents[j], zs.vmagents[i]
zs.vmclusters[i], zs.vmclusters[j] = zs.vmclusters[j], zs.vmclusters[i]
zs.hasChanges[i], zs.hasChanges[j] = zs.hasChanges[j], zs.hasChanges[i]
+ zs.trafficModes[i], zs.trafficModes[j] = zs.trafficModes[j], zs.trafficModes[i]
}
// getZones builds desired zones
@@ -74,9 +76,10 @@ func getZones(ctx context.Context, rclient client.Client, cr *vmv1alpha1.VMDistr
httpClient: &http.Client{
Timeout: httpTimeout,
},
- vmagents: make([]*vmv1beta1.VMAgent, len(cr.Spec.Zones)),
- vmclusters: make([]*vmv1beta1.VMCluster, len(cr.Spec.Zones)),
- hasChanges: make([]bool, len(cr.Spec.Zones)),
+ vmagents: make([]*vmv1beta1.VMAgent, len(cr.Spec.Zones)),
+ vmclusters: make([]*vmv1beta1.VMCluster, len(cr.Spec.Zones)),
+ hasChanges: make([]bool, len(cr.Spec.Zones)),
+ trafficModes: make([]vmv1alpha1.VMDistributedTrafficMode, len(cr.Spec.Zones)),
}
for i := range cr.Spec.Zones {
z := &cr.Spec.Zones[i]
@@ -102,8 +105,12 @@ func getZones(ctx context.Context, rclient client.Client, cr *vmv1alpha1.VMDistr
prevClusterSpec := vmCluster.Spec
vmCluster.Spec = *vmClusterSpec
rclient.Scheme().Default(&vmCluster)
+ if (z.TrafficMode == vmv1alpha1.VMDistributedTrafficModeReadOnly || z.TrafficMode == vmv1alpha1.VMDistributedTrafficModeMaintenance) && vmCluster.Spec.VMInsert != nil {
+ vmCluster.Spec.VMInsert.ReplicaCount = ptr.To(int32(0))
+ }
zs.hasChanges[i] = !equality.Semantic.DeepEqual(&vmCluster.Spec, &prevClusterSpec)
zs.vmclusters[i] = &vmCluster
+ zs.trafficModes[i] = z.TrafficMode
}
for i := range cr.Spec.Zones {
@@ -238,7 +245,7 @@ func (zs *zones) updateLB(ctx context.Context, rclient client.Client, cr *vmv1al
if !ptr.Deref(cr.Spec.VMAuth.Enabled, true) {
return nil
}
- vmAuth := buildVMAuthLB(cr, zs.vmagents, zs.vmclusters, excludeIds...)
+ vmAuth := buildVMAuthLB(cr, zs.vmagents, zs.vmclusters, zs.trafficModes, excludeIds...)
if vmAuth == nil {
return nil
}
diff --git a/internal/controller/operator/factory/vmdistributed/zone_test.go b/internal/controller/operator/factory/vmdistributed/zone_test.go
index 22852d3bf..09339142e 100644
--- a/internal/controller/operator/factory/vmdistributed/zone_test.go
+++ b/internal/controller/operator/factory/vmdistributed/zone_test.go
@@ -260,9 +260,10 @@ func TestZonesSorting(t *testing.T) {
f := func(o opts) {
t.Helper()
zs := &zones{
- vmclusters: o.clusters,
- vmagents: make([]*vmv1beta1.VMAgent, len(o.clusters)),
- hasChanges: make([]bool, len(o.clusters)),
+ vmclusters: o.clusters,
+ vmagents: make([]*vmv1beta1.VMAgent, len(o.clusters)),
+ hasChanges: make([]bool, len(o.clusters)),
+ trafficModes: make([]vmv1alpha1.VMDistributedTrafficMode, len(o.clusters)),
}
for i := range o.clusters {
zs.vmagents[i] = &vmv1beta1.VMAgent{