/* * Minio Cloud Storage, (C) 2016 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 ( "errors" "fmt" "net/url" "path" "github.com/minio/cli" "github.com/minio/mc/pkg/console" ) var healCmd = cli.Command{ Name: "heal", Usage: "To heal objects.", Action: healControl, Flags: globalFlags, CustomHelpTemplate: `NAME: minio control {{.Name}} - {{.Usage}} USAGE: minio control {{.Name}} FLAGS: {{range .Flags}}{{.}} {{end}} EXAMPLES: 1. Heal missing on-disk format across all inconsistent nodes. $ minio control {{.Name}} http://localhost:9000 2. Heals a specific object. $ minio control {{.Name}} http://localhost:9000/songs/classical/western/piano.mp3 3. Heal bucket and all objects in a bucket recursively. $ minio control {{.Name}} http://localhost:9000/songs 4. Heal all objects with a given prefix recursively. $ minio control {{.Name}} http://localhost:9000/songs/classical/ `, } // heals backend storage format, useful in restoring `format.json` missing on a // fresh or corrupted disks. This call does deep inspection of backend layout // and applies appropriate `format.json` to the disk. func healStorageFormat(authClnt *AuthRPCClient) error { args := &GenericArgs{} reply := &GenericReply{} return authClnt.Call("Control.HealFormatHandler", args, reply) } // lists all objects which needs to be healed, this is a precursor helper function called before // calling actual healing operation. Returns a maximum of 1000 objects that needs healing at a time. // Marker indicates the next entry point where the listing will start. func listObjectsHeal(authClnt *AuthRPCClient, bucketName, prefixName, markerName string) (*HealListReply, error) { args := &HealListArgs{ Bucket: bucketName, Prefix: prefixName, Marker: markerName, Delimiter: "", MaxKeys: 1000, } reply := &HealListReply{} err := authClnt.Call("Control.ListObjectsHealHandler", args, reply) if err != nil { return nil, err } return reply, nil } // Internal custom struct encapsulates pretty msg to be printed by the caller. type healMsg struct { Msg string Err error } // Prettifies heal results and returns them over a channel, caller reads from this channel and prints. func prettyHealResults(healedObjects []ObjectInfo, healReply *HealObjectReply) <-chan healMsg { var msgCh = make(chan healMsg) // Starts writing to message channel for the list of results sent back // by a previous healing operation. go func(msgCh chan<- healMsg) { defer close(msgCh) // Go through all the results and validate if we have success or failure. for i, healStr := range healReply.Results { objPath := path.Join(healedObjects[i].Bucket, healedObjects[i].Name) // TODO: We need to still print heal error cause. if healStr != "" { msgCh <- healMsg{ Msg: fmt.Sprintf("%s %s", colorRed("FAILED"), objPath), Err: errors.New(healStr), } continue } msgCh <- healMsg{ Msg: fmt.Sprintf("%s %s", colorGreen("SUCCESS"), objPath), } } }(msgCh) // Return .. return msgCh } var scanBar = scanBarFactory() // Heals all the objects under a given bucket, optionally you can specify an // object prefix to heal objects under this prefix. func healObjects(authClnt *AuthRPCClient, bucketName, prefixName string) error { if authClnt == nil || bucketName == "" { return errInvalidArgument } // Save marker for the next request. var markerName string for { healListReply, err := listObjectsHeal(authClnt, bucketName, prefixName, markerName) if err != nil { return err } // Attempt to heal only if there are any objects to heal. if len(healListReply.Objects) > 0 { healArgs := &HealObjectArgs{ Bucket: bucketName, Objects: healListReply.Objects, } healReply := &HealObjectReply{} err = authClnt.Call("Control.HealObjectsHandler", healArgs, healReply) if err != nil { return err } // Pretty print all the heal results. for msg := range prettyHealResults(healArgs.Objects, healReply) { if msg.Err != nil { // TODO we need to print the error cause as well. scanBar(msg.Msg) continue } // Success. scanBar(msg.Msg) } } // End of listing objects for healing. if !healListReply.IsTruncated { break } // Set the marker to list the next set of keys. markerName = healListReply.NextMarker } return nil } // Heals your bucket for any missing entries. func healBucket(authClnt *AuthRPCClient, bucketName string) error { if authClnt == nil || bucketName == "" { return errInvalidArgument } return authClnt.Call("Control.HealBucketHandler", &HealBucketArgs{ Bucket: bucketName, }, &GenericReply{}) } // Entry point for minio control heal command. func healControl(ctx *cli.Context) { if ctx.Args().Present() && len(ctx.Args()) != 1 { cli.ShowCommandHelpAndExit(ctx, "heal", 1) } parsedURL, err := url.Parse(ctx.Args().Get(0)) fatalIf(err, "Unable to parse URL %s", ctx.Args().Get(0)) authCfg := &authConfig{ accessKey: serverConfig.GetCredential().AccessKeyID, secretKey: serverConfig.GetCredential().SecretAccessKey, secureConn: parsedURL.Scheme == "https", address: parsedURL.Host, path: path.Join(reservedBucket, controlPath), loginMethod: "Control.LoginHandler", } client := newAuthClient(authCfg) if parsedURL.Path == "/" || parsedURL.Path == "" { err = healStorageFormat(client) fatalIf(err, "Unable to heal disk metadata.") return } bucketName, prefixName := urlPathSplit(parsedURL.Path) // Heal the bucket. err = healBucket(client, bucketName) fatalIf(err, "Unable to heal bucket %s", bucketName) // Heal all the objects. err = healObjects(client, bucketName, prefixName) fatalIf(err, "Unable to heal objects on bucket %s at prefix %s", bucketName, prefixName) console.Println() }