// Copyright 2018 NetApp, Inc. All Rights Reserved. package common import ( "fmt" hash "github.com/mitchellh/hashstructure" log "github.com/sirupsen/logrus" "github.com/netapp/trident/config" "github.com/netapp/trident/core" "github.com/netapp/trident/storage" sa "github.com/netapp/trident/storage_attribute" "github.com/netapp/trident/storage_class" "github.com/netapp/trident/utils" ) const ( autoStorageClassPrefix = "auto_sc_%d" ) // getStorageClass accepts a list of volume creation options and returns a // matching storage class. If the orchestrator already has a matching // storage class, that is returned; otherwise a new one is created and // registered with the orchestrator. func GetStorageClass(options map[string]string, o core.Orchestrator) (*storageclass.Config, error) { // Create a storage class based on available options newScConfig, err := makeStorageClass(options) if err != nil { return nil, err } // Check existing storage classes for a match based on the name sc, err := o.GetStorageClass(newScConfig.Name) if err != nil && !core.IsNotFoundError(err) { return nil, err } if sc != nil { log.WithField("storageClass", sc.Config.Name).Debug("Matched existing storage class.") return sc.Config, nil } // No match found, so register the new storage class addedSc, err := o.AddStorageClass(newScConfig) if err != nil { log.WithFields(log.Fields{ "storageClass": newScConfig.Name, }).Error("couldn't add the storage class: ", err) return nil, err } return addedSc.Config, nil } // MakeStorageClass accepts a list of volume creation options and creates a // matching storage class. The name of the new storage class contains a hash // of the attributes it contains, thereby enabling comparison of storage // classes generated by this method by simply comparing their names. func makeStorageClass(options map[string]string) (*storageclass.Config, error) { scConfig := new(storageclass.Config) if p, ok := options[sa.StoragePools]; ok { if pools, err := sa.CreateBackendStoragePoolsMapFromEncodedString(p); err != nil { return nil, err } else { scConfig.Pools = pools delete(options, sa.StoragePools) } } if p, ok := options[sa.AdditionalStoragePools]; ok { if additionalPools, err := sa.CreateBackendStoragePoolsMapFromEncodedString(p); err != nil { return nil, err } else { scConfig.AdditionalPools = additionalPools delete(options, sa.AdditionalStoragePools) } } // Map options to storage class attributes scConfig.Attributes = make(map[string]sa.Request) for k, v := range options { // format: attribute: "type:value" req, err := sa.CreateAttributeRequestFromAttributeValue(k, v) if err != nil { log.WithFields(log.Fields{ "storageClass": scConfig.Name, "storageClass_parameters": options, }).Debugf("Frontend ignoring storage class attribute: %v", err) continue } scConfig.Attributes[k] = req } // Set name based on hash value scHash, err := hash.Hash(scConfig, nil) if err != nil { log.WithFields(log.Fields{ "storageClass": scConfig.Name, "storageClass_parameters": options, }).Errorf("Frontend couldn't hash the storage class attributes: %v", err) return nil, err } scConfig.Name = fmt.Sprintf(autoStorageClassPrefix, scHash) return scConfig, nil } // GetVolumeConfig accepts a set of parameters describing a volume creation request // and returns a volume config structure suitable for passing to the orchestrator core. func GetVolumeConfig( name, storageClass string, sizeBytes int64, opts map[string]string, protocol config.Protocol, accessMode config.AccessMode, volumeMode config.VolumeMode, ) (*storage.VolumeConfig, error) { return &storage.VolumeConfig{ Name: name, Size: fmt.Sprintf("%d", sizeBytes), StorageClass: storageClass, Protocol: protocol, AccessMode: accessMode, VolumeMode: volumeMode, SpaceReserve: utils.GetV(opts, "spaceReserve", ""), SecurityStyle: utils.GetV(opts, "securityStyle", ""), SplitOnClone: utils.GetV(opts, "splitOnClone", ""), SnapshotPolicy: utils.GetV(opts, "snapshotPolicy", ""), SnapshotReserve: utils.GetV(opts, "snapshotReserve", ""), SnapshotDir: utils.GetV(opts, "snapshotDir", ""), ExportPolicy: utils.GetV(opts, "exportPolicy", ""), UnixPermissions: utils.GetV(opts, "unixPermissions", ""), BlockSize: utils.GetV(opts, "blocksize", ""), QoS: utils.GetV(opts, "qos", ""), QoSType: utils.GetV(opts, "type", ""), FileSystem: utils.GetV(opts, "fstype|fileSystemType", ""), Encryption: utils.GetV(opts, "encryption", ""), CloneSourceVolume: utils.GetV(opts, "from", ""), CloneSourceSnapshot: utils.GetV(opts, "fromSnapshot", ""), ServiceLevel: utils.GetV(opts, "serviceLevel", ""), Network: utils.GetV(opts, "network", ""), }, nil } func CombineAccessModes(accessModes []config.AccessMode) config.AccessMode { volConfigAccessMode := config.ModeAny for _, accessMode := range accessModes { // Rules for combining multiple access modes into a single access mode: // Current AccessMode Next AccessMode Result // Any Any Any // Any ReadWriteOnce ReadWriteOnce // Any ReadOnlyMany ReadOnlyMany // Any ReadWriteMany ReadWriteMany // ReadWriteOnce Any ReadWriteOnce // ReadWriteOnce ReadWriteOnce ReadWriteOnce // ReadWriteOnce ReadOnlyMany ReadWriteMany // ReadWriteOnce ReadWriteMany ReadWriteMany // ReadOnlyMany Any ReadOnlyMany // ReadOnlyMany ReadWriteOnce ReadWriteMany // ReadOnlyMany ReadOnlyMany ReadOnlyMany // ReadOnlyMany ReadWriteMany ReadWriteMany // ReadWriteMany Any ReadWriteMany // ReadWriteMany ReadWriteOnce ReadWriteMany // ReadWriteMany ReadOnlyMany ReadWriteMany // ReadWriteMany ReadWriteMany ReadWriteMany if volConfigAccessMode == config.ModeAny { volConfigAccessMode = accessMode } else if volConfigAccessMode == config.ReadWriteOnce { if accessMode == config.ReadOnlyMany || accessMode == config.ReadWriteMany { volConfigAccessMode = config.ReadWriteMany } } else if volConfigAccessMode == config.ReadOnlyMany { if accessMode == config.ReadWriteOnce || accessMode == config.ReadWriteMany { volConfigAccessMode = config.ReadWriteMany } } } return volConfigAccessMode }