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 the first set of changes to move the config
to the backend, the changes use 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 b0e9a39
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 b0e9a39

Please sign in to comment.