storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/config/storageclass/storage-class.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2019 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package storageclass
    18  
    19  import (
    20  	"encoding/json"
    21  	"errors"
    22  	"fmt"
    23  	"strconv"
    24  	"strings"
    25  	"sync"
    26  
    27  	"storj.io/minio/cmd/config"
    28  	"storj.io/minio/pkg/env"
    29  )
    30  
    31  // Standard constants for all storage class
    32  const (
    33  	// Reduced redundancy storage class
    34  	RRS = "REDUCED_REDUNDANCY"
    35  	// Standard storage class
    36  	STANDARD = "STANDARD"
    37  	// DMA storage class
    38  	DMA = "DMA"
    39  
    40  	// Valid values are "write" and "read+write"
    41  	DMAWrite     = "write"
    42  	DMAReadWrite = "read+write"
    43  )
    44  
    45  // Standard constats for config info storage class
    46  const (
    47  	ClassStandard = "standard"
    48  	ClassRRS      = "rrs"
    49  	ClassDMA      = "dma"
    50  
    51  	// Reduced redundancy storage class environment variable
    52  	RRSEnv = "MINIO_STORAGE_CLASS_RRS"
    53  	// Standard storage class environment variable
    54  	StandardEnv = "MINIO_STORAGE_CLASS_STANDARD"
    55  	// DMA storage class environment variable
    56  	DMAEnv = "MINIO_STORAGE_CLASS_DMA"
    57  
    58  	// Supported storage class scheme is EC
    59  	schemePrefix = "EC"
    60  
    61  	// Min parity disks
    62  	minParityDisks = 2
    63  
    64  	// Default RRS parity is always minimum parity.
    65  	defaultRRSParity = minParityDisks
    66  
    67  	// Default DMA value
    68  	defaultDMA = DMAReadWrite
    69  )
    70  
    71  // DefaultKVS - default storage class config
    72  var (
    73  	DefaultKVS = config.KVS{
    74  		config.KV{
    75  			Key:   ClassStandard,
    76  			Value: "",
    77  		},
    78  		config.KV{
    79  			Key:   ClassRRS,
    80  			Value: "EC:2",
    81  		},
    82  		config.KV{
    83  			Key:   ClassDMA,
    84  			Value: defaultDMA,
    85  		},
    86  	}
    87  )
    88  
    89  // StorageClass - holds storage class information
    90  type StorageClass struct {
    91  	Parity int
    92  }
    93  
    94  // ConfigLock is a global lock for storage-class config
    95  var ConfigLock = sync.RWMutex{}
    96  
    97  // Config storage class configuration
    98  type Config struct {
    99  	Standard StorageClass `json:"standard"`
   100  	RRS      StorageClass `json:"rrs"`
   101  	DMA      string       `json:"dma"`
   102  }
   103  
   104  // UnmarshalJSON - Validate SS and RRS parity when unmarshalling JSON.
   105  func (sCfg *Config) UnmarshalJSON(data []byte) error {
   106  	type Alias Config
   107  	aux := &struct {
   108  		*Alias
   109  	}{
   110  		Alias: (*Alias)(sCfg),
   111  	}
   112  	return json.Unmarshal(data, &aux)
   113  }
   114  
   115  // IsValid - returns true if input string is a valid
   116  // storage class kind supported.
   117  func IsValid(sc string) bool {
   118  	return sc == RRS || sc == STANDARD
   119  }
   120  
   121  // UnmarshalText unmarshals storage class from its textual form into
   122  // storageClass structure.
   123  func (sc *StorageClass) UnmarshalText(b []byte) error {
   124  	scStr := string(b)
   125  	if scStr == "" {
   126  		return nil
   127  	}
   128  	s, err := parseStorageClass(scStr)
   129  	if err != nil {
   130  		return err
   131  	}
   132  	sc.Parity = s.Parity
   133  	return nil
   134  }
   135  
   136  // MarshalText - marshals storage class string.
   137  func (sc *StorageClass) MarshalText() ([]byte, error) {
   138  	if sc.Parity != 0 {
   139  		return []byte(fmt.Sprintf("%s:%d", schemePrefix, sc.Parity)), nil
   140  	}
   141  	return []byte{}, nil
   142  }
   143  
   144  func (sc *StorageClass) String() string {
   145  	if sc.Parity != 0 {
   146  		return fmt.Sprintf("%s:%d", schemePrefix, sc.Parity)
   147  	}
   148  	return ""
   149  }
   150  
   151  // Parses given storageClassEnv and returns a storageClass structure.
   152  // Supported Storage Class format is "Scheme:Number of parity disks".
   153  // Currently only supported scheme is "EC".
   154  func parseStorageClass(storageClassEnv string) (sc StorageClass, err error) {
   155  	s := strings.Split(storageClassEnv, ":")
   156  
   157  	// only two elements allowed in the string - "scheme" and "number of parity disks"
   158  	if len(s) > 2 {
   159  		return StorageClass{}, config.ErrStorageClassValue(nil).Msg("Too many sections in " + storageClassEnv)
   160  	} else if len(s) < 2 {
   161  		return StorageClass{}, config.ErrStorageClassValue(nil).Msg("Too few sections in " + storageClassEnv)
   162  	}
   163  
   164  	// only allowed scheme is "EC"
   165  	if s[0] != schemePrefix {
   166  		return StorageClass{}, config.ErrStorageClassValue(nil).Msg("Unsupported scheme " + s[0] + ". Supported scheme is EC")
   167  	}
   168  
   169  	// Number of parity disks should be integer
   170  	parityDisks, err := strconv.Atoi(s[1])
   171  	if err != nil {
   172  		return StorageClass{}, config.ErrStorageClassValue(err)
   173  	}
   174  
   175  	return StorageClass{
   176  		Parity: parityDisks,
   177  	}, nil
   178  }
   179  
   180  // ValidateParity validate standard storage class parity.
   181  func ValidateParity(ssParity, setDriveCount int) error {
   182  	// SS parity disks should be greater than or equal to minParityDisks.
   183  	// Parity below minParityDisks is not supported.
   184  	if ssParity > 0 && ssParity < minParityDisks {
   185  		return fmt.Errorf("Standard storage class parity %d should be greater than or equal to %d",
   186  			ssParity, minParityDisks)
   187  	}
   188  
   189  	if ssParity > setDriveCount/2 {
   190  		return fmt.Errorf("Standard storage class parity %d should be less than or equal to %d", ssParity, setDriveCount/2)
   191  	}
   192  
   193  	return nil
   194  }
   195  
   196  // Validates the parity disks.
   197  func validateParity(ssParity, rrsParity, setDriveCount int) (err error) {
   198  	// SS parity disks should be greater than or equal to minParityDisks.
   199  	// Parity below minParityDisks is not supported.
   200  	if ssParity > 0 && ssParity < minParityDisks {
   201  		return fmt.Errorf("Standard storage class parity %d should be greater than or equal to %d",
   202  			ssParity, minParityDisks)
   203  	}
   204  
   205  	// RRS parity disks should be greater than or equal to minParityDisks.
   206  	// Parity below minParityDisks is not supported.
   207  	if rrsParity > 0 && rrsParity < minParityDisks {
   208  		return fmt.Errorf("Reduced redundancy storage class parity %d should be greater than or equal to %d", rrsParity, minParityDisks)
   209  	}
   210  
   211  	if ssParity > setDriveCount/2 {
   212  		return fmt.Errorf("Standard storage class parity %d should be less than or equal to %d", ssParity, setDriveCount/2)
   213  	}
   214  
   215  	if rrsParity > setDriveCount/2 {
   216  		return fmt.Errorf("Reduced redundancy storage class parity %d should be less than  or equal to %d", rrsParity, setDriveCount/2)
   217  	}
   218  
   219  	if ssParity > 0 && rrsParity > 0 {
   220  		if ssParity > 0 && ssParity < rrsParity {
   221  			return fmt.Errorf("Standard storage class parity disks %d should be greater than or equal to Reduced redundancy storage class parity disks %d", ssParity, rrsParity)
   222  		}
   223  	}
   224  	return nil
   225  }
   226  
   227  // GetParityForSC - Returns the data and parity drive count based on storage class
   228  // If storage class is set using the env vars MINIO_STORAGE_CLASS_RRS and
   229  // MINIO_STORAGE_CLASS_STANDARD or server config fields corresponding values are
   230  // returned.
   231  //
   232  // -- if input storage class is empty then standard is assumed
   233  // -- if input is RRS but RRS is not configured default '2' parity
   234  //    for RRS is assumed
   235  // -- if input is STANDARD but STANDARD is not configured '0' parity
   236  //    is returned, the caller is expected to choose the right parity
   237  //    at that point.
   238  func (sCfg Config) GetParityForSC(sc string) (parity int) {
   239  	ConfigLock.RLock()
   240  	defer ConfigLock.RUnlock()
   241  	switch strings.TrimSpace(sc) {
   242  	case RRS:
   243  		// set the rrs parity if available
   244  		if sCfg.RRS.Parity == 0 {
   245  			return defaultRRSParity
   246  		}
   247  		return sCfg.RRS.Parity
   248  	default:
   249  		return sCfg.Standard.Parity
   250  	}
   251  }
   252  
   253  // Update update storage-class with new config
   254  func (sCfg Config) Update(newCfg Config) {
   255  	ConfigLock.Lock()
   256  	defer ConfigLock.Unlock()
   257  	sCfg.RRS = newCfg.RRS
   258  	sCfg.DMA = newCfg.DMA
   259  	sCfg.Standard = newCfg.Standard
   260  }
   261  
   262  // GetDMA - returns DMA configuration.
   263  func (sCfg Config) GetDMA() string {
   264  	ConfigLock.RLock()
   265  	defer ConfigLock.RUnlock()
   266  	return sCfg.DMA
   267  }
   268  
   269  // Enabled returns if etcd is enabled.
   270  func Enabled(kvs config.KVS) bool {
   271  	ssc := kvs.Get(ClassStandard)
   272  	rrsc := kvs.Get(ClassRRS)
   273  	return ssc != "" || rrsc != ""
   274  }
   275  
   276  // LookupConfig - lookup storage class config and override with valid environment settings if any.
   277  func LookupConfig(kvs config.KVS, setDriveCount int) (cfg Config, err error) {
   278  	cfg = Config{}
   279  
   280  	if err = config.CheckValidKeys(config.StorageClassSubSys, kvs, DefaultKVS); err != nil {
   281  		return Config{}, err
   282  	}
   283  
   284  	ssc := env.Get(StandardEnv, kvs.Get(ClassStandard))
   285  	rrsc := env.Get(RRSEnv, kvs.Get(ClassRRS))
   286  	dma := env.Get(DMAEnv, kvs.Get(ClassDMA))
   287  	// Check for environment variables and parse into storageClass struct
   288  	if ssc != "" {
   289  		cfg.Standard, err = parseStorageClass(ssc)
   290  		if err != nil {
   291  			return Config{}, err
   292  		}
   293  	}
   294  
   295  	if rrsc != "" {
   296  		cfg.RRS, err = parseStorageClass(rrsc)
   297  		if err != nil {
   298  			return Config{}, err
   299  		}
   300  	}
   301  	if cfg.RRS.Parity == 0 {
   302  		cfg.RRS.Parity = defaultRRSParity
   303  	}
   304  
   305  	if dma == "" {
   306  		dma = defaultDMA
   307  	}
   308  	if dma != DMAReadWrite && dma != DMAWrite {
   309  		return Config{}, errors.New(`valid dma values are "read-write" and "write"`)
   310  	}
   311  	cfg.DMA = dma
   312  
   313  	// Validation is done after parsing both the storage classes. This is needed because we need one
   314  	// storage class value to deduce the correct value of the other storage class.
   315  	if err = validateParity(cfg.Standard.Parity, cfg.RRS.Parity, setDriveCount); err != nil {
   316  		return Config{}, err
   317  	}
   318  
   319  	return cfg, nil
   320  }