diff --git a/.gitignore b/.gitignore index e97bb81..5c30c49 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ out gen vendor/ -bin/ \ No newline at end of file +bin/ +cover.out diff --git a/Makefile b/Makefile index f704ff2..645ec7f 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ CRD_REF_DOCS ?= $(LOCALBIN)/crd-ref-docs GCI ?= $(LOCALBIN)/gci CONTROLLER_TOOLS_VERSION ?= v0.18.0 -CRD_REF_DOCS_VERSION ?= v0.1.0 +CRD_REF_DOCS_VERSION ?= v0.2.0 CHART_DIR ?= charts/qdrant-kubernetes-api CRDS_DIR ?= crds @@ -102,4 +102,4 @@ GOBIN=$(LOCALBIN) go install $${package} ;\ mv $(1) $(1)-$(3) ;\ } ;\ ln -sf $(1)-$(3) $(1) -endef \ No newline at end of file +endef diff --git a/api/v1/qdrantcluster_types.go b/api/v1/qdrantcluster_types.go index 0640e8e..5efacee 100644 --- a/api/v1/qdrantcluster_types.go +++ b/api/v1/qdrantcluster_types.go @@ -55,6 +55,17 @@ 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 { @@ -117,6 +128,10 @@ 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 + // +optional + StorageTier *StorageTier `json:"storageTier,omitempty"` // TopologySpreadConstraints specifies the topology spread constraints for the cluster. // +optional TopologySpreadConstraints *[]corev1.TopologySpreadConstraint `json:"topologySpreadConstraints,omitempty"` diff --git a/api/v1/qdrantcluster_types_test.go b/api/v1/qdrantcluster_types_test.go index 8abe5ba..961a46b 100644 --- a/api/v1/qdrantcluster_types_test.go +++ b/api/v1/qdrantcluster_types_test.go @@ -1,19 +1,92 @@ package v1 import ( + "fmt" "testing" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" ) func TestValidate(t *testing.T) { - r := Resources{} - err := r.Validate("test") - require.Error(t, err) - require.ErrorContains(t, err, "test.CPU error: quantities must match the regular expression '^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$'") + testCases := []struct { + name string + spec QdrantClusterSpec + expectedError error + }{ + { + name: "Storage size is not specified", + spec: QdrantClusterSpec{ + Resources: Resources{ + CPU: "100m", + Memory: "128Mi", + }, + }, + expectedError: fmt.Errorf("Spec.Resources.Storage error: quantities must match the regular expression '^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$'"), + }, + { + name: "Invalid storage size", + spec: QdrantClusterSpec{ + Resources: Resources{ + CPU: "100m", + Memory: "128Mi", + Storage: "foo", + }, + }, + expectedError: fmt.Errorf("Spec.Resources.Storage error: quantities must match the regular expression '^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$'"), + }, + { + name: "CPU amount is not specified", + spec: QdrantClusterSpec{ + Resources: Resources{ + Memory: "128Mi", + Storage: "2Gi", + }, + }, + expectedError: fmt.Errorf("Spec.Resources.CPU error: quantities must match the regular expression '^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$'"), + }, - spec := QdrantClusterSpec{} - err = spec.Validate() - require.Error(t, err) - require.ErrorContains(t, err, "Spec.Resources.CPU error: quantities must match the regular expression '^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$'") + { + name: "Invalid CPU amount", + spec: QdrantClusterSpec{ + Resources: Resources{ + CPU: "foo", + Memory: "128Mi", + Storage: "2Gi", + }, + }, + expectedError: fmt.Errorf("Spec.Resources.CPU error: quantities must match the regular expression '^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$'"), + }, + { + name: "Memory amount is not specified", + spec: QdrantClusterSpec{ + Resources: Resources{ + CPU: "100m", + Storage: "2Gi", + }, + }, + expectedError: fmt.Errorf("Spec.Resources.Memory error: quantities must match the regular expression '^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$'"), + }, + { + name: "Invalid Memory amount", + spec: QdrantClusterSpec{ + Resources: Resources{ + CPU: "100m", + Memory: "foo", + Storage: "2Gi", + }, + }, + expectedError: fmt.Errorf("Spec.Resources.Memory error: quantities must match the regular expression '^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$'"), + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + err := tt.spec.Validate() + if tt.expectedError == nil { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tt.expectedError.Error()) + } + }) + } } diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index ad19dfc..978de84 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -951,6 +951,11 @@ 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.TopologySpreadConstraints != nil { in, out := &in.TopologySpreadConstraints, &out.TopologySpreadConstraints *out = new([]corev1.TopologySpreadConstraint) 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 6a0453f..f2060f6 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 @@ -870,6 +870,19 @@ 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 f6a90f8..908a03c 100644 --- a/crds/qdrant.io_qdrantclusters.yaml +++ b/crds/qdrant.io_qdrantclusters.yaml @@ -869,6 +869,19 @@ 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 b0c84b2..41cb91d 100644 --- a/docs/api.md +++ b/docs/api.md @@ -796,6 +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]
| | `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. | | | @@ -1361,6 +1362,25 @@ _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 a4b328e..7d80c9b 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ 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 ) @@ -67,7 +68,6 @@ 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