Skip to content

Commit

Permalink
Migrate config.json from config-dir to backend
Browse files Browse the repository at this point in the history
This PR is a first set of changes to move the config
to the backend, the changes uses the existing `config.json`
allows it to be migrated such that we can save it in on
backend disks.

In future releases we will slowly migrate out of the
current architecture.

Fixes minio#6182
  • Loading branch information
harshavardhana committed Jul 24, 2018
1 parent 157ed65 commit a971361
Show file tree
Hide file tree
Showing 12 changed files with 360 additions and 95 deletions.
22 changes: 19 additions & 3 deletions cmd/admin-handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"io"
"net/http"
"net/url"
"path"
"sync"
"time"

Expand Down Expand Up @@ -823,13 +824,28 @@ func (a adminAPIHandlers) UpdateCredentialsHandler(w http.ResponseWriter,

// Update local credentials in memory.
globalServerConfig.SetCredential(creds)
if err = globalServerConfig.Save(getConfigFile()); err != nil {
writeErrorResponseJSON(w, ErrInternalError, r.URL)

// Construct path to config.json for the given bucket.
configFile := path.Join(bucketConfigPrefix, minioConfigFile)
transactionConfigFile := configFile + ".transaction"

// As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket
// and configFile, take a transaction lock to avoid race.
objLock := globalNSMutex.NewNSLock(minioMetaBucket, transactionConfigFile)
if err := objLock.GetLock(globalOperationTimeout); err != nil {
writeErrorResponseJSON(w, toAdminAPIErrCode(err), r.URL)
return
}

if err = saveServerConfig(newObjectLayerFn(), globalServerConfig); err != nil {
objLock.Unlock()
writeErrorResponseJSON(w, toAdminAPIErrCode(err), r.URL)
return
}
objLock.Unlock()

// Notify all other Minio peers to update credentials
for host, err := range globalNotificationSys.SetCredentials(creds) {
for host, err := range globalNotificationSys.LoadCredentials() {
reqInfo := (&logger.ReqInfo{}).AppendTags("peerAddress", host.String())
ctx := logger.SetReqInfo(context.Background(), reqInfo)
logger.LogIf(ctx, err)
Expand Down
15 changes: 0 additions & 15 deletions cmd/config-current.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,21 +208,6 @@ func (s *serverConfig) Validate() error {
return nil
}

// Save config file to corresponding backend
func Save(configFile string, data interface{}) error {
return quick.SaveConfig(data, configFile, globalEtcdClient)
}

// Load config from backend
func Load(configFile string, data interface{}) (quick.Config, error) {
return quick.LoadConfig(configFile, globalEtcdClient, data)
}

// GetVersion gets config version from backend
func GetVersion(configFile string) (string, error) {
return quick.GetVersion(configFile, globalEtcdClient)
}

// Returns the string describing a difference with the given
// configuration object. If the given configuration object is
// identical, an empty string is returned.
Expand Down
3 changes: 0 additions & 3 deletions cmd/config-dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ const (
// Default minio configuration directory where below configuration files/directories are stored.
defaultMinioConfigDir = ".minio"

// Minio configuration file.
minioConfigFile = "config.json"

// Directory contains below files/directories for HTTPS configuration.
certsDir = "certs"

Expand Down
15 changes: 15 additions & 0 deletions cmd/config-migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@ import (
// DO NOT EDIT following message template, please open a github issue to discuss instead.
var configMigrateMSGTemplate = "Configuration file %s migrated from version '%s' to '%s' successfully."

// Save config file to corresponding backend
func Save(configFile string, data interface{}) error {
return quick.SaveConfig(data, configFile, globalEtcdClient)
}

// Load config from backend
func Load(configFile string, data interface{}) (quick.Config, error) {
return quick.LoadConfig(configFile, globalEtcdClient, data)
}

// GetVersion gets config version from backend
func GetVersion(configFile string) (string, error) {
return quick.GetVersion(configFile, globalEtcdClient)
}

// Migrates all config versions from "1" to "18".
func migrateConfig() error {
// Purge all configs with version '1',
Expand Down
281 changes: 281 additions & 0 deletions cmd/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
/*
* Minio Cloud Storage, (C) 2018 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package cmd

import (
"bytes"
"context"
"encoding/json"
"errors"
"io/ioutil"
"path"

"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/quick"
)

const (
minioConfigPrefix = "config"

// Minio configuration file.
minioConfigFile = "config.json"
)

func saveServerConfig(objAPI ObjectLayer, config *serverConfig) error {
if err := quick.CheckData(config); err != nil {
return err
}

data, err := json.Marshal(config)
if err != nil {
return err
}

configFile := path.Join(minioConfigPrefix, minioConfigFile)
return saveConfig(objAPI, configFile, data)
}

func readServerConfig(ctx context.Context, objAPI ObjectLayer) (*serverConfig, error) {
configFile := path.Join(minioConfigPrefix, minioConfigFile)
reader, err := readConfig(ctx, objAPI, configFile)
if err != nil {
return nil, err
}
var config = &serverConfig{}
d := json.NewDecoder(reader)
if err = d.Decode(config); err != nil {
return nil, err
}
if err = quick.CheckData(config); err != nil {
return nil, err
}
return config, nil
}

func checkServerConfig(ctx context.Context, objAPI ObjectLayer) error {
configFile := path.Join(minioConfigPrefix, minioConfigFile)
if _, err := objAPI.GetObjectInfo(ctx, minioMetaBucket, configFile); err != nil {
if isErrObjectNotFound(err) {
return errConfigNotFound
}
logger.GetReqInfo(ctx).AppendTags("configFile", configFile)
logger.LogIf(ctx, err)
return err
}
return nil
}

func saveConfig(objAPI ObjectLayer, configFile string, data []byte) error {
hashReader, err := hash.NewReader(bytes.NewReader(data), int64(len(data)), "", getSHA256Hash(data))
if err != nil {
return err
}

_, err = objAPI.PutObject(context.Background(), minioMetaBucket, configFile, hashReader, nil)
return err
}

var errConfigNotFound = errors.New("config file not found")

func readConfig(ctx context.Context, objAPI ObjectLayer, configFile string) (*bytes.Buffer, error) {
var buffer bytes.Buffer
// Read entire content by setting size to -1
if err := objAPI.GetObject(ctx, minioMetaBucket, configFile, 0, -1, &buffer, ""); err != nil {
// Ignore if err is ObjectNotFound or IncompleteBody when bucket is not configured with notification
if isErrObjectNotFound(err) || isErrIncompleteBody(err) {
return nil, errConfigNotFound
}

logger.GetReqInfo(ctx).AppendTags("configFile", configFile)
logger.LogIf(ctx, err)
return nil, err
}

// Return config not found on empty content.
if buffer.Len() == 0 {
return nil, errConfigNotFound
}

return &buffer, nil
}

// ConfigSys - config system.
type ConfigSys struct{}

// Load - load config.json.
func (sys *ConfigSys) Load(objAPI ObjectLayer) error {
if objAPI == nil {
return errInvalidArgument
}
srvCfg, err := readServerConfig(context.Background(), objAPI)
if err != nil {
return err
}
if err = srvCfg.Validate(); err != nil {
return err
}

// If env is set override the credentials from config file.
if globalIsEnvCreds {
srvCfg.SetCredential(globalActiveCred)
}

if globalIsEnvBrowser {
srvCfg.SetBrowser(globalIsBrowserEnabled)
}

if globalIsEnvRegion {
srvCfg.SetRegion(globalServerRegion)
}

if globalIsEnvDomainName {
srvCfg.Domain = globalDomainName
}

if globalIsStorageClass {
srvCfg.SetStorageClass(globalStandardStorageClass, globalRRStorageClass)
}

if globalIsDiskCacheEnabled {
srvCfg.SetCacheConfig(globalCacheDrives, globalCacheExcludes, globalCacheExpiry, globalCacheMaxUse)
}

globalServerConfigMu.Lock()
globalServerConfig = srvCfg
globalServerConfigMu.Unlock()

if !globalIsEnvCreds {
globalActiveCred = globalServerConfig.GetCredential()
}
if !globalIsEnvBrowser {
globalIsBrowserEnabled = globalServerConfig.GetBrowser()
}
if !globalIsEnvWORM {
globalWORMEnabled = globalServerConfig.GetWorm()
}
if !globalIsEnvRegion {
globalServerRegion = globalServerConfig.GetRegion()
}
if !globalIsEnvDomainName {
globalDomainName = globalServerConfig.Domain
}
if !globalIsStorageClass {
globalStandardStorageClass, globalRRStorageClass = globalServerConfig.GetStorageClass()
}
if !globalIsDiskCacheEnabled {
cacheConf := globalServerConfig.GetCacheConfig()
globalCacheDrives = cacheConf.Drives
globalCacheExcludes = cacheConf.Exclude
globalCacheExpiry = cacheConf.Expiry
globalCacheMaxUse = cacheConf.MaxUse
}

return nil
}

// Init - initializes config system from config.json.
func (sys *ConfigSys) Init(objAPI ObjectLayer) error {
if objAPI == nil {
return errInvalidArgument
}

srvCfg, err := readServerConfig(context.Background(), objAPI)
if err != nil {
return err
}

if err = srvCfg.Validate(); err != nil {
return err
}

// If env is set override the credentials from config file.
if globalIsEnvCreds {
srvCfg.SetCredential(globalActiveCred)
}

if globalIsEnvBrowser {
srvCfg.SetBrowser(globalIsBrowserEnabled)
}

if globalIsEnvWORM {
srvCfg.SetWorm(globalWORMEnabled)
}

if globalIsEnvRegion {
srvCfg.SetRegion(globalServerRegion)
}

if globalIsEnvDomainName {
srvCfg.Domain = globalDomainName
}

if globalIsStorageClass {
srvCfg.SetStorageClass(globalStandardStorageClass, globalRRStorageClass)
}

if globalIsDiskCacheEnabled {
srvCfg.SetCacheConfig(globalCacheDrives, globalCacheExcludes, globalCacheExpiry, globalCacheMaxUse)
}

globalServerConfigMu.Lock()
defer globalServerConfigMu.Unlock()
globalServerConfig = srvCfg
return nil
}

// NewConfigSys - creates new config system object.
func NewConfigSys() *ConfigSys {
return &ConfigSys{}
}

// Migrates ${HOME}/.minio/config.json to '<export_path>/.minio.sys/config/minio.json'
func migrateConfigToMinioSys() error {
// Construct path to config.json for the given bucket.
configFile := path.Join(bucketConfigPrefix, minioConfigFile)
transactionConfigFile := configFile + ".transaction"

// As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket
// and configFile, take a transaction lock to avoid race.
objLock := globalNSMutex.NewNSLock(minioMetaBucket, transactionConfigFile)
if err := objLock.GetLock(globalOperationTimeout); err != nil {
return err
}
defer objLock.Unlock()

// Verify if backend already has the file.
if err := checkServerConfig(context.Background(), newObjectLayerFn()); err != errConfigNotFound {
return err
} // if errConfigNotFound proceed to migrate..

data, err := ioutil.ReadFile(getConfigFile())
if err != nil {
return err
}

var config = &serverConfig{}
d := json.NewDecoder(bytes.NewReader(data))
if err = d.Decode(config); err != nil {
return err
}

if err = quick.CheckData(config); err != nil {
return err
}

return saveServerConfig(newObjectLayerFn(), config)
}
3 changes: 3 additions & 0 deletions cmd/globals.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ var (
// Holds the host that was passed using --address
globalMinioHost = ""

// globalConfigSys server config system.
globalConfigSys *ConfigSys

globalNotificationSys *NotificationSys
globalPolicySys *PolicySys

Expand Down

0 comments on commit a971361

Please sign in to comment.