Enhance fatal errors printing of common issues seen by users (#5878)

This commit is contained in:
Anis Elleuch 2018-05-08 19:04:36 -07:00 committed by Dee Koder
parent 54cd29b51c
commit 32700fca52
20 changed files with 591 additions and 135 deletions

View File

@ -22,7 +22,6 @@ import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
) )
@ -44,19 +43,19 @@ func parsePublicCertFile(certFile string) (x509Certs []*x509.Certificate, err er
for len(current) > 0 { for len(current) > 0 {
var pemBlock *pem.Block var pemBlock *pem.Block
if pemBlock, current = pem.Decode(current); pemBlock == nil { if pemBlock, current = pem.Decode(current); pemBlock == nil {
return nil, fmt.Errorf("Could not read PEM block from file %s", certFile) return nil, uiErrSSLUnexpectedData(nil).Msg("Could not read PEM block from file %s", certFile)
} }
var x509Cert *x509.Certificate var x509Cert *x509.Certificate
if x509Cert, err = x509.ParseCertificate(pemBlock.Bytes); err != nil { if x509Cert, err = x509.ParseCertificate(pemBlock.Bytes); err != nil {
return nil, err return nil, uiErrSSLUnexpectedData(err)
} }
x509Certs = append(x509Certs, x509Cert) x509Certs = append(x509Certs, x509Cert)
} }
if len(x509Certs) == 0 { if len(x509Certs) == 0 {
return nil, fmt.Errorf("Empty public certificate file %s", certFile) return nil, uiErrSSLUnexpectedData(nil).Msg("Empty public certificate file %s", certFile)
} }
return x509Certs, nil return x509Certs, nil
@ -107,28 +106,32 @@ func getRootCAs(certsCAsDir string) (*x509.CertPool, error) {
func loadX509KeyPair(certFile, keyFile string) (tls.Certificate, error) { func loadX509KeyPair(certFile, keyFile string) (tls.Certificate, error) {
certPEMBlock, err := ioutil.ReadFile(certFile) certPEMBlock, err := ioutil.ReadFile(certFile)
if err != nil { if err != nil {
return tls.Certificate{}, fmt.Errorf("TLS: failed to read cert file: %v", err) return tls.Certificate{}, uiErrSSLUnexpectedError(err)
} }
keyPEMBlock, err := ioutil.ReadFile(keyFile) keyPEMBlock, err := ioutil.ReadFile(keyFile)
if err != nil { if err != nil {
return tls.Certificate{}, fmt.Errorf("TLS: failed to read private key: %v", err) return tls.Certificate{}, uiErrSSLUnexpectedError(err)
} }
key, rest := pem.Decode(keyPEMBlock) key, rest := pem.Decode(keyPEMBlock)
if len(rest) > 0 { if len(rest) > 0 {
return tls.Certificate{}, fmt.Errorf("TLS: private key contains additional data") return tls.Certificate{}, uiErrSSLUnexpectedData(nil).Msg("The private key contains additional data")
} }
if x509.IsEncryptedPEMBlock(key) { if x509.IsEncryptedPEMBlock(key) {
password, ok := os.LookupEnv(TLSPrivateKeyPassword) password, ok := os.LookupEnv(TLSPrivateKeyPassword)
if !ok { if !ok {
return tls.Certificate{}, fmt.Errorf("TLS: private key is encrypted but no password is present - set env var: %s", TLSPrivateKeyPassword) return tls.Certificate{}, uiErrSSLNoPassword(nil)
} }
decryptedKey, decErr := x509.DecryptPEMBlock(key, []byte(password)) decryptedKey, decErr := x509.DecryptPEMBlock(key, []byte(password))
if decErr != nil { if decErr != nil {
return tls.Certificate{}, fmt.Errorf("TLS: failed to decrypt private key: %v", decErr) return tls.Certificate{}, uiErrSSLWrongPassword(decErr)
} }
keyPEMBlock = pem.EncodeToMemory(&pem.Block{Type: key.Type, Bytes: decryptedKey}) keyPEMBlock = pem.EncodeToMemory(&pem.Block{Type: key.Type, Bytes: decryptedKey})
} }
return tls.X509KeyPair(certPEMBlock, keyPEMBlock) cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
if err != nil {
return tls.Certificate{}, uiErrSSLUnexpectedData(nil).Msg(err.Error())
}
return cert, nil
} }
func getSSLConfig() (x509Certs []*x509.Certificate, rootCAs *x509.CertPool, tlsCert *tls.Certificate, secureConn bool, err error) { func getSSLConfig() (x509Certs []*x509.Certificate, rootCAs *x509.CertPool, tlsCert *tls.Certificate, secureConn bool, err error) {
@ -149,7 +152,7 @@ func getSSLConfig() (x509Certs []*x509.Certificate, rootCAs *x509.CertPool, tlsC
if priv, ok := cert.PrivateKey.(crypto.Signer); ok { if priv, ok := cert.PrivateKey.(crypto.Signer); ok {
if pub, ok := priv.Public().(*ecdsa.PublicKey); ok { if pub, ok := priv.Public().(*ecdsa.PublicKey); ok {
if name := pub.Params().Name; name == "P-384" || name == "P-521" { // unfortunately there is no cleaner way to check if name := pub.Params().Name; name == "P-384" || name == "P-521" { // unfortunately there is no cleaner way to check
return nil, nil, nil, false, fmt.Errorf("TLS: the ECDSA curve '%s' is not supported", name) return nil, nil, nil, false, uiErrSSLUnexpectedData(nil).Msg("tls: the ECDSA curve '%s' is not supported", name)
} }
} }

View File

@ -45,7 +45,7 @@ func initConfig() {
// Config file does not exist, we create it fresh and return upon success. // Config file does not exist, we create it fresh and return upon success.
if isFile(getConfigFile()) { if isFile(getConfigFile()) {
logger.FatalIf(migrateConfig(), "Config migration failed.") logger.FatalIf(migrateConfig(), "Config migration failed.")
logger.FatalIf(loadConfig(), "Unable to load config version: '%s'.", serverConfigVersion) logger.FatalIf(loadConfig(), "Unable to load the configuration file")
} else { } else {
logger.FatalIf(newConfig(), "Unable to initialize minio config for the first time.") logger.FatalIf(newConfig(), "Unable to initialize minio config for the first time.")
logger.Info("Created minio configuration file successfully at " + getConfigDir()) logger.Info("Created minio configuration file successfully at " + getConfigDir())
@ -95,7 +95,9 @@ func handleCommonEnvVars() {
secretKey := os.Getenv("MINIO_SECRET_KEY") secretKey := os.Getenv("MINIO_SECRET_KEY")
if accessKey != "" && secretKey != "" { if accessKey != "" && secretKey != "" {
cred, err := auth.CreateCredentials(accessKey, secretKey) cred, err := auth.CreateCredentials(accessKey, secretKey)
logger.FatalIf(err, "Invalid access/secret Key set in environment.") if err != nil {
logger.Fatal(uiErrInvalidCredentials(err), "Unable to validate credentials inherited from the shell environment")
}
// credential Envs are set globally. // credential Envs are set globally.
globalIsEnvCreds = true globalIsEnvCreds = true
@ -105,7 +107,7 @@ func handleCommonEnvVars() {
if browser := os.Getenv("MINIO_BROWSER"); browser != "" { if browser := os.Getenv("MINIO_BROWSER"); browser != "" {
browserFlag, err := ParseBrowserFlag(browser) browserFlag, err := ParseBrowserFlag(browser)
if err != nil { if err != nil {
logger.FatalIf(errors.New("invalid value"), "Unknown value %s in MINIO_BROWSER environment variable.", browser) logger.Fatal(uiErrInvalidBrowserValue(nil).Msg("Unknown value `%s`", browser), "Unable to validate MINIO_BROWSER environment variable")
} }
// browser Envs are set globally, this does not represent // browser Envs are set globally, this does not represent
@ -128,18 +130,26 @@ func handleCommonEnvVars() {
if drives := os.Getenv("MINIO_CACHE_DRIVES"); drives != "" { if drives := os.Getenv("MINIO_CACHE_DRIVES"); drives != "" {
driveList, err := parseCacheDrives(strings.Split(drives, cacheEnvDelimiter)) driveList, err := parseCacheDrives(strings.Split(drives, cacheEnvDelimiter))
logger.FatalIf(err, "Invalid value set in environment variable MINIO_CACHE_DRIVES %s.", drives) if err != nil {
logger.Fatal(err, "Unable to parse MINIO_CACHE_DRIVES value (%s)", drives)
}
globalCacheDrives = driveList globalCacheDrives = driveList
globalIsDiskCacheEnabled = true globalIsDiskCacheEnabled = true
} }
if excludes := os.Getenv("MINIO_CACHE_EXCLUDE"); excludes != "" { if excludes := os.Getenv("MINIO_CACHE_EXCLUDE"); excludes != "" {
excludeList, err := parseCacheExcludes(strings.Split(excludes, cacheEnvDelimiter)) excludeList, err := parseCacheExcludes(strings.Split(excludes, cacheEnvDelimiter))
logger.FatalIf(err, "Invalid value set in environment variable MINIO_CACHE_EXCLUDE %s.", excludes) if err != nil {
logger.Fatal(err, "Unable to parse MINIO_CACHE_EXCLUDE value (`%s`).", excludes)
}
globalCacheExcludes = excludeList globalCacheExcludes = excludeList
} }
if expiryStr := os.Getenv("MINIO_CACHE_EXPIRY"); expiryStr != "" { if expiryStr := os.Getenv("MINIO_CACHE_EXPIRY"); expiryStr != "" {
expiry, err := strconv.Atoi(expiryStr) expiry, err := strconv.Atoi(expiryStr)
logger.FatalIf(err, "Invalid value set in environment variable MINIO_CACHE_EXPIRY %s.", expiryStr) if err != nil {
logger.Fatal(uiErrInvalidCacheExpiryValue(err), "Unable to parse MINIO_CACHE_EXPIRY value (`%s`)", expiryStr)
}
globalCacheExpiry = expiry globalCacheExpiry = expiry
} }

View File

@ -287,7 +287,7 @@ func getValidConfig() (*serverConfig, error) {
func loadConfig() error { func loadConfig() error {
srvCfg, err := getValidConfig() srvCfg, err := getValidConfig()
if err != nil { if err != nil {
return err return uiErrInvalidConfig(nil).Msg(err.Error())
} }
// If env is set override the credentials from config file. // If env is set override the credentials from config file.

View File

@ -18,7 +18,6 @@ package cmd
import ( import (
"encoding/json" "encoding/json"
"fmt"
"path/filepath" "path/filepath"
) )
@ -54,7 +53,7 @@ func (cfg *CacheConfig) UnmarshalJSON(data []byte) (err error) {
func parseCacheDrives(drives []string) ([]string, error) { func parseCacheDrives(drives []string) ([]string, error) {
for _, d := range drives { for _, d := range drives {
if !filepath.IsAbs(d) { if !filepath.IsAbs(d) {
return nil, fmt.Errorf("cache dir should be absolute path: %s", d) return nil, uiErrInvalidCacheDrivesValue(nil).Msg("cache dir should be absolute path: %s", d)
} }
} }
return drives, nil return drives, nil
@ -64,10 +63,10 @@ func parseCacheDrives(drives []string) ([]string, error) {
func parseCacheExcludes(excludes []string) ([]string, error) { func parseCacheExcludes(excludes []string) ([]string, error) {
for _, e := range excludes { for _, e := range excludes {
if len(e) == 0 { if len(e) == 0 {
return nil, fmt.Errorf("cache exclude path (%s) cannot be empty", e) return nil, uiErrInvalidCacheExcludesValue(nil).Msg("cache exclude path (%s) cannot be empty", e)
} }
if hasPrefix(e, slashSeparator) { if hasPrefix(e, slashSeparator) {
return nil, fmt.Errorf("cache exclude pattern (%s) cannot start with / as prefix", e) return nil, uiErrInvalidCacheExcludesValue(nil).Msg("cache exclude pattern (%s) cannot start with / as prefix", e)
} }
} }
return excludes, nil return excludes, nil

View File

@ -70,11 +70,10 @@ func getSetIndexes(args []string, totalSizes []uint64) (setIndexes [][]uint64, e
} }
setIndexes = make([][]uint64, len(totalSizes)) setIndexes = make([][]uint64, len(totalSizes))
for i, totalSize := range totalSizes { for _, totalSize := range totalSizes {
// Check if totalSize has minimum range upto setSize // Check if totalSize has minimum range upto setSize
if totalSize < setSizes[0] { if totalSize < setSizes[0] {
return nil, fmt.Errorf("Invalid inputs (%s). Ellipses range or number of args %d should be atleast divisible by least possible set size %d", return nil, uiErrInvalidNumberOfErasureEndpoints(nil)
args[i], totalSize, setSizes[0])
} }
} }
@ -103,8 +102,7 @@ func getSetIndexes(args []string, totalSizes []uint64) (setIndexes [][]uint64, e
// Check whether setSize is with the supported range. // Check whether setSize is with the supported range.
if !isValidSetSize(setSize) { if !isValidSetSize(setSize) {
return nil, fmt.Errorf("Invalid inputs (%s). Ellipses range or number of args %d should be atleast divisible by least possible set size %d", return nil, uiErrInvalidNumberOfErasureEndpoints(nil)
args, setSize, setSizes[0])
} }
for i := range totalSizes { for i := range totalSizes {
@ -167,14 +165,14 @@ func parseEndpointSet(args ...string) (ep endpointSet, err error) {
for i, arg := range args { for i, arg := range args {
patterns, perr := ellipses.FindEllipsesPatterns(arg) patterns, perr := ellipses.FindEllipsesPatterns(arg)
if perr != nil { if perr != nil {
return endpointSet{}, perr return endpointSet{}, uiErrInvalidErasureEndpoints(nil).Msg(perr.Error())
} }
argPatterns[i] = patterns argPatterns[i] = patterns
} }
ep.setIndexes, err = getSetIndexes(args, getTotalSizes(argPatterns)) ep.setIndexes, err = getSetIndexes(args, getTotalSizes(argPatterns))
if err != nil { if err != nil {
return endpointSet{}, err return endpointSet{}, uiErrInvalidErasureEndpoints(nil).Msg(err.Error())
} }
ep.argPatterns = argPatterns ep.argPatterns = argPatterns
@ -223,7 +221,7 @@ func getAllSets(args ...string) ([][]string, error) {
for _, sargs := range setArgs { for _, sargs := range setArgs {
for _, arg := range sargs { for _, arg := range sargs {
if uniqueArgs.Contains(arg) { if uniqueArgs.Contains(arg) {
return nil, fmt.Errorf("Input args (%s) has duplicate ellipses", args) return nil, uiErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("Input args (%s) has duplicate ellipses", args))
} }
uniqueArgs.Add(arg) uniqueArgs.Add(arg)
} }

View File

@ -252,14 +252,14 @@ func CreateEndpoints(serverAddr string, args ...[]string) (string, EndpointList,
return serverAddr, endpoints, setupType, err return serverAddr, endpoints, setupType, err
} }
if endpoint.Type() != PathEndpointType { if endpoint.Type() != PathEndpointType {
return serverAddr, endpoints, setupType, fmt.Errorf("use path style endpoint for FS setup") return serverAddr, endpoints, setupType, uiErrInvalidFSEndpoint(nil).Msg("use path style endpoint for FS setup")
} }
endpoints = append(endpoints, endpoint) endpoints = append(endpoints, endpoint)
setupType = FSSetupType setupType = FSSetupType
// Check for cross device mounts if any. // Check for cross device mounts if any.
if err = checkCrossDeviceMounts(endpoints); err != nil { if err = checkCrossDeviceMounts(endpoints); err != nil {
return serverAddr, endpoints, setupType, err return serverAddr, endpoints, setupType, uiErrInvalidFSEndpoint(nil).Msg(err.Error())
} }
return serverAddr, endpoints, setupType, nil return serverAddr, endpoints, setupType, nil
} }
@ -270,12 +270,12 @@ func CreateEndpoints(serverAddr string, args ...[]string) (string, EndpointList,
var eps EndpointList var eps EndpointList
eps, err = NewEndpointList(iargs...) eps, err = NewEndpointList(iargs...)
if err != nil { if err != nil {
return serverAddr, endpoints, setupType, err return serverAddr, endpoints, setupType, uiErrInvalidErasureEndpoints(nil).Msg(err.Error())
} }
// Check for cross device mounts if any. // Check for cross device mounts if any.
if err = checkCrossDeviceMounts(eps); err != nil { if err = checkCrossDeviceMounts(eps); err != nil {
return serverAddr, endpoints, setupType, err return serverAddr, endpoints, setupType, uiErrInvalidErasureEndpoints(nil).Msg(err.Error())
} }
for _, ep := range eps { for _, ep := range eps {
@ -316,7 +316,7 @@ func CreateEndpoints(serverAddr string, args ...[]string) (string, EndpointList,
// No local endpoint found. // No local endpoint found.
if localEndpointCount == 0 { if localEndpointCount == 0 {
return serverAddr, endpoints, setupType, fmt.Errorf("no endpoint found for this host") return serverAddr, endpoints, setupType, uiErrInvalidErasureEndpoints(nil).Msg("no endpoint pointing to the local machine is found")
} }
// Check whether same path is not used in endpoints of a host on different port. // Check whether same path is not used in endpoints of a host on different port.
@ -331,8 +331,8 @@ func CreateEndpoints(serverAddr string, args ...[]string) (string, EndpointList,
hostIPSet, _ := getHostIP4(host) hostIPSet, _ := getHostIP4(host)
if IPSet, ok := pathIPMap[endpoint.Path]; ok { if IPSet, ok := pathIPMap[endpoint.Path]; ok {
if !IPSet.Intersection(hostIPSet).IsEmpty() { if !IPSet.Intersection(hostIPSet).IsEmpty() {
err = fmt.Errorf("path '%s' can not be served by different port on same address", endpoint.Path) return serverAddr, endpoints, setupType,
return serverAddr, endpoints, setupType, err uiErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("path '%s' can not be served by different port on same address", endpoint.Path))
} }
pathIPMap[endpoint.Path] = IPSet.Union(hostIPSet) pathIPMap[endpoint.Path] = IPSet.Union(hostIPSet)
} else { } else {
@ -349,8 +349,8 @@ func CreateEndpoints(serverAddr string, args ...[]string) (string, EndpointList,
continue continue
} }
if localPathSet.Contains(endpoint.Path) { if localPathSet.Contains(endpoint.Path) {
err = fmt.Errorf("path '%s' cannot be served by different address on same server", endpoint.Path) return serverAddr, endpoints, setupType,
return serverAddr, endpoints, setupType, err uiErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("path '%s' cannot be served by different address on same server", endpoint.Path))
} }
localPathSet.Add(endpoint.Path) localPathSet.Add(endpoint.Path)
} }
@ -360,12 +360,11 @@ func CreateEndpoints(serverAddr string, args ...[]string) (string, EndpointList,
{ {
if !localPortSet.Contains(serverAddrPort) { if !localPortSet.Contains(serverAddrPort) {
if len(localPortSet) > 1 { if len(localPortSet) > 1 {
err = fmt.Errorf("port number in server address must match with one of the port in local endpoints") return serverAddr, endpoints, setupType,
} else { uiErrInvalidErasureEndpoints(nil).Msg("port number in server address must match with one of the port in local endpoints")
err = fmt.Errorf("server address and local endpoint have different ports")
} }
return serverAddr, endpoints, setupType,
return serverAddr, endpoints, setupType, err uiErrInvalidErasureEndpoints(nil).Msg("server address and local endpoint have different ports")
} }
} }
@ -374,7 +373,7 @@ func CreateEndpoints(serverAddr string, args ...[]string) (string, EndpointList,
// If all endpoints have same port number, then this is XL setup using URL style endpoints. // If all endpoints have same port number, then this is XL setup using URL style endpoints.
if len(localPortSet) == 1 { if len(localPortSet) == 1 {
if len(localServerAddrSet) > 1 { if len(localServerAddrSet) > 1 {
// TODO: Eventhough all endpoints are local, the local host is referred by different IP/name. // TODO: Even though all endpoints are local, the local host is referred by different IP/name.
// eg '172.0.0.1', 'localhost' and 'mylocalhostname' point to same local host. // eg '172.0.0.1', 'localhost' and 'mylocalhostname' point to same local host.
// //
// In this case, we bind to 0.0.0.0 ie to all interfaces. // In this case, we bind to 0.0.0.0 ie to all interfaces.
@ -388,7 +387,7 @@ func CreateEndpoints(serverAddr string, args ...[]string) (string, EndpointList,
return serverAddr, endpoints, setupType, nil return serverAddr, endpoints, setupType, nil
} }
// Eventhough all endpoints are local, but those endpoints use different ports. // Even though all endpoints are local, but those endpoints use different ports.
// This means it is DistXL setup. // This means it is DistXL setup.
} else { } else {
// This is DistXL setup. // This is DistXL setup.

View File

@ -225,8 +225,8 @@ func TestCreateEndpoints(t *testing.T) {
{"localhost:10000", [][]string{{"./d1"}}, "localhost:10000", EndpointList{Endpoint{URL: &url.URL{Path: "d1"}, IsLocal: true}}, FSSetupType, nil}, {"localhost:10000", [][]string{{"./d1"}}, "localhost:10000", EndpointList{Endpoint{URL: &url.URL{Path: "d1"}, IsLocal: true}}, FSSetupType, nil},
{"localhost:10000", [][]string{{`\d1`}}, "localhost:10000", EndpointList{Endpoint{URL: &url.URL{Path: `\d1`}, IsLocal: true}}, FSSetupType, nil}, {"localhost:10000", [][]string{{`\d1`}}, "localhost:10000", EndpointList{Endpoint{URL: &url.URL{Path: `\d1`}, IsLocal: true}}, FSSetupType, nil},
{"localhost:10000", [][]string{{`.\d1`}}, "localhost:10000", EndpointList{Endpoint{URL: &url.URL{Path: `.\d1`}, IsLocal: true}}, FSSetupType, nil}, {"localhost:10000", [][]string{{`.\d1`}}, "localhost:10000", EndpointList{Endpoint{URL: &url.URL{Path: `.\d1`}, IsLocal: true}}, FSSetupType, nil},
{":8080", [][]string{{"https://example.org/d1", "https://example.org/d2", "https://example.org/d3", "https://example.org/d4"}}, "", EndpointList{}, -1, fmt.Errorf("no endpoint found for this host")}, {":8080", [][]string{{"https://example.org/d1", "https://example.org/d2", "https://example.org/d3", "https://example.org/d4"}}, "", EndpointList{}, -1, fmt.Errorf("no endpoint pointing to the local machine is found")},
{":8080", [][]string{{"https://example.org/d1", "https://example.com/d2", "https://example.net:8000/d3", "https://example.edu/d1"}}, "", EndpointList{}, -1, fmt.Errorf("no endpoint found for this host")}, {":8080", [][]string{{"https://example.org/d1", "https://example.com/d2", "https://example.net:8000/d3", "https://example.edu/d1"}}, "", EndpointList{}, -1, fmt.Errorf("no endpoint pointing to the local machine is found")},
{"localhost:9000", [][]string{{"https://127.0.0.1:9000/d1", "https://localhost:9001/d1", "https://example.com/d1", "https://example.com/d2"}}, "", EndpointList{}, -1, fmt.Errorf("path '/d1' can not be served by different port on same address")}, {"localhost:9000", [][]string{{"https://127.0.0.1:9000/d1", "https://localhost:9001/d1", "https://example.com/d1", "https://example.com/d2"}}, "", EndpointList{}, -1, fmt.Errorf("path '/d1' can not be served by different port on same address")},
{"localhost:9000", [][]string{{"https://127.0.0.1:8000/d1", "https://localhost:9001/d2", "https://example.com/d1", "https://example.com/d2"}}, "", EndpointList{}, -1, fmt.Errorf("port number in server address must match with one of the port in local endpoints")}, {"localhost:9000", [][]string{{"https://127.0.0.1:8000/d1", "https://localhost:9001/d2", "https://example.com/d1", "https://example.com/d2"}}, "", EndpointList{}, -1, fmt.Errorf("port number in server address must match with one of the port in local endpoints")},
{"localhost:10000", [][]string{{"https://127.0.0.1:8000/d1", "https://localhost:8000/d2", "https://example.com/d1", "https://example.com/d2"}}, "", EndpointList{}, -1, fmt.Errorf("server address and local endpoint have different ports")}, {"localhost:10000", [][]string{{"https://127.0.0.1:8000/d1", "https://localhost:8000/d2", "https://example.com/d1", "https://example.com/d2"}}, "", EndpointList{}, -1, fmt.Errorf("server address and local endpoint have different ports")},

View File

@ -24,7 +24,6 @@ import (
"path" "path"
"time" "time"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/lock" "github.com/minio/minio/pkg/lock"
) )
@ -113,7 +112,6 @@ func formatFSMigrateV1ToV2(ctx context.Context, wlk *lock.LockedFile, fsPath str
} }
if err = os.MkdirAll(path.Join(fsPath, minioMetaMultipartBucket), 0755); err != nil { if err = os.MkdirAll(path.Join(fsPath, minioMetaMultipartBucket), 0755); err != nil {
logger.LogIf(ctx, err)
return err return err
} }
@ -147,7 +145,7 @@ func formatFSMigrate(ctx context.Context, wlk *lock.LockedFile, fsPath string) e
return err return err
} }
if version != formatFSVersionV2 { if version != formatFSVersionV2 {
return fmt.Errorf(`%s file: expected FS version: %s, found FS version: %s`, formatConfigFile, formatFSVersionV2, version) return uiErrUnexpectedBackendVersion(fmt.Errorf(`%s file: expected FS version: %s, found FS version: %s`, formatConfigFile, formatFSVersionV2, version))
} }
return nil return nil
} }
@ -158,7 +156,6 @@ func createFormatFS(ctx context.Context, fsFormatPath string) error {
// file stored in minioMetaBucket(.minio.sys) directory. // file stored in minioMetaBucket(.minio.sys) directory.
lk, err := lock.TryLockedOpenFile(fsFormatPath, os.O_RDWR|os.O_CREATE, 0600) lk, err := lock.TryLockedOpenFile(fsFormatPath, os.O_RDWR|os.O_CREATE, 0600)
if err != nil { if err != nil {
logger.LogIf(ctx, err)
return err return err
} }
// Close the locked file upon return. // Close the locked file upon return.
@ -166,7 +163,6 @@ func createFormatFS(ctx context.Context, fsFormatPath string) error {
fi, err := lk.Stat() fi, err := lk.Stat()
if err != nil { if err != nil {
logger.LogIf(ctx, err)
return err return err
} }
if fi.Size() != 0 { if fi.Size() != 0 {
@ -195,7 +191,6 @@ func initFormatFS(ctx context.Context, fsPath string) (rlk *lock.RLockedFile, er
var fi os.FileInfo var fi os.FileInfo
fi, err = rlk.Stat() fi, err = rlk.Stat()
if err != nil { if err != nil {
logger.LogIf(ctx, err)
return nil, err return nil, err
} }
isEmpty = fi.Size() == 0 isEmpty = fi.Size() == 0
@ -214,7 +209,6 @@ func initFormatFS(ctx context.Context, fsPath string) (rlk *lock.RLockedFile, er
continue continue
} }
if err != nil { if err != nil {
logger.LogIf(ctx, err)
return nil, err return nil, err
} }
// After successfully creating format.json try to hold a read-lock on // After successfully creating format.json try to hold a read-lock on
@ -222,13 +216,11 @@ func initFormatFS(ctx context.Context, fsPath string) (rlk *lock.RLockedFile, er
continue continue
} }
if err != nil { if err != nil {
logger.LogIf(ctx, err)
return nil, err return nil, err
} }
formatBackend, err := formatMetaGetFormatBackendFS(rlk) formatBackend, err := formatMetaGetFormatBackendFS(rlk)
if err != nil { if err != nil {
logger.LogIf(ctx, err)
return nil, err return nil, err
} }
if formatBackend != formatBackendFS { if formatBackend != formatBackendFS {

View File

@ -19,7 +19,6 @@ package cmd
import ( import (
"context" "context"
"encoding/hex" "encoding/hex"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
@ -80,6 +79,7 @@ func initMetaVolumeFS(fsPath, fsUUID string) error {
// optimizing all other calls. Create minio meta volume, // optimizing all other calls. Create minio meta volume,
// if it doesn't exist yet. // if it doesn't exist yet.
metaBucketPath := pathJoin(fsPath, minioMetaBucket) metaBucketPath := pathJoin(fsPath, minioMetaBucket)
if err := os.MkdirAll(metaBucketPath, 0777); err != nil { if err := os.MkdirAll(metaBucketPath, 0777); err != nil {
return err return err
} }
@ -103,7 +103,7 @@ func NewFSObjectLayer(fsPath string) (ObjectLayer, error) {
var err error var err error
if fsPath, err = checkPathValid(fsPath); err != nil { if fsPath, err = checkPathValid(fsPath); err != nil {
return nil, err return nil, uiErrUnableToWriteInBackend(err)
} }
// Assign a new UUID for FS minio mode. Each server instance // Assign a new UUID for FS minio mode. Each server instance
@ -112,7 +112,7 @@ func NewFSObjectLayer(fsPath string) (ObjectLayer, error) {
// Initialize meta volume, if volume already exists ignores it. // Initialize meta volume, if volume already exists ignores it.
if err = initMetaVolumeFS(fsPath, fsUUID); err != nil { if err = initMetaVolumeFS(fsPath, fsUUID); err != nil {
return nil, fmt.Errorf("Unable to initialize '.minio.sys' meta volume, %s", err) return nil, err
} }
// Initialize `format.json`, this function also returns. // Initialize `format.json`, this function also returns.
@ -142,12 +142,12 @@ func NewFSObjectLayer(fsPath string) (ObjectLayer, error) {
// Initialize notification system. // Initialize notification system.
if err = globalNotificationSys.Init(fs); err != nil { if err = globalNotificationSys.Init(fs); err != nil {
return nil, fmt.Errorf("Unable to initialize notification system. %v", err) return nil, uiErrUnableToReadFromBackend(err).Msg("Unable to initialize notification system")
} }
// Initialize policy system. // Initialize policy system.
if err = globalPolicySys.Init(fs); err != nil { if err = globalPolicySys.Init(fs); err != nil {
return nil, fmt.Errorf("Unable to initialize policy system. %v", err) return nil, uiErrUnableToReadFromBackend(err).Msg("Unable to initialize policy system")
} }
go fs.cleanupStaleMultipartUploads(ctx, globalMultipartCleanupInterval, globalMultipartExpiry, globalServiceDoneCh) go fs.cleanupStaleMultipartUploads(ctx, globalMultipartCleanupInterval, globalMultipartExpiry, globalServiceDoneCh)

View File

@ -112,6 +112,10 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
logger.FatalIf(errUnexpected, "Gateway implementation not initialized, exiting.") logger.FatalIf(errUnexpected, "Gateway implementation not initialized, exiting.")
} }
// Disable logging until gateway initialization is complete, any
// error during initialization will be shown as a fatal message
logger.Disable = true
// Validate if we have access, secret set through environment. // Validate if we have access, secret set through environment.
gatewayName := gw.Name() gatewayName := gw.Name()
if ctx.Args().First() == "help" { if ctx.Args().First() == "help" {
@ -219,5 +223,8 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
printGatewayStartupMessage(getAPIEndpoints(gatewayAddr), gatewayName) printGatewayStartupMessage(getAPIEndpoints(gatewayAddr), gatewayName)
} }
// Reenable logging
logger.Disable = false
handleSignals() handleSignals()
} }

View File

@ -192,9 +192,12 @@ var (
// global colors. // global colors.
var ( var (
colorBold = color.New(color.Bold).SprintFunc() colorBold = color.New(color.Bold).SprintFunc()
colorBlue = color.New(color.FgBlue).SprintfFunc() colorRed = color.New(color.FgRed).SprintfFunc()
colorYellow = color.New(color.FgYellow).SprintfFunc() colorBlue = color.New(color.FgBlue).SprintfFunc()
colorYellow = color.New(color.FgYellow).SprintfFunc()
colorBgYellow = color.New(color.BgYellow).SprintfFunc()
colorBlack = color.New(color.FgBlack).SprintfFunc()
) )
// Returns minio global information, as a key value map. // Returns minio global information, as a key value map.

View File

@ -27,14 +27,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/fatih/color"
c "github.com/minio/mc/pkg/console" c "github.com/minio/mc/pkg/console"
) "golang.org/x/crypto/ssh/terminal"
// global colors.
var (
colorBold = color.New(color.Bold).SprintFunc()
colorRed = color.New(color.FgRed).SprintfFunc()
) )
// Disable disables all logging, false by default. (used for "go test") // Disable disables all logging, false by default. (used for "go test")
@ -47,9 +41,9 @@ type Level int8
// Enumerated level types // Enumerated level types
const ( const (
Information Level = iota + 1 InformationLvl Level = iota + 1
Error ErrorLvl
Fatal FatalLvl
) )
const loggerTimeFormat string = "15:04:05 MST 01/02/2006" const loggerTimeFormat string = "15:04:05 MST 01/02/2006"
@ -64,11 +58,11 @@ var matchingFuncNames = [...]string{
func (level Level) String() string { func (level Level) String() string {
var lvlStr string var lvlStr string
switch level { switch level {
case Information: case InformationLvl:
lvlStr = "INFO" lvlStr = "INFO"
case Error: case ErrorLvl:
lvlStr = "ERROR" lvlStr = "ERROR"
case Fatal: case FatalLvl:
lvlStr = "FATAL" lvlStr = "FATAL"
} }
return lvlStr return lvlStr
@ -82,10 +76,9 @@ type Console interface {
} }
func consoleLog(console Console, msg string, args ...interface{}) { func consoleLog(console Console, msg string, args ...interface{}) {
if Disable {
return
}
if jsonFlag { if jsonFlag {
// Strip escape control characters from json message
msg = ansiRE.ReplaceAllLiteralString(msg, "")
console.json(msg, args...) console.json(msg, args...)
} else if quiet { } else if quiet {
console.quiet(msg, args...) console.quiet(msg, args...)
@ -124,6 +117,8 @@ type logEntry struct {
// jsonFlag: Display in JSON format, if enabled // jsonFlag: Display in JSON format, if enabled
var ( var (
quiet, jsonFlag bool quiet, jsonFlag bool
// Custom function to format error
errorFmtFunc func(string, error, bool) string
) )
// EnableQuiet - turns quiet option on. // EnableQuiet - turns quiet option on.
@ -137,11 +132,18 @@ func EnableJSON() {
quiet = true quiet = true
} }
// RegisterUIError registers the specified rendering function. This latter
// will be called for a pretty rendering of fatal errors.
func RegisterUIError(f func(string, error, bool) string) {
errorFmtFunc = f
}
// Init sets the trimStrings to possible GOPATHs // Init sets the trimStrings to possible GOPATHs
// and GOROOT directories. Also append github.com/minio/minio // and GOROOT directories. Also append github.com/minio/minio
// This is done to clean up the filename, when stack trace is // This is done to clean up the filename, when stack trace is
// displayed when an error happens. // displayed when an error happens.
func Init(goPath string) { func Init(goPath string) {
var goPathList []string var goPathList []string
var defaultgoPathList []string var defaultgoPathList []string
// Add all possible GOPATH paths into trimStrings // Add all possible GOPATH paths into trimStrings
@ -182,8 +184,8 @@ func trimTrace(f string) string {
return filepath.FromSlash(f) return filepath.FromSlash(f)
} }
func getSource() string { func getSource(level int) string {
pc, file, lineNumber, ok := runtime.Caller(5) pc, file, lineNumber, ok := runtime.Caller(level)
if ok { if ok {
// Clean up the common prefixes // Clean up the common prefixes
file = trimTrace(file) file = trimTrace(file)
@ -225,7 +227,8 @@ func getTrace(traceLevel int) []string {
return trace return trace
} }
// LogIf : // LogIf prints a detailed error message during
// the execution of the server.
func LogIf(ctx context.Context, err error) { func LogIf(ctx context.Context, err error) {
if Disable { if Disable {
return return
@ -251,15 +254,17 @@ func LogIf(ctx context.Context, err error) {
tags[entry.Key] = entry.Val tags[entry.Key] = entry.Val
} }
// Get the cause for the Error
message := err.Error()
// Get full stack trace // Get full stack trace
trace := getTrace(2) trace := getTrace(2)
// Get the cause for the Error
message := err.Error()
// Output the formatted log message at console // Output the formatted log message at console
var output string var output string
if jsonFlag { if jsonFlag {
logJSON, err := json.Marshal(&logEntry{ logJSON, err := json.Marshal(&logEntry{
Level: Error.String(), Level: ErrorLvl.String(),
RemoteHost: req.RemoteHost, RemoteHost: req.RemoteHost,
RequestID: req.RequestID, RequestID: req.RequestID,
UserAgent: req.UserAgent, UserAgent: req.UserAgent,
@ -317,9 +322,10 @@ func LogIf(ctx context.Context, err error) {
tagString = "\n " + tagString tagString = "\n " + tagString
} }
var msg = colorFgRed(colorBold(message))
output = fmt.Sprintf("\n%s\n%s%s%s%s\nError: %s%s\n%s", output = fmt.Sprintf("\n%s\n%s%s%s%s\nError: %s%s\n%s",
apiString, timeString, requestID, remoteHost, userAgent, apiString, timeString, requestID, remoteHost, userAgent,
colorRed(colorBold(message)), tagString, strings.Join(trace, "\n")) msg, tagString, strings.Join(trace, "\n"))
} }
fmt.Println(output) fmt.Println(output)
} }
@ -334,17 +340,28 @@ func CriticalIf(ctx context.Context, err error) {
} }
} }
// FatalIf : // FatalIf is similar to Fatal() but it ignores passed nil error
// Just fatal error message, no stack trace
// It'll be called for input validation failures
func FatalIf(err error, msg string, data ...interface{}) { func FatalIf(err error, msg string, data ...interface{}) {
if err != nil { if err == nil {
if msg != "" { return
consoleLog(fatalMessage, msg, data...)
} else {
consoleLog(fatalMessage, err.Error())
}
} }
fatal(err, msg, data...)
}
// Fatal prints only fatal error message without no stack trace
// it will be called for input validation failures
func Fatal(err error, msg string, data ...interface{}) {
fatal(err, msg, data...)
}
func fatal(err error, msg string, data ...interface{}) {
var errMsg string
if msg != "" {
errMsg = errorFmtFunc(fmt.Sprintf(msg, data...), err, jsonFlag)
} else {
errMsg = err.Error()
}
consoleLog(fatalMessage, errMsg)
} }
var fatalMessage fatalMsg var fatalMessage fatalMsg
@ -354,14 +371,15 @@ type fatalMsg struct {
func (f fatalMsg) json(msg string, args ...interface{}) { func (f fatalMsg) json(msg string, args ...interface{}) {
logJSON, err := json.Marshal(&logEntry{ logJSON, err := json.Marshal(&logEntry{
Level: Fatal.String(), Level: FatalLvl.String(),
Time: time.Now().UTC().Format(time.RFC3339Nano), Time: time.Now().UTC().Format(time.RFC3339Nano),
Trace: &traceEntry{Message: fmt.Sprintf(msg, args...), Source: []string{getSource()}}, Trace: &traceEntry{Message: fmt.Sprintf(msg, args...), Source: []string{getSource(6)}},
}) })
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println(string(logJSON)) fmt.Println(string(logJSON))
os.Exit(1) os.Exit(1)
} }
@ -370,9 +388,65 @@ func (f fatalMsg) quiet(msg string, args ...interface{}) {
f.pretty(msg, args...) f.pretty(msg, args...)
} }
var (
logTag = "ERROR"
logBanner = colorBgRed(colorFgWhite(colorBold(logTag))) + " "
emptyBanner = colorBgRed(strings.Repeat(" ", len(logTag))) + " "
minimumWidth = 80
bannerWidth = len(logTag) + 1
)
func (f fatalMsg) pretty(msg string, args ...interface{}) { func (f fatalMsg) pretty(msg string, args ...interface{}) {
// Build the passed error message
errMsg := fmt.Sprintf(msg, args...) errMsg := fmt.Sprintf(msg, args...)
fmt.Println(colorRed(colorBold("Error: " + errMsg))) // Check terminal width
termWidth, _, err := terminal.GetSize(0)
if err != nil || termWidth < minimumWidth {
termWidth = minimumWidth
}
// Calculate available widht without the banner
width := termWidth - bannerWidth
tagPrinted := false
// Print the error message: the following code takes care
// of splitting error text and always pretty printing the
// red banner along with the error message. Since the error
// message itself contains some colored text, we needed
// to use some ANSI control escapes to cursor color state
// and freely move in the screen.
for _, line := range strings.Split(errMsg, "\n") {
if len(line) == 0 {
// No more text to print, just quit.
break
}
for {
// Save the attributes of the current cursor helps
// us save the text color of the passed error message
ansiSaveAttributes()
// Print banner with or without the log tag
if !tagPrinted {
fmt.Print(logBanner)
tagPrinted = true
} else {
fmt.Print(emptyBanner)
}
// Restore the text color of the error message
ansiRestoreAttributes()
ansiMoveRight(bannerWidth)
// Continue error message printing
if len(line) > width {
fmt.Println(line[:width])
line = line[width:]
} else {
fmt.Println(line)
break
}
}
}
// Exit because this is a fatal error message
os.Exit(1) os.Exit(1)
} }
@ -383,7 +457,7 @@ type infoMsg struct {
func (i infoMsg) json(msg string, args ...interface{}) { func (i infoMsg) json(msg string, args ...interface{}) {
logJSON, err := json.Marshal(&logEntry{ logJSON, err := json.Marshal(&logEntry{
Level: Information.String(), Level: InformationLvl.String(),
Message: fmt.Sprintf(msg, args...), Message: fmt.Sprintf(msg, args...),
Time: time.Now().UTC().Format(time.RFC3339Nano), Time: time.Now().UTC().Format(time.RFC3339Nano),
}) })

52
cmd/logger/utils.go Normal file
View File

@ -0,0 +1,52 @@
/*
* 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 logger
import (
"fmt"
"regexp"
"github.com/fatih/color"
)
// Global colors.
var (
colorBold = color.New(color.Bold).SprintFunc()
colorFgRed = color.New(color.FgRed).SprintfFunc()
colorBgRed = color.New(color.BgRed).SprintfFunc()
colorFgWhite = color.New(color.FgWhite).SprintfFunc()
)
var ansiRE = regexp.MustCompile("(\x1b[^m]*m)")
// Print ANSI Control escape
func ansiEscape(format string, args ...interface{}) {
var Esc = "\x1b"
fmt.Printf("%s%s", Esc, fmt.Sprintf(format, args...))
}
func ansiMoveRight(n int) {
ansiEscape("[%dC", n)
}
func ansiSaveAttributes() {
ansiEscape("7")
}
func ansiRestoreAttributes() {
ansiEscape("8")
}

View File

@ -352,15 +352,15 @@ func sameLocalAddrs(addr1, addr2 string) (bool, error) {
func CheckLocalServerAddr(serverAddr string) error { func CheckLocalServerAddr(serverAddr string) error {
host, port, err := net.SplitHostPort(serverAddr) host, port, err := net.SplitHostPort(serverAddr)
if err != nil { if err != nil {
return err return uiErrInvalidAddressFlag(err)
} }
// Check whether port is a valid port number. // Check whether port is a valid port number.
p, err := strconv.Atoi(port) p, err := strconv.Atoi(port)
if err != nil { if err != nil {
return fmt.Errorf("invalid port number") return uiErrInvalidAddressFlag(err).Msg("invalid port number")
} else if p < 1 || p > 65535 { } else if p < 1 || p > 65535 {
return fmt.Errorf("port number must be between 1 to 65535") return uiErrInvalidAddressFlag(nil).Msg("port number must be between 1 to 65535")
} }
// 0.0.0.0 is a wildcard address and refers to local network // 0.0.0.0 is a wildcard address and refers to local network
@ -372,7 +372,7 @@ func CheckLocalServerAddr(serverAddr string) error {
return err return err
} }
if !isLocalHost { if !isLocalHost {
return fmt.Errorf("host in server address should be this server") return uiErrInvalidAddressFlag(nil).Msg("host in server address should be this server")
} }
} }

View File

@ -18,10 +18,10 @@ package cmd
import ( import (
"context" "context"
"fmt"
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"runtime"
"syscall" "syscall"
"github.com/minio/cli" "github.com/minio/cli"
@ -118,26 +118,26 @@ func serverHandleCmdArgs(ctx *cli.Context) {
// Server address. // Server address.
serverAddr := ctx.String("address") serverAddr := ctx.String("address")
logger.FatalIf(CheckLocalServerAddr(serverAddr), "Invalid address %s in command line argument.", serverAddr) logger.FatalIf(CheckLocalServerAddr(serverAddr), "Unable to validate passed arguments")
var setupType SetupType var setupType SetupType
var err error var err error
if len(ctx.Args()) > serverCommandLineArgsMax { if len(ctx.Args()) > serverCommandLineArgsMax {
logger.FatalIf(errInvalidArgument, "Invalid total number of arguments (%d) passed, supported upto 32 unique arguments", len(ctx.Args())) uErr := uiErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("Invalid total number of endpoints (%d) passed, supported upto 32 unique arguments", len(ctx.Args())))
logger.FatalIf(uErr, "Unable to validate passed endpoints")
} }
globalMinioAddr, globalEndpoints, setupType, globalXLSetCount, globalXLSetDriveCount, err = createServerEndpoints(serverAddr, ctx.Args()...) globalMinioAddr, globalEndpoints, setupType, globalXLSetCount, globalXLSetDriveCount, err = createServerEndpoints(serverAddr, ctx.Args()...)
logger.FatalIf(err, "Invalid command line arguments server=%s, args=%s", serverAddr, ctx.Args()) logger.FatalIf(err, "Invalid command line arguments")
globalMinioHost, globalMinioPort = mustSplitHostPort(globalMinioAddr) globalMinioHost, globalMinioPort = mustSplitHostPort(globalMinioAddr)
if runtime.GOOS == "darwin" {
// On macOS, if a process already listens on LOCALIPADDR:PORT, net.Listen() falls back // On macOS, if a process already listens on LOCALIPADDR:PORT, net.Listen() falls back
// to IPv6 address ie minio will start listening on IPv6 address whereas another // to IPv6 address ie minio will start listening on IPv6 address whereas another
// (non-)minio process is listening on IPv4 of given port. // (non-)minio process is listening on IPv4 of given port.
// To avoid this error sutiation we check for port availability only for macOS. // To avoid this error sutiation we check for port availability.
logger.FatalIf(checkPortAvailability(globalMinioPort), "Port %d already in use", globalMinioPort) logger.FatalIf(checkPortAvailability(globalMinioPort), "Unable to start the server")
}
globalIsXL = (setupType == XLSetupType) globalIsXL = (setupType == XLSetupType)
globalIsDistXL = (setupType == DistXLSetupType) globalIsDistXL = (setupType == DistXLSetupType)
@ -168,6 +168,10 @@ func serverMain(ctx *cli.Context) {
cli.ShowCommandHelpAndExit(ctx, "server", 1) cli.ShowCommandHelpAndExit(ctx, "server", 1)
} }
// Disable logging until server initialization is complete, any
// error during initialization will be shown as a fatal message
logger.Disable = true
// Get "json" flag from command line argument and // Get "json" flag from command line argument and
// enable json and quite modes if jason flag is turned on. // enable json and quite modes if jason flag is turned on.
jsonFlag := ctx.IsSet("json") || ctx.GlobalIsSet("json") jsonFlag := ctx.IsSet("json") || ctx.GlobalIsSet("json")
@ -181,6 +185,8 @@ func serverMain(ctx *cli.Context) {
logger.EnableQuiet() logger.EnableQuiet()
} }
logger.RegisterUIError(fmtError)
// Handle all server command args. // Handle all server command args.
serverHandleCmdArgs(ctx) serverHandleCmdArgs(ctx)
@ -188,7 +194,7 @@ func serverMain(ctx *cli.Context) {
serverHandleEnvVars() serverHandleEnvVars()
// Create certs path. // Create certs path.
logger.FatalIf(createConfigDir(), "Unable to create configuration directories.") logger.FatalIf(createConfigDir(), "Unable to initialize configuration files")
// Initialize server config. // Initialize server config.
initConfig() initConfig()
@ -196,15 +202,15 @@ func serverMain(ctx *cli.Context) {
// Check and load SSL certificates. // Check and load SSL certificates.
var err error var err error
globalPublicCerts, globalRootCAs, globalTLSCertificate, globalIsSSL, err = getSSLConfig() globalPublicCerts, globalRootCAs, globalTLSCertificate, globalIsSSL, err = getSSLConfig()
logger.FatalIf(err, "Invalid SSL certificate file") logger.FatalIf(err, "Unable to load the TLS configuration")
// Is distributed setup, error out if no certificates are found for HTTPS endpoints. // Is distributed setup, error out if no certificates are found for HTTPS endpoints.
if globalIsDistXL { if globalIsDistXL {
if globalEndpoints.IsHTTPS() && !globalIsSSL { if globalEndpoints.IsHTTPS() && !globalIsSSL {
logger.FatalIf(errInvalidArgument, "No certificates found, use HTTP endpoints (%s)", globalEndpoints) logger.Fatal(uiErrNoCertsAndHTTPSEndpoints(nil), "Unable to start the server")
} }
if !globalEndpoints.IsHTTPS() && globalIsSSL { if !globalEndpoints.IsHTTPS() && globalIsSSL {
logger.FatalIf(errInvalidArgument, "TLS Certificates found, use HTTPS endpoints (%s)", globalEndpoints) logger.Fatal(uiErrCertsAndHTTPEndpoints(nil), "Unable to start the server")
} }
} }
@ -225,7 +231,9 @@ func serverMain(ctx *cli.Context) {
// Set nodes for dsync for distributed setup. // Set nodes for dsync for distributed setup.
if globalIsDistXL { if globalIsDistXL {
globalDsync, err = dsync.New(newDsyncNodes(globalEndpoints)) globalDsync, err = dsync.New(newDsyncNodes(globalEndpoints))
logger.FatalIf(err, "Unable to initialize distributed locking on %s", globalEndpoints) if err != nil {
logger.Fatal(err, "Unable to initialize distributed locking on %s", globalEndpoints)
}
} }
// Initialize name space lock. // Initialize name space lock.
@ -237,11 +245,15 @@ func serverMain(ctx *cli.Context) {
// Configure server. // Configure server.
var handler http.Handler var handler http.Handler
handler, err = configureServerHandler(globalEndpoints) handler, err = configureServerHandler(globalEndpoints)
logger.FatalIf(err, "Unable to configure one of server's RPC services.") if err != nil {
logger.Fatal(uiErrUnexpectedError(err), "Unable to configure one of server's RPC services.")
}
// Create new notification system. // Create new notification system.
globalNotificationSys, err = NewNotificationSys(globalServerConfig, globalEndpoints) globalNotificationSys, err = NewNotificationSys(globalServerConfig, globalEndpoints)
logger.FatalIf(err, "Unable to create new notification system.") if err != nil {
logger.Fatal(uiErrUnexpectedError(err), "Unable to create new notification system.")
}
// Create new policy system. // Create new policy system.
globalPolicySys = NewPolicySys() globalPolicySys = NewPolicySys()
@ -262,10 +274,8 @@ func serverMain(ctx *cli.Context) {
newObject, err := newObjectLayer(globalEndpoints) newObject, err := newObjectLayer(globalEndpoints)
if err != nil { if err != nil {
logger.LogIf(context.Background(), err) globalHTTPServer.Shutdown()
err = globalHTTPServer.Shutdown() logger.FatalIf(err, "Unable to initialize backend")
logger.LogIf(context.Background(), err)
os.Exit(1)
} }
globalObjLayerMutex.Lock() globalObjLayerMutex.Lock()
@ -279,6 +289,9 @@ func serverMain(ctx *cli.Context) {
// Set uptime time after object layer has initialized. // Set uptime time after object layer has initialized.
globalBootTime = UTCNow() globalBootTime = UTCNow()
// Re-enable logging
logger.Disable = false
handleSignals() handleSignals()
} }

View File

@ -18,7 +18,6 @@ package cmd
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
@ -104,20 +103,20 @@ func parseStorageClass(storageClassEnv string) (sc storageClass, err error) {
// only two elements allowed in the string - "scheme" and "number of parity disks" // only two elements allowed in the string - "scheme" and "number of parity disks"
if len(s) > 2 { if len(s) > 2 {
return storageClass{}, errors.New("Too many sections in " + storageClassEnv) return storageClass{}, uiErrStorageClassValue(nil).Msg("Too many sections in " + storageClassEnv)
} else if len(s) < 2 { } else if len(s) < 2 {
return storageClass{}, errors.New("Too few sections in " + storageClassEnv) return storageClass{}, uiErrStorageClassValue(nil).Msg("Too few sections in " + storageClassEnv)
} }
// only allowed scheme is "EC" // only allowed scheme is "EC"
if s[0] != supportedStorageClassScheme { if s[0] != supportedStorageClassScheme {
return storageClass{}, errors.New("Unsupported scheme " + s[0] + ". Supported scheme is EC") return storageClass{}, uiErrStorageClassValue(nil).Msg("Unsupported scheme " + s[0] + ". Supported scheme is EC")
} }
// Number of parity disks should be integer // Number of parity disks should be integer
parityDisks, err := strconv.Atoi(s[1]) parityDisks, err := strconv.Atoi(s[1])
if err != nil { if err != nil {
return storageClass{}, err return storageClass{}, uiErrStorageClassValue(err)
} }
sc = storageClass{ sc = storageClass{

View File

@ -69,8 +69,8 @@ func testParseStorageClass(obj ObjectLayer, instanceType string, t TestErrHandle
t.Errorf("Test %d, Expected %v, got %v", i+1, tt.wantSc, gotSc) t.Errorf("Test %d, Expected %v, got %v", i+1, tt.wantSc, gotSc)
return return
} }
if tt.expectedError != nil && !reflect.DeepEqual(err, tt.expectedError) { if tt.expectedError != nil && err.Error() != tt.expectedError.Error() {
t.Errorf("Test %d, Expected %v, got %v", i+1, tt.expectedError, err) t.Errorf("Test %d, Expected `%v`, got `%v`", i+1, tt.expectedError, err)
} }
} }
} }

View File

@ -16,7 +16,9 @@
package cmd package cmd
import "errors" import (
"errors"
)
// errInvalidArgument means that input argument is invalid. // errInvalidArgument means that input argument is invalid.
var errInvalidArgument = errors.New("Invalid arguments specified") var errInvalidArgument = errors.New("Invalid arguments specified")

135
cmd/ui-errors-utils.go Normal file
View File

@ -0,0 +1,135 @@
/*
* 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 (
"fmt"
"io"
"net"
"os"
)
// uiErr is a structure which contains all information
// to print a fatal error message in json or pretty mode
// uiErr implements error so we can use it anywhere
type uiErr struct {
msg string
detail string
action string
help string
}
// Return the error message
func (u uiErr) Error() string {
if u.detail == "" {
return u.msg
}
return u.detail
}
// Replace the current error's message
func (u uiErr) Msg(m string, args ...interface{}) uiErr {
return uiErr{
msg: fmt.Sprintf(m, args...),
detail: u.detail,
action: u.action,
help: u.help,
}
}
type uiErrFn func(err error) uiErr
// Create a UI error generator, this is needed to simplify
// the update of the detailed error message in several places
// in Minio code
func newUIErrFn(msg, action, help string) uiErrFn {
return func(err error) uiErr {
u := uiErr{
msg: msg,
action: action,
help: help,
}
if err != nil {
u.detail = err.Error()
}
return u
}
}
// errorToUIError inspects the passed error and transforms it
// to the appropriate UI error.
func errorToUIErr(err error) uiErr {
// If this is already a uiErr, do nothing
if e, ok := err.(uiErr); ok {
return e
}
// Show a generic message for known golang errors
switch e := err.(type) {
case *net.OpError:
if e.Op == "listen" {
return uiErrPortAlreadyInUse(e).Msg("Port " + e.Addr.String() + " is already in use")
}
case *os.PathError:
if os.IsPermission(e) {
return uiErrNoPermissionsToAccessDirFiles(e).Msg("Unsufficent permissions to access `" + e.Path + "` path")
}
}
switch err {
case io.ErrUnexpectedEOF:
return uiErrUnexpectedDataContent(err)
default:
// Failed to identify what type of error this, return a simple UI error
return uiErr{msg: err.Error()}
}
}
// fmtError() converts a fatal error message to a more understood error
// using some colors
func fmtError(introMsg string, err error, jsonFlag bool) string {
renderedTxt := ""
uiErr := errorToUIErr(err)
// JSON print
if jsonFlag {
// Message text in json should be simple
if uiErr.detail != "" {
return uiErr.msg + ": " + uiErr.detail
}
return uiErr.msg
}
// Pretty print error message
introMsg += ": "
if uiErr.msg != "" {
introMsg += colorBold(uiErr.msg + ".")
} else {
introMsg += colorBold(err.Error() + ".")
}
renderedTxt += colorRed(introMsg) + "\n"
// Add action message
if uiErr.action != "" {
renderedTxt += "> " + colorBgYellow(colorBlack(uiErr.action+".")) + "\n"
}
// Add help
if uiErr.help != "" {
renderedTxt += colorBold("HELP:") + "\n"
renderedTxt += " " + uiErr.help
}
return renderedTxt
}

170
cmd/ui-errors.go Normal file
View File

@ -0,0 +1,170 @@
/*
* 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
var (
uiErrInvalidConfig = newUIErrFn(
"Invalid value found in the configuration file",
"Please ensure a valid value in the configuration file, for more details refer https://docs.minio.io/docs/minio-server-configuration-guide",
"",
)
uiErrInvalidBrowserValue = newUIErrFn(
"Invalid browser value",
"Please check the passed value",
"Browser can only accept `on` and `off` values. To disable web browser access, set this value to `off`",
)
uiErrInvalidCacheDrivesValue = newUIErrFn(
"Invalid cache drive value",
"Please check the passed value",
"MINIO_CACHE_DRIVES: List of mounted drives or directories delimited by `;`.",
)
uiErrInvalidCacheExcludesValue = newUIErrFn(
"Invalid cache excludes value",
"Please check the passed value",
"MINIO_CACHE_EXCLUDE: List of cache exclusion patterns delimited by `;`.",
)
uiErrInvalidCacheExpiryValue = newUIErrFn(
"Invalid cache expiry value",
"Please check the passed value",
"MINIO_CACHE_EXPIRY: Cache expiry duration in days.",
)
uiErrInvalidCredentials = newUIErrFn(
"Passed credentials are not suitable for use",
"Please provide correct credentials",
`Access key length should be between minimum 3 characters in length.
Secret key should not be between 8 and 40 characters.`,
)
uiErrInvalidErasureEndpoints = newUIErrFn(
"Invalid endpoints to use in erasure mode",
"Please provide correct combinations of local/remote paths",
"For more information, please refer to the following link: https://docs.minio.io/docs/minio-erasure-code-quickstart-guide",
)
uiErrInvalidNumberOfErasureEndpoints = newUIErrFn(
"The total number of endpoints is not suitable for erasure mode",
"Please provide an even number of endpoints greater or equal to 4",
"For more information, please refer to the following link: https://docs.minio.io/docs/minio-erasure-code-quickstart-guide",
)
uiErrStorageClassValue = newUIErrFn(
"Invalid storage class value",
"Please check the passed value",
`MINIO_STORAGE_CLASS_STANDARD: Format "EC:<Default_Parity_Standard_Class>" (e.g. "EC:3"). This sets the number of parity disks for Minio server in Standard mode. Object are stored in Standard mode, if storage class is not defined in Put request.
MINIO_STORAGE_CLASS_RRS: Format "EC:<Default_Parity_Reduced_Redundancy_Class>" (e.g. "EC:3"). This sets the number of parity disks for Minio server in reduced redundancy mode. Objects are stored in Reduced Redundancy mode, if Put request specifies RRS storage class.
Refer to the link https://github.com/minio/minio/tree/master/docs/erasure/storage-class for more information.`,
)
uiErrUnexpectedBackendVersion = newUIErrFn(
"Backend version seems to be too recent",
"Please update to the latest Minio version",
"",
)
uiErrInvalidAddressFlag = newUIErrFn(
"--address input is invalid",
"Please check --address parameter",
`--address binds a specific ADDRESS:PORT, ADDRESS can be an IP or hostname (default:':9000')
Examples: --address ':443'
--address '172.16.34.31:9000'`,
)
uiErrInvalidFSEndpoint = newUIErrFn(
"The given endpoint is not suitable to activate standalone FS mode",
"Please check the given FS endpoint",
`FS mode requires only one writable disk path.
Example 1:
$ minio server /data/minio/`,
)
uiErrUnableToWriteInBackend = newUIErrFn(
"Unable to write to the backend",
"Please ensure that Minio binary has write permissions to the backend",
"",
)
uiErrUnableToReadFromBackend = newUIErrFn(
"Unable to read from the backend",
"Please ensure that Minio binary has read permission from the backend",
"",
)
uiErrPortAlreadyInUse = newUIErrFn(
"Port is already in use",
"Please ensure no other program is using the same address/port",
"",
)
uiErrNoPermissionsToAccessDirFiles = newUIErrFn(
"Missing permissions to access to the specified path",
"Please ensure the specified path can be accessed",
"",
)
uiErrSSLUnexpectedError = newUIErrFn(
"Invalid TLS certificate",
"Please check the content of your certificate data",
`Only PEM (x.509) format is accepted as valid public & private certificates.`,
)
uiErrSSLUnexpectedData = newUIErrFn(
"Something is wrong with your TLS certificate",
"Please check your certificate",
"",
)
uiErrSSLNoPassword = newUIErrFn(
"no password was specified",
"Please set the password to this environment variable `"+TLSPrivateKeyPassword+"` so the private key can be decrypted",
"",
)
uiErrNoCertsAndHTTPSEndpoints = newUIErrFn(
"HTTPS is specified in endpoint URLs but no TLS certificate is found on the local machine",
"Please add a certificate or switch to HTTP.",
"Refer to https://docs.minio.io/docs/how-to-secure-access-to-minio-server-with-tls for information about how to load a TLS certificate in th server.",
)
uiErrCertsAndHTTPEndpoints = newUIErrFn(
"HTTP is specified in endpoint URLs but the server in the local machine is configured with a TLS certificate",
"Please remove the certificate in the configuration directory or switch to HTTPS",
"",
)
uiErrSSLWrongPassword = newUIErrFn(
"Unable to decrypt the private key using the provided password",
"Please set the correct password in "+TLSPrivateKeyPassword,
"",
)
uiErrUnexpectedDataContent = newUIErrFn(
"Unexpected data content",
"Please contact us at https://slack.minio.io",
"",
)
uiErrUnexpectedError = newUIErrFn(
"Unexpected error",
"Please contact us at https://slack.minio.io",
"",
)
)