diff --git a/sdl/storage.go b/sdl/storage.go index 611b1f8cda..a591b4b347 100644 --- a/sdl/storage.go +++ b/sdl/storage.go @@ -17,6 +17,7 @@ const ( StorageAttributeMount = "mount" StorageAttributeReadOnly = "readOnly" // we might not need it at this point of time StorageClassDefault = "default" + StorageClassRAM = "ram" ) var ( @@ -25,6 +26,7 @@ var ( errStorageMultipleRootEphemeral = errors.New("sdl: multiple root ephemeral storages are not allowed") errStorageDuplicatedVolumeName = errors.New("sdl: duplicated volume name") errStorageEphemeralClass = errors.New("sdl: ephemeral storage should not set attribute class") + errStorageRAMClass = errors.New("sdl: ram storage class cannot be persistent") ) type v2StorageAttributes types.Attributes @@ -45,10 +47,11 @@ type v2ResourceStorageArray []v2ResourceStorage type validateAttrFn func(string, *string) error var allowedStorageClasses = map[string]bool{ - "default": true, - "beta1": true, - "beta2": true, - "beta3": true, + "default": true, + "beta1": true, + "beta2": true, + "beta3": true, + StorageClassRAM: true, } var validateStorageAttributes = map[string]validateAttrFn{ @@ -162,11 +165,20 @@ func (sdl *v2StorageAttributes) UnmarshalYAML(node *yaml.Node) error { persistent := res[StorageAttributePersistent] class := res[StorageAttributeClass] - if persistent == valueFalse && class != "" { - return errStorageEphemeralClass - } - if persistent == valueTrue && class == "" { - res[StorageAttributeClass] = StorageClassDefault + + switch class { + case "": + if persistent == valueTrue { + res[StorageAttributeClass] = StorageClassDefault + } + case StorageClassRAM: + if persistent != valueFalse { + return errStorageRAMClass + } + default: + if persistent == valueFalse { + return errStorageEphemeralClass + } } for k, v := range res { diff --git a/sdl/storage_test.go b/sdl/storage_test.go index b7bcf2fb98..b60c35f2d9 100644 --- a/sdl/storage_test.go +++ b/sdl/storage_test.go @@ -140,6 +140,38 @@ func TestStorage_PersistentClass(t *testing.T) { require.Equal(t, p[0].Attributes[0].Value, "beta1") } +func TestStorage_RAMClass_Valid(t *testing.T) { + var stream = ` +- size: 1Gi + attributes: + persistent: false + class: ram +` + + var p v2ResourceStorageArray + + err := yaml.Unmarshal([]byte(stream), &p) + require.NoError(t, err) + require.Len(t, p[0].Attributes, 2) + + require.Equal(t, p[0].Attributes[0].Key, "class") + require.Equal(t, p[0].Attributes[0].Value, "ram") +} + +func TestStorage_RamClass_Invalid(t *testing.T) { + var stream = ` +- size: 1Gi + attributes: + persistent: true + class: ram +` + + var p v2ResourceStorageArray + + err := yaml.Unmarshal([]byte(stream), &p) + require.Error(t, err) +} + func TestStorage_StableSort(t *testing.T) { storage := v2ResourceStorageArray{ { diff --git a/sdl/v2_1.go b/sdl/v2_1.go index 79dc9db6f5..33a5b0e68a 100644 --- a/sdl/v2_1.go +++ b/sdl/v2_1.go @@ -228,12 +228,14 @@ func (sdl *v2_1) validate() error { } } - attr := make(map[string]string) - mounts := make(map[string]string) - if svc.Params != nil { + mounts := make(map[string]string) + for name, params := range svc.Params.Storage { - if _, exists := volumes[name]; !exists { + + volume, exists := volumes[name] + + if !exists { return fmt.Errorf( "%w: service \"%s\" references to no-existing compute volume named \"%s\"", errSDLInvalid, @@ -251,42 +253,51 @@ func (sdl *v2_1) validate() error { ) } - attr[StorageAttributeMount] = params.Mount - attr[StorageAttributeReadOnly] = strconv.FormatBool(params.ReadOnly) - - mount := attr[StorageAttributeMount] - if vlname, exists := mounts[mount]; exists { - if mount == "" { + if vlname, exists := mounts[params.Mount]; exists { + if params.Mount == "" { return errStorageMultipleRootEphemeral } return fmt.Errorf( "%w: mount %q already in use by volume %q", errStorageDupMountPoint, - mount, + params.Mount, vlname, ) } - mounts[mount] = name - } - } + mounts[params.Mount] = name - for name, volume := range volumes { - for _, nd := range types.Attributes(volume.Attributes) { - attr[nd.Key] = nd.Value - } + attr := make(map[string]string) + attr[StorageAttributeMount] = params.Mount + attr[StorageAttributeReadOnly] = strconv.FormatBool(params.ReadOnly) + + for _, nd := range types.Attributes(volume.Attributes) { + attr[nd.Key] = nd.Value + } - persistent, _ := strconv.ParseBool(attr[StorageAttributePersistent]) + persistent, _ := strconv.ParseBool(attr[StorageAttributePersistent]) + class := attr[StorageAttributeClass] - if persistent && attr[StorageAttributeMount] == "" { - return fmt.Errorf( - "%w: compute.storage.%s has persistent=true which requires service.%s.params.storage.%s to have mount", - errSDLInvalid, - name, - svcName, - name, - ) + if persistent && params.Mount == "" { + return fmt.Errorf( + "%w: compute.storage.%s has persistent=true which requires service.%s.params.storage.%s to have mount", + errSDLInvalid, + name, + svcName, + name, + ) + } + + if class == StorageClassRAM && params.ReadOnly { + return fmt.Errorf( + "%w: services.%s.params.storage.%s has readOnly=true which is not allowed for storage class \"%s\"", + errSDLInvalid, + svcName, + name, + class, + ) + } } } }