diff --git a/api/v1/qdrantcluster_types.go b/api/v1/qdrantcluster_types.go
index 5efacee..523eec8 100644
--- a/api/v1/qdrantcluster_types.go
+++ b/api/v1/qdrantcluster_types.go
@@ -55,17 +55,6 @@ const (
ByCountAndSize RebalanceStrategy = "by_count_and_size"
)
-// StorageTier specifies the performance profile for the disk to use.
-// +kubebuilder:validation:Enum=budget;balanced;performance
-type StorageTier string
-
-//goland:noinspection GoUnusedConst
-const (
- StorageTierBudget StorageTier = "budget"
- StorageTierBalanced StorageTier = "balanced"
- StorageTierPerformance StorageTier = "performance"
-)
-
// QdrantClusterSpec defines the desired state of QdrantCluster
// +kubebuilder:pruning:PreserveUnknownFields
type QdrantClusterSpec struct {
@@ -128,10 +117,9 @@ type QdrantClusterSpec struct {
// StorageClassNames specifies the storage class names for db and snapshots.
// +optional
StorageClassNames *StorageClassNames `json:"storageClassNames,omitempty"`
- // StorageTier specifies the performance tier to use for the disk
- // +kubebuilder:validation:Enum=budget;balanced;performance
+ // Storage specifies the storage specification for the PVCs of the cluster. If the field is not set, no configuration will be applied.
// +optional
- StorageTier *StorageTier `json:"storageTier,omitempty"`
+ Storage *Storage `json:"storage,omitempty"`
// TopologySpreadConstraints specifies the topology spread constraints for the cluster.
// +optional
TopologySpreadConstraints *[]corev1.TopologySpreadConstraint `json:"topologySpreadConstraints,omitempty"`
@@ -169,6 +157,10 @@ func (s QdrantClusterSpec) Validate() error {
if err := s.Resources.Validate("Spec.Resources"); err != nil {
return err
}
+ // Validate Storage configurations
+ if err := s.Storage.Validate(); err != nil {
+ return err
+ }
return nil
}
@@ -790,6 +782,38 @@ func (n *StorageClassNames) GetSnapshots() *string {
return n.Snapshots
}
+type Storage struct {
+ // VolumeAttributesClassName specifies VolumeAttributeClass name to use for the storage PVCs
+ // +optional
+ VolumeAttributesClassName *string `json:"volumeAttributesClassName,omitempty"`
+ // IOPS defines the IOPS number to configure for the storage PVCs
+ // +optional
+ IOPS *int `json:"iops,omitempty"`
+ // Throughput defines the throughput number in MB/s for the storage PVCs
+ // +optional
+ Throughput *int `json:"throughput,omitempty"`
+}
+
+// Validate storage configurations
+func (s *Storage) Validate() error {
+ if s == nil {
+ return nil
+ }
+ // User can specify either VolumeAttributesClassName or both IOPS and Throughput
+ if s.VolumeAttributesClassName != nil {
+ // Both IOPS and Throughput must be nil
+ if s.Throughput != nil || s.IOPS != nil {
+ return fmt.Errorf(".spec.storage: can not specify both VolumeAttributesClassName and IOPS/Throughput")
+ }
+ return nil
+ }
+ // Must specify either both IOPS and Throughput or none
+ if (s.IOPS == nil && s.Throughput == nil) || (s.IOPS != nil && s.Throughput != nil) {
+ return nil
+ }
+ return fmt.Errorf(".spec.storage: must specify both IOPS and Throughput")
+}
+
type ClusterPhase string
//goland:noinspection GoUnusedConst
diff --git a/api/v1/qdrantcluster_types_test.go b/api/v1/qdrantcluster_types_test.go
index 961a46b..a2564d7 100644
--- a/api/v1/qdrantcluster_types_test.go
+++ b/api/v1/qdrantcluster_types_test.go
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "k8s.io/utils/ptr"
)
func TestValidate(t *testing.T) {
@@ -77,6 +78,103 @@ func TestValidate(t *testing.T) {
},
expectedError: fmt.Errorf("Spec.Resources.Memory error: quantities must match the regular expression '^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$'"),
},
+ {
+ name: "No storage configuration",
+ spec: QdrantClusterSpec{
+ Resources: Resources{
+ CPU: "100m",
+ Memory: "1Gi",
+ Storage: "2Gi",
+ },
+ },
+ expectedError: nil,
+ },
+ {
+ name: "Empty storage configuration",
+ spec: QdrantClusterSpec{
+ Resources: Resources{
+ CPU: "100m",
+ Memory: "1Gi",
+ Storage: "2Gi",
+ },
+ Storage: &Storage{},
+ },
+ expectedError: nil,
+ },
+ {
+ name: "Only VolumeAttributeClassName specified",
+ spec: QdrantClusterSpec{
+ Resources: Resources{
+ CPU: "100m",
+ Memory: "1Gi",
+ Storage: "2Gi",
+ },
+ Storage: &Storage{
+ VolumeAttributesClassName: ptr.To("foo"),
+ },
+ },
+ expectedError: nil,
+ },
+
+ {
+ name: "Both VolumeAttributeClassName and IOPS/Throughput specified",
+ spec: QdrantClusterSpec{
+ Resources: Resources{
+ CPU: "100m",
+ Memory: "1Gi",
+ Storage: "2Gi",
+ },
+ Storage: &Storage{
+ VolumeAttributesClassName: ptr.To("foo"),
+ IOPS: ptr.To(10000),
+ Throughput: ptr.To(500),
+ },
+ },
+ expectedError: fmt.Errorf(".spec.storage: can not specify both VolumeAttributesClassName and IOPS/Throughput"),
+ },
+ {
+ name: "Only IOPS specified",
+ spec: QdrantClusterSpec{
+ Resources: Resources{
+ CPU: "100m",
+ Memory: "1Gi",
+ Storage: "2Gi",
+ },
+ Storage: &Storage{
+ IOPS: ptr.To(10000),
+ },
+ },
+ expectedError: fmt.Errorf(".spec.storage: must specify both IOPS and Throughput"),
+ },
+ {
+ name: "Only Throughput specified",
+ spec: QdrantClusterSpec{
+ Resources: Resources{
+ CPU: "100m",
+ Memory: "1Gi",
+ Storage: "2Gi",
+ },
+ Storage: &Storage{
+ Throughput: ptr.To(500),
+ },
+ },
+ expectedError: fmt.Errorf(".spec.storage: must specify both IOPS and Throughput"),
+ },
+ {
+ name: "Both IOPS/Throughput specified",
+ spec: QdrantClusterSpec{
+ Resources: Resources{
+ CPU: "100m",
+ Memory: "1Gi",
+ Storage: "2Gi",
+ },
+ Storage: &Storage{
+ IOPS: ptr.To(10000),
+ Throughput: ptr.To(500),
+ },
+ },
+ expectedError: nil,
+ },
}
for _, tt := range testCases {
diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go
index 978de84..44ea736 100644
--- a/api/v1/zz_generated.deepcopy.go
+++ b/api/v1/zz_generated.deepcopy.go
@@ -951,10 +951,10 @@ func (in *QdrantClusterSpec) DeepCopyInto(out *QdrantClusterSpec) {
*out = new(StorageClassNames)
(*in).DeepCopyInto(*out)
}
- if in.StorageTier != nil {
- in, out := &in.StorageTier, &out.StorageTier
- *out = new(StorageTier)
- **out = **in
+ if in.Storage != nil {
+ in, out := &in.Storage, &out.Storage
+ *out = new(Storage)
+ (*in).DeepCopyInto(*out)
}
if in.TopologySpreadConstraints != nil {
in, out := &in.TopologySpreadConstraints, &out.TopologySpreadConstraints
@@ -1613,6 +1613,36 @@ func (in *RestoreSource) DeepCopy() *RestoreSource {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Storage) DeepCopyInto(out *Storage) {
+ *out = *in
+ if in.VolumeAttributesClassName != nil {
+ in, out := &in.VolumeAttributesClassName, &out.VolumeAttributesClassName
+ *out = new(string)
+ **out = **in
+ }
+ if in.IOPS != nil {
+ in, out := &in.IOPS, &out.IOPS
+ *out = new(int)
+ **out = **in
+ }
+ if in.Throughput != nil {
+ in, out := &in.Throughput, &out.Throughput
+ *out = new(int)
+ **out = **in
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Storage.
+func (in *Storage) DeepCopy() *Storage {
+ if in == nil {
+ return nil
+ }
+ out := new(Storage)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *StorageClass) DeepCopyInto(out *StorageClass) {
*out = *in
diff --git a/charts/qdrant-kubernetes-api/templates/region-crds/qdrant.io_qdrantclusters.yaml b/charts/qdrant-kubernetes-api/templates/region-crds/qdrant.io_qdrantclusters.yaml
index f2060f6..dc1449b 100644
--- a/charts/qdrant-kubernetes-api/templates/region-crds/qdrant.io_qdrantclusters.yaml
+++ b/charts/qdrant-kubernetes-api/templates/region-crds/qdrant.io_qdrantclusters.yaml
@@ -858,6 +858,24 @@ spec:
type: object
type: object
type: object
+ storage:
+ description: Storage specifies the storage specification for the PVCs
+ of the cluster. If the field is not set, no configuration will be
+ applied.
+ properties:
+ iops:
+ description: IOPS defines the IOPS number to configure for the
+ storage PVCs
+ type: integer
+ throughput:
+ description: Throughput defines the throughput number in MB/s
+ for the storage PVCs
+ type: integer
+ volumeAttributesClassName:
+ description: VolumeAttributesClassName specifies VolumeAttributeClass
+ name to use for the storage PVCs
+ type: string
+ type: object
storageClassNames:
description: StorageClassNames specifies the storage class names for
db and snapshots.
@@ -870,19 +888,6 @@ spec:
volume.
type: string
type: object
- storageTier:
- allOf:
- - enum:
- - budget
- - balanced
- - performance
- - enum:
- - budget
- - balanced
- - performance
- description: StorageTier specifies the performance tier to use for
- the disk
- type: string
suspend:
default: false
description: |-
diff --git a/crds/qdrant.io_qdrantclusters.yaml b/crds/qdrant.io_qdrantclusters.yaml
index 908a03c..01ca838 100644
--- a/crds/qdrant.io_qdrantclusters.yaml
+++ b/crds/qdrant.io_qdrantclusters.yaml
@@ -857,6 +857,24 @@ spec:
type: object
type: object
type: object
+ storage:
+ description: Storage specifies the storage specification for the PVCs
+ of the cluster. If the field is not set, no configuration will be
+ applied.
+ properties:
+ iops:
+ description: IOPS defines the IOPS number to configure for the
+ storage PVCs
+ type: integer
+ throughput:
+ description: Throughput defines the throughput number in MB/s
+ for the storage PVCs
+ type: integer
+ volumeAttributesClassName:
+ description: VolumeAttributesClassName specifies VolumeAttributeClass
+ name to use for the storage PVCs
+ type: string
+ type: object
storageClassNames:
description: StorageClassNames specifies the storage class names for
db and snapshots.
@@ -869,19 +887,6 @@ spec:
volume.
type: string
type: object
- storageTier:
- allOf:
- - enum:
- - budget
- - balanced
- - performance
- - enum:
- - budget
- - balanced
- - performance
- description: StorageTier specifies the performance tier to use for
- the disk
- type: string
suspend:
default: false
description: |-
diff --git a/docs/api.md b/docs/api.md
index 41cb91d..4258d65 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -796,7 +796,7 @@ _Appears in:_
| `gpu` _[GPU](#gpu)_ | GPU specifies GPU configuration for the cluster. If this field is not set, no GPU will be used. | | |
| `statefulSet` _[KubernetesStatefulSet](#kubernetesstatefulset)_ | StatefulSet specifies the configuration of the Qdrant Kubernetes StatefulSet. | | |
| `storageClassNames` _[StorageClassNames](#storageclassnames)_ | StorageClassNames specifies the storage class names for db and snapshots. | | |
-| `storageTier` _[StorageTier](#storagetier)_ | StorageTier specifies the performance tier to use for the disk | | Enum: [budget balanced performance]
|
+| `storage` _[Storage](#storage)_ | Storage specifies the storage specification for the PVCs of the cluster. If the field is not set, no configuration will be applied. | | |
| `topologySpreadConstraints` _[TopologySpreadConstraint](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#topologyspreadconstraint-v1-core)_ | TopologySpreadConstraints specifies the topology spread constraints for the cluster. | | |
| `podDisruptionBudget` _[PodDisruptionBudgetSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#poddisruptionbudgetspec-v1-policy)_ | PodDisruptionBudget specifies the pod disruption budget for the cluster. | | |
| `restartAllPodsConcurrently` _boolean_ | RestartAllPodsConcurrently specifies whether to restart all pods concurrently (also called one-shot-restart).
If enabled, all the pods in the cluster will be restarted concurrently in situations where multiple pods
need to be restarted, like when RestartedAtAnnotationKey is added/updated or the Qdrant version needs to be upgraded.
This helps sharded but not replicated clusters to reduce downtime to a possible minimum during restart.
If unset, the operator is going to restart nodes concurrently if none of the collections if replicated. | | |
@@ -1290,6 +1290,24 @@ _Appears in:_
| `Disabled` | |
+#### Storage
+
+
+
+
+
+
+
+_Appears in:_
+- [QdrantClusterSpec](#qdrantclusterspec)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `volumeAttributesClassName` _string_ | VolumeAttributesClassName specifies VolumeAttributeClass name to use for the storage PVCs | | |
+| `iops` _integer_ | IOPS defines the IOPS number to configure for the storage PVCs | | |
+| `throughput` _integer_ | Throughput defines the throughput number in MB/s for the storage PVCs | | |
+
+
#### StorageClass
@@ -1362,25 +1380,6 @@ _Appears in:_
| `async_scorer` _boolean_ | AsyncScorer enables io_uring when rescoring | | |
-#### StorageTier
-
-_Underlying type:_ _string_
-
-StorageTier specifies the performance profile for the disk to use.
-
-_Validation:_
-- Enum: [budget balanced performance]
-
-_Appears in:_
-- [QdrantClusterSpec](#qdrantclusterspec)
-
-| Field | Description |
-| --- | --- |
-| `budget` | |
-| `balanced` | |
-| `performance` | |
-
-
#### TraefikConfig
diff --git a/go.mod b/go.mod
index 7d80c9b..a4b328e 100644
--- a/go.mod
+++ b/go.mod
@@ -14,7 +14,6 @@ require (
k8s.io/apiextensions-apiserver v0.33.3
k8s.io/apimachinery v0.33.3
k8s.io/client-go v0.33.3
- k8s.io/utils v0.0.0-20241210054802-24370beab758
sigs.k8s.io/controller-runtime v0.21.0
)
@@ -68,6 +67,7 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
+ k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect