Skip to content

Commit

Permalink
statefulset: added support for template PersistentVolumeClaims.
Browse files Browse the repository at this point in the history
  • Loading branch information
hugosantos committed Jul 25, 2023
1 parent bab4fde commit 437d47e
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 75 deletions.
2 changes: 2 additions & 0 deletions internal/frontend/cuefrontend/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type cueRequiredStorage struct {
ByteCount string `json:"byteCount"`
MountPath string `json:"mountPath"`
PersistentID string `json:"persistentId"`
Template bool `json:"template"`
}

type cueProvides struct {
Expand Down Expand Up @@ -340,6 +341,7 @@ func parseCueNode(ctx context.Context, env *schema.Environment, pl parsing.Early
pv, err := anypb.New(&schema.PersistentVolume{
Id: d.PersistentID,
SizeBytes: uint64(v),
Template: d.Template,
})
if err != nil {
return fnerrors.NewWithLocation(loc, "failed to marshal persistent volume: %w", err)
Expand Down
6 changes: 4 additions & 2 deletions internal/frontend/cuefrontend/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ type cueEphemeralVolume struct {
}

type cuePersistentVolume struct {
Id string `json:"id"`
Size string `json:"size"`
Id string `json:"id"`
Size string `json:"size"`
Template bool `json:"template"`
}

type cueWorkspaceSyncVolume struct {
Expand Down Expand Up @@ -150,6 +151,7 @@ func parseVolume(ctx context.Context, pl parsing.EarlyPackageLoader, loc pkggrap
definition = &schema.PersistentVolume{
Id: bits.Id,
SizeBytes: uint64(sizeBytes),
Template: bits.Template,
}

case constants.VolumeKindHostPath:
Expand Down
30 changes: 27 additions & 3 deletions internal/runtime/kubernetes/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"strings"
"time"

"github.com/dustin/go-humanize"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"golang.org/x/exp/slices"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -466,6 +467,8 @@ func prepareDeployment(ctx context.Context, target BoundNamespace, deployable ru
volumes := deployable.Volumes
mounts := deployable.MainContainer.Mounts

var volumeTemplates []*applycorev1.PersistentVolumeClaimApplyConfiguration

volumeDefs := map[string]*volumeDef{}
for k, volume := range volumes {
if volume.Name == "" {
Expand All @@ -488,6 +491,7 @@ func prepareDeployment(ctx context.Context, target BoundNamespace, deployable ru
quantity := resource.NewScaledQuantity(int64(ev.SizeBytes), 0)
emptydir = emptydir.WithSizeLimit(*quantity)
}

spec = spec.WithVolumes(applycorev1.Volume().WithName(name).WithEmptyDir(emptydir))

case constants.VolumeKindHostPath:
Expand All @@ -509,17 +513,36 @@ func prepareDeployment(ctx context.Context, target BoundNamespace, deployable ru
return fnerrors.InternalError("%s: failed to unmarshal persistent volume definition: %w", volume.Name, err)
}

if pv.Template {
if deployable.Class != schema.DeployableClass_STATEFUL {
return fnerrors.InternalError("volume %q is a template, but the server is not stateful", volume.Name)
}
}

if pv.Id == "" {
return fnerrors.BadInputError("%s: persistent ID is missing", volume.Name)
}

v, operations, err := makePersistentVolume(target.namespace, target.env, deployable.ErrorLocation, volume.Owner, name, pv.Id, pv.SizeBytes, annotations)
v, pvc, err := makePersistentVolume(target.namespace, target.env, deployable.ErrorLocation, volume.Owner, name, pv.Id, pv.SizeBytes, pv.Template, annotations)
if err != nil {
return err
}

spec = spec.WithVolumes(v)
s.operations = append(s.operations, operations...)
if v != nil {
spec = spec.WithVolumes(v)
}

if pvc != nil {
if pv.Template {
volumeTemplates = append(volumeTemplates, pvc)
} else {
// spec = spec.WithVolumes(v)
s.operations = append(s.operations, kubedef.Apply{
Description: fmt.Sprintf("Persistent storage for %s (%s)", volume.Owner, humanize.Bytes(pv.SizeBytes)),
Resource: pvc,
})
}
}

case constants.VolumeKindWorkspaceSync:
volumeDef.isWorkspaceSync = true
Expand Down Expand Up @@ -928,6 +951,7 @@ func prepareDeployment(ctx context.Context, target BoundNamespace, deployable ru
WithReplicas(replicas).
WithRevisionHistoryLimit(revisionHistoryLimit).
WithTemplate(tmpl).
WithVolumeClaimTemplates(volumeTemplates...).
WithSelector(applymetav1.LabelSelector().WithMatchLabels(kubedef.SelectById(deployable))))
if deployable.ConfigImage != nil {
statefulSet.WithAnnotations(map[string]string{
Expand Down
45 changes: 23 additions & 22 deletions internal/runtime/kubernetes/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
package kubernetes

import (
"fmt"
"math"

"github.com/dustin/go-humanize"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
applycorev1 "k8s.io/client-go/applyconfigurations/core/v1"
Expand All @@ -24,7 +22,7 @@ type volumeDef struct {
isWorkspaceSync bool
}

func makePersistentVolume(ns string, env *schema.Environment, loc fnerrors.Location, owner, name, persistentId string, sizeBytes uint64, annotations map[string]string) (*applycorev1.VolumeApplyConfiguration, definitions, error) {
func makePersistentVolume(ns string, env *schema.Environment, loc fnerrors.Location, owner, name, persistentId string, sizeBytes uint64, template bool, annotations map[string]string) (*applycorev1.VolumeApplyConfiguration, *applycorev1.PersistentVolumeClaimApplyConfiguration, error) {
if sizeBytes >= math.MaxInt64 {
return nil, nil, fnerrors.NewWithLocation(loc, "requiredstorage value too high (maximum is %d)", math.MaxInt64)
}
Expand All @@ -33,35 +31,38 @@ func makePersistentVolume(ns string, env *schema.Environment, loc fnerrors.Locat

// Ephemeral environments are short lived, so there is no need for persistent volume claims.
// Admin servers are excluded here as they run as singletons in a global namespace.
var operations definitions
var v *applycorev1.VolumeApplyConfiguration

if env.GetEphemeral() {
v = applycorev1.Volume().
return applycorev1.Volume().
WithName(name).
WithEmptyDir(applycorev1.EmptyDirVolumeSource().
WithSizeLimit(*quantity))
WithSizeLimit(*quantity)), nil, nil
} else if template {
return nil, applycorev1.PersistentVolumeClaim(name, ns).
WithLabels(kubedef.ManagedByUs()).
WithAnnotations(annotations).
WithSpec(applycorev1.PersistentVolumeClaimSpec().
WithAccessModes(corev1.ReadWriteOnce).
WithResources(applycorev1.ResourceRequirements().WithRequests(corev1.ResourceList{
corev1.ResourceStorage: *quantity,
}))), nil
} else {
v = applycorev1.Volume().
v := applycorev1.Volume().
WithName(name).
WithPersistentVolumeClaim(
applycorev1.PersistentVolumeClaimVolumeSource().
WithClaimName(persistentId))

operations = append(operations, kubedef.Apply{
Description: fmt.Sprintf("Persistent storage for %s (%s)", owner, humanize.Bytes(sizeBytes)),
Resource: applycorev1.PersistentVolumeClaim(persistentId, ns).
WithLabels(kubedef.ManagedByUs()).
WithAnnotations(annotations).
WithSpec(applycorev1.PersistentVolumeClaimSpec().
WithAccessModes(corev1.ReadWriteOnce).
WithResources(applycorev1.ResourceRequirements().WithRequests(corev1.ResourceList{
corev1.ResourceStorage: *quantity,
}))),
})
}
pvc := applycorev1.PersistentVolumeClaim(persistentId, ns).
WithLabels(kubedef.ManagedByUs()).
WithAnnotations(annotations).
WithSpec(applycorev1.PersistentVolumeClaimSpec().
WithAccessModes(corev1.ReadWriteOnce).
WithResources(applycorev1.ResourceRequirements().WithRequests(corev1.ResourceList{
corev1.ResourceStorage: *quantity,
})))

return v, operations, nil
return v, pvc, nil
}
}

func toK8sVol(vol *kubedef.SpecExtension_Volume) (*applycorev1.VolumeApplyConfiguration, error) {
Expand Down
51 changes: 51 additions & 0 deletions internal/testdata/server/statefulminio/server.cue
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
server: {
name: "minio-server"

image: "minio/minio@sha256:de46799fc1ced82b784554ba4602b677a71966148b77f5028132fc50adf37b1f"

// MinIO acts as an object store which requires a stateful deployment (more conservative update strategy).
class: "stateful"

env: {
// Disable update checking as self-update will never be used.
MINIO_UPDATE: "off"

MINIO_ROOT_USER: fromSecret: ":user"
MINIO_ROOT_PASSWORD: fromSecret: ":password"
}

args: [
"server",
"/minio",
"--address=:9000",
"--console-address=:9001",
]

mounts: {
"/minio": persistent: {
// Unique volume identifier
id: "minio-server-data"
size: "10GiB"
template: true
}
}
}

secrets: {
"user": {
description: "Minio root user"
generate: {
uniqueId: "minio-user"
randomByteCount: 32
format: "FORMAT_BASE32"
}
}
"password": {
description: "Minio root password"
generate: {
uniqueId: "minio-password"
randomByteCount: 32
format: "FORMAT_BASE32"
}
}
}
2 changes: 1 addition & 1 deletion internal/versions/versions.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"api_version": 86,
"api_version": 87,
"minimum_api_version": 40,
"cache_version": 1
}
104 changes: 57 additions & 47 deletions schema/volume.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions schema/volume.proto
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ message EphemeralVolume {
message PersistentVolume {
string id = 1;
uint64 size_bytes = 2;
bool template = 3;
}

message HostPathVolume {
Expand Down

0 comments on commit 437d47e

Please sign in to comment.