diff --git a/api/v1alpha1/dragonfly_types.go b/api/v1alpha1/dragonfly_types.go index 2a969466..55bd5d63 100644 --- a/api/v1alpha1/dragonfly_types.go +++ b/api/v1alpha1/dragonfly_types.go @@ -135,6 +135,9 @@ type DragonflySpec struct { // +kubebuilder:validation:Optional TLSSecretRef *corev1.SecretReference `json:"tlsSecretRef,omitempty"` + // (Optional) Dragonfly SSD Tiering configuration + Tiering *Tiering `json:"tiering,omitempty"` + // (Optional) Dragonfly Snapshot configuration // +optional // +kubebuilder:validation:Optional @@ -183,6 +186,18 @@ type ServiceSpec struct { Labels map[string]string `json:"labels,omitempty"` } +type Tiering struct { + // (Optional) The path to the tiering directory + // +optional + // +kubebuilder:validation:Optional + Dir string `json:"dir,omitempty"` + + // (Optional) Dragonfly PVC spec + // +optional + // +kubebuilder:validation:Optional + PersistentVolumeClaimSpec *corev1.PersistentVolumeClaimSpec `json:"persistentVolumeClaimSpec,omitempty"` +} + type Snapshot struct { // (Optional) The path to the snapshot directory // This can also be an S3 URI with the prefix `s3://` when diff --git a/charts/dragonfly-operator/templates/crds.yaml b/charts/dragonfly-operator/templates/crds.yaml index c7152820..5706b55f 100644 --- a/charts/dragonfly-operator/templates/crds.yaml +++ b/charts/dragonfly-operator/templates/crds.yaml @@ -1534,6 +1534,221 @@ spec: for platforms such as Openshift that require IDs to not be set, as it injects a fixed randomized ID per namespace into all pods. type: boolean + tiering: + description: (Optional) Dragonfly SSD Tiering configuration + properties: + dir: + description: (Optional) The path to the tiering directory. + type: string + persistentVolumeClaimSpec: + description: (Optional) Dragonfly PVC spec + properties: + accessModes: + description: 'accessModes contains the desired access modes + the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + items: + type: string + type: array + dataSource: + description: 'dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) If the provisioner + or an external controller can support the specified data + source, it will create a new volume based on the contents + of the specified data source. When the AnyVolumeDataSource + feature gate is enabled, dataSource contents will be copied + to dataSourceRef, and dataSourceRef contents will be copied + to dataSource when dataSourceRef.namespace is not specified. + If the namespace is specified, then dataSourceRef will not + be copied to dataSource.' + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: 'dataSourceRef specifies the object from which + to populate the volume with data, if a non-empty volume + is desired. This may be any object from a non-empty API + group (non core object) or a PersistentVolumeClaim object. + When this field is specified, volume binding will only succeed + if the type of the specified object matches some installed + volume populator or dynamic provisioner. This field will + replace the functionality of the dataSource field and as + such if both fields are non-empty, they must have the same + value. For backwards compatibility, when namespace isn''t + specified in dataSourceRef, both fields (dataSource and + dataSourceRef) will be set to the same value automatically + if one of them is empty and the other is non-empty. When + namespace is specified in dataSourceRef, dataSource isn''t + set to the same value and must be empty. There are three + important differences between dataSource and dataSourceRef: + * While dataSource only allows two specific types of objects, + dataSourceRef allows any non-core object, as well as PersistentVolumeClaim + objects. * While dataSource ignores disallowed values (dropping + them), dataSourceRef preserves all values, and generates + an error if a disallowed value is specified. * While dataSource + only allows local objects, dataSourceRef allows objects + in any namespaces. (Beta) Using this field requires the + AnyVolumeDataSource feature gate to be enabled. (Alpha) + Using the namespace field of dataSourceRef requires the + CrossNamespaceVolumeDataSource feature gate to be enabled.' + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + namespace: + description: Namespace is the namespace of resource being + referenced Note that when a namespace is specified, + a gateway.networking.k8s.io/ReferenceGrant object is + required in the referent namespace to allow that namespace's + owner to accept the reference. See the ReferenceGrant + documentation for details. (Alpha) This field requires + the CrossNamespaceVolumeDataSource feature gate to be + enabled. + type: string + required: + - kind + - name + type: object + resources: + description: 'resources represents the minimum resources the + volume should have. If RecoverVolumeExpansionFailure feature + is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher + than capacity recorded in the status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. \n This field + is immutable. It can only be set for containers." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry + in pod.spec.resourceClaims of the Pod where this + field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + selector: + description: selector is a label query over volumes to consider + for binding. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists or + DoesNotExist, the values array must be empty. + This array is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + description: 'storageClassName is the name of the StorageClass + required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of volume is required + by the claim. Value of Filesystem is implied when not included + in claim spec. + type: string + volumeName: + description: volumeName is the binding reference to the PersistentVolume + backing this claim. + type: string + type: object + type: object snapshot: description: (Optional) Dragonfly Snapshot configuration properties: diff --git a/go.mod b/go.mod index e2b04ab7..71793d71 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/onsi/gomega v1.33.1 github.com/pkg/errors v0.9.1 github.com/redis/go-redis/v9 v9.5.3 - github.com/samber/lo v1.47.0 k8s.io/api v0.30.2 k8s.io/apimachinery v0.30.2 k8s.io/client-go v0.30.2 diff --git a/go.sum b/go.sum index e602756c..77c6f00e 100644 --- a/go.sum +++ b/go.sum @@ -98,8 +98,6 @@ github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRci github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= -github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/internal/resources/resources.go b/internal/resources/resources.go index 043d4929..af47f171 100644 --- a/internal/resources/resources.go +++ b/internal/resources/resources.go @@ -243,7 +243,6 @@ func GetDragonflyResources(ctx context.Context, df *resourcesv1.Dragonfly) ([]cl }, Spec: *df.Spec.Snapshot.PersistentVolumeClaimSpec, }) - statefulset.Spec.Template.Spec.Containers[0].VolumeMounts = append(statefulset.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ Name: "df", MountPath: dir, @@ -256,6 +255,35 @@ func GetDragonflyResources(ctx context.Context, df *resourcesv1.Dragonfly) ([]cl statefulset.Spec.Template.Spec.Containers[0].Args = append(statefulset.Spec.Template.Spec.Containers[0].Args, fmt.Sprintf("--snapshot_cron=%s", df.Spec.Snapshot.Cron)) } } + if df.Spec.Tiering != nil { + // Doc: https://www.dragonflydb.io/blog/a-preview-of-dragonfly-ssd-tiering + + dir := "/dragonfly/tiering" + if df.Spec.Tiering.Dir != "" { + dir = df.Spec.Tiering.Dir + } + + if df.Spec.Tiering.PersistentVolumeClaimSpec != nil { + // attach and use the PVC if specified + statefulset.Spec.VolumeClaimTemplates = append(statefulset.Spec.VolumeClaimTemplates, corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dft", + Labels: map[string]string{ + "app": df.Name, + KubernetesPartOfLabelKey: "dragonfly", + KubernetesAppNameLabelKey: "dragonfly", + }, + }, + Spec: *df.Spec.Tiering.PersistentVolumeClaimSpec, + }) + statefulset.Spec.Template.Spec.Containers[0].VolumeMounts = append(statefulset.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ + Name: "dft", + MountPath: dir, + }) + } + + statefulset.Spec.Template.Spec.Containers[0].Args = append(statefulset.Spec.Template.Spec.Containers[0].Args, fmt.Sprintf("--tiered_prefix=%s", dir)) + } if df.Spec.TLSSecretRef != nil { statefulset.Spec.Template.Spec.Volumes = append(statefulset.Spec.Template.Spec.Volumes, corev1.Volume{