mirror of
https://github.com/minio/minio.git
synced 2024-12-23 21:55:53 -05:00
heal: Add ListBucketsHeal object API (#3563)
ListBucketsHeal will list which buckets that need to be healed: * ListBucketsHeal() (buckets []BucketInfo, err error)
This commit is contained in:
parent
dfc2ef3004
commit
0715032598
@ -345,6 +345,34 @@ func (adminAPI adminAPIHandlers) ListObjectsHealHandler(w http.ResponseWriter, r
|
||||
writeSuccessResponseXML(w, encodeResponse(listResponse))
|
||||
}
|
||||
|
||||
// ListBucketsHealHandler - GET /?heal
|
||||
func (adminAPI adminAPIHandlers) ListBucketsHealHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Get object layer instance.
|
||||
objLayer := newObjectLayerFn()
|
||||
if objLayer == nil {
|
||||
writeErrorResponse(w, ErrServerNotInitialized, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate request signature.
|
||||
adminAPIErr := checkRequestAuthType(r, "", "", "")
|
||||
if adminAPIErr != ErrNone {
|
||||
writeErrorResponse(w, adminAPIErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the list buckets to be healed.
|
||||
bucketsInfo, err := objLayer.ListBucketsHeal()
|
||||
if err != nil {
|
||||
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
listResponse := generateListBucketsResponse(bucketsInfo)
|
||||
// Write success response.
|
||||
writeSuccessResponseXML(w, encodeResponse(listResponse))
|
||||
}
|
||||
|
||||
// HealBucketHandler - POST /?heal&bucket=mybucket
|
||||
// - bucket is mandatory query parameter
|
||||
// Heal a given bucket, if present.
|
||||
|
@ -687,7 +687,7 @@ func TestListObjectsHealHandler(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d - Failed to construct list objects needing heal request - %v", i+1, err)
|
||||
}
|
||||
req.Header.Set(minioAdminOpHeader, "list")
|
||||
req.Header.Set(minioAdminOpHeader, "list-objects")
|
||||
|
||||
cred := serverConfig.GetCredential()
|
||||
err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
|
||||
|
@ -49,7 +49,10 @@ func registerAdminRouter(mux *router.Router) {
|
||||
/// Heal operations
|
||||
|
||||
// List Objects needing heal.
|
||||
adminRouter.Methods("GET").Queries("heal", "").Headers(minioAdminOpHeader, "list").HandlerFunc(adminAPI.ListObjectsHealHandler)
|
||||
adminRouter.Methods("GET").Queries("heal", "").Headers(minioAdminOpHeader, "list-objects").HandlerFunc(adminAPI.ListObjectsHealHandler)
|
||||
// List Buckets needing heal.
|
||||
adminRouter.Methods("GET").Queries("heal", "").Headers(minioAdminOpHeader, "list-buckets").HandlerFunc(adminAPI.ListBucketsHealHandler)
|
||||
|
||||
// Heal Buckets.
|
||||
adminRouter.Methods("POST").Queries("heal", "").Headers(minioAdminOpHeader, "bucket").HandlerFunc(adminAPI.HealBucketHandler)
|
||||
// Heal Objects.
|
||||
|
@ -181,8 +181,9 @@ type CommonPrefix struct {
|
||||
|
||||
// Bucket container for bucket metadata
|
||||
type Bucket struct {
|
||||
Name string
|
||||
CreationDate string // time string of format "2006-01-02T15:04:05.000Z"
|
||||
Name string
|
||||
CreationDate string // time string of format "2006-01-02T15:04:05.000Z"
|
||||
HealBucketInfo *HealBucketInfo `xml:"HealBucketInfo,omitempty"`
|
||||
}
|
||||
|
||||
// Object container for object metadata
|
||||
@ -196,8 +197,8 @@ type Object struct {
|
||||
Owner Owner
|
||||
|
||||
// The class of storage used to store the object.
|
||||
StorageClass string
|
||||
HealInfo *HealInfo `xml:"HealInfo,omitempty"`
|
||||
StorageClass string
|
||||
HealObjectInfo *HealObjectInfo `xml:"HealObjectInfo,omitempty"`
|
||||
}
|
||||
|
||||
// CopyObjectResponse container returns ETag and LastModified of the successfully copied object
|
||||
@ -285,6 +286,7 @@ func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
|
||||
var listbucket = Bucket{}
|
||||
listbucket.Name = bucket.Name
|
||||
listbucket.CreationDate = bucket.Created.Format(timeFormatAMZLong)
|
||||
listbucket.HealBucketInfo = bucket.HealBucketInfo
|
||||
listbuckets = append(listbuckets, listbucket)
|
||||
}
|
||||
|
||||
@ -317,8 +319,8 @@ func generateListObjectsV1Response(bucket, prefix, marker, delimiter string, max
|
||||
content.Size = object.Size
|
||||
content.StorageClass = globalMinioDefaultStorageClass
|
||||
content.Owner = owner
|
||||
// object.HealInfo is non-empty only when resp is constructed in ListObjectsHeal.
|
||||
content.HealInfo = object.HealInfo
|
||||
// object.HealObjectInfo is non-empty only when resp is constructed in ListObjectsHeal.
|
||||
content.HealObjectInfo = object.HealObjectInfo
|
||||
contents = append(contents, content)
|
||||
}
|
||||
// TODO - support EncodingType in xml decoding
|
||||
|
@ -897,3 +897,8 @@ func (fs fsObjects) HealBucket(bucket string) error {
|
||||
func (fs fsObjects) ListObjectsHeal(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error) {
|
||||
return ListObjectsInfo{}, traceError(NotImplemented{})
|
||||
}
|
||||
|
||||
// ListBucketsHeal - list all buckets to be healed. Valid only for XL
|
||||
func (fs fsObjects) ListBucketsHeal() ([]BucketInfo, error) {
|
||||
return []BucketInfo{}, traceError(NotImplemented{})
|
||||
}
|
||||
|
@ -50,6 +50,20 @@ type StorageInfo struct {
|
||||
}
|
||||
}
|
||||
|
||||
type healStatus int
|
||||
|
||||
const (
|
||||
healthy healStatus = iota // Object is healthy
|
||||
canHeal // Object can be healed
|
||||
corrupted // Object can't be healed
|
||||
quorumUnavailable // Object can't be healed until read quorum is available
|
||||
)
|
||||
|
||||
// HealBucketInfo - represents healing related information of a bucket.
|
||||
type HealBucketInfo struct {
|
||||
Status healStatus
|
||||
}
|
||||
|
||||
// BucketInfo - represents bucket metadata.
|
||||
type BucketInfo struct {
|
||||
// Name of the bucket.
|
||||
@ -57,18 +71,13 @@ type BucketInfo struct {
|
||||
|
||||
// Date and time when the bucket was created.
|
||||
Created time.Time
|
||||
|
||||
// Healing information
|
||||
HealBucketInfo *HealBucketInfo `xml:"HealBucketInfo,omitempty"`
|
||||
}
|
||||
|
||||
type healStatus int
|
||||
|
||||
const (
|
||||
canHeal healStatus = iota // Object can be healed
|
||||
corrupted // Object can't be healed
|
||||
quorumUnavailable // Object can't be healed until read quorum is available
|
||||
)
|
||||
|
||||
// HealInfo - represents healing related information of an object.
|
||||
type HealInfo struct {
|
||||
// HealObjectInfo - represents healing related information of an object.
|
||||
type HealObjectInfo struct {
|
||||
Status healStatus
|
||||
MissingDataCount int
|
||||
MissingPartityCount int
|
||||
@ -103,8 +112,8 @@ type ObjectInfo struct {
|
||||
ContentEncoding string
|
||||
|
||||
// User-Defined metadata
|
||||
UserDefined map[string]string
|
||||
HealInfo *HealInfo `xml:"HealInfo,omitempty"`
|
||||
UserDefined map[string]string
|
||||
HealObjectInfo *HealObjectInfo `xml:"HealObjectInfo,omitempty"`
|
||||
}
|
||||
|
||||
// ListPartsInfo - represents list of all parts.
|
||||
|
@ -48,6 +48,7 @@ type ObjectLayer interface {
|
||||
|
||||
// Healing operations.
|
||||
HealBucket(bucket string) error
|
||||
ListBucketsHeal() (buckets []BucketInfo, err error)
|
||||
HealObject(bucket, object string) error
|
||||
ListObjectsHeal(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error)
|
||||
}
|
||||
|
@ -141,12 +141,12 @@ func xlShouldHeal(partsMetadata []xlMetaV1, errs []error) bool {
|
||||
// xlHealStat - returns a structure which describes how many data,
|
||||
// parity erasure blocks are missing and if it is possible to heal
|
||||
// with the blocks present.
|
||||
func xlHealStat(xl xlObjects, partsMetadata []xlMetaV1, errs []error) HealInfo {
|
||||
func xlHealStat(xl xlObjects, partsMetadata []xlMetaV1, errs []error) HealObjectInfo {
|
||||
// Less than quorum erasure coded blocks of the object have the same create time.
|
||||
// This object can't be healed with the information we have.
|
||||
modTime, count := commonTime(listObjectModtimes(partsMetadata, errs))
|
||||
if count < xl.readQuorum {
|
||||
return HealInfo{
|
||||
return HealObjectInfo{
|
||||
Status: quorumUnavailable,
|
||||
MissingDataCount: 0,
|
||||
MissingPartityCount: 0,
|
||||
@ -156,7 +156,7 @@ func xlHealStat(xl xlObjects, partsMetadata []xlMetaV1, errs []error) HealInfo {
|
||||
// If there isn't a valid xlMeta then we can't heal the object.
|
||||
xlMeta, err := pickValidXLMeta(partsMetadata, modTime)
|
||||
if err != nil {
|
||||
return HealInfo{
|
||||
return HealObjectInfo{
|
||||
Status: corrupted,
|
||||
MissingDataCount: 0,
|
||||
MissingPartityCount: 0,
|
||||
@ -183,7 +183,7 @@ func xlHealStat(xl xlObjects, partsMetadata []xlMetaV1, errs []error) HealInfo {
|
||||
|
||||
// This object can be healed. We have enough object metadata
|
||||
// to reconstruct missing erasure coded blocks.
|
||||
return HealInfo{
|
||||
return HealObjectInfo{
|
||||
Status: canHeal,
|
||||
MissingDataCount: missingDataCount,
|
||||
MissingPartityCount: missingParityCount,
|
||||
|
@ -19,6 +19,7 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@ -153,9 +154,11 @@ func healBucketMetadata(storageDisks []StorageAPI, bucket string, readQuorum int
|
||||
return healBucketMetaFn(lConfigPath)
|
||||
}
|
||||
|
||||
// listBucketNames list all bucket names from all disks to heal.
|
||||
func listBucketNames(storageDisks []StorageAPI) (bucketNames map[string]struct{}, err error) {
|
||||
bucketNames = make(map[string]struct{})
|
||||
// listAllBuckets lists all buckets from all disks. It also
|
||||
// returns the occurrence of each buckets in all disks
|
||||
func listAllBuckets(storageDisks []StorageAPI) (buckets map[string]VolInfo, bucketsOcc map[string]int, err error) {
|
||||
buckets = make(map[string]VolInfo)
|
||||
bucketsOcc = make(map[string]int)
|
||||
for _, disk := range storageDisks {
|
||||
if disk == nil {
|
||||
continue
|
||||
@ -173,7 +176,10 @@ func listBucketNames(storageDisks []StorageAPI) (bucketNames map[string]struct{}
|
||||
if isMinioMetaBucketName(volInfo.Name) {
|
||||
continue
|
||||
}
|
||||
bucketNames[volInfo.Name] = struct{}{}
|
||||
// Increase counter per bucket name
|
||||
bucketsOcc[volInfo.Name]++
|
||||
// Save volume info under bucket name
|
||||
buckets[volInfo.Name] = volInfo
|
||||
}
|
||||
continue
|
||||
}
|
||||
@ -183,7 +189,101 @@ func listBucketNames(storageDisks []StorageAPI) (bucketNames map[string]struct{}
|
||||
}
|
||||
break
|
||||
}
|
||||
return bucketNames, err
|
||||
return buckets, bucketsOcc, err
|
||||
}
|
||||
|
||||
// reduceHealStatus - fetches the worst heal status in a provided slice
|
||||
func reduceHealStatus(status []healStatus) healStatus {
|
||||
worstStatus := healthy
|
||||
for _, st := range status {
|
||||
if st > worstStatus {
|
||||
worstStatus = st
|
||||
}
|
||||
}
|
||||
return worstStatus
|
||||
}
|
||||
|
||||
// bucketHealStatus - returns the heal status of the provided bucket. Internally,
|
||||
// this function lists all object heal status of objects inside meta bucket config
|
||||
// directory and returns the worst heal status that can be found
|
||||
func (xl xlObjects) bucketHealStatus(bucketName string) (healStatus, error) {
|
||||
// A list of all the bucket config files
|
||||
configFiles := []string{bucketPolicyConfig, bucketNotificationConfig, bucketListenerConfig}
|
||||
// The status of buckets config files
|
||||
configsHealStatus := make([]healStatus, len(configFiles))
|
||||
// The list of errors found during checking heal status of each config file
|
||||
configsErrs := make([]error, len(configFiles))
|
||||
// The path of meta bucket that contains all config files
|
||||
configBucket := path.Join(minioMetaBucket, bucketConfigPrefix, bucketName)
|
||||
|
||||
// Check of config files heal status in go-routines
|
||||
var wg sync.WaitGroup
|
||||
// Loop over config files
|
||||
for idx, configFile := range configFiles {
|
||||
wg.Add(1)
|
||||
// Compute heal status of current config file
|
||||
go func(bucket, object string, index int) {
|
||||
defer wg.Done()
|
||||
// Check
|
||||
listObjectsHeal, err := xl.listObjectsHeal(bucket, object, "", "", 1)
|
||||
// If any error, save and immediately quit
|
||||
if err != nil {
|
||||
configsErrs[index] = err
|
||||
return
|
||||
}
|
||||
// Check if current bucket contains any not healthy config file and save heal status
|
||||
if len(listObjectsHeal.Objects) > 0 {
|
||||
configsHealStatus[index] = listObjectsHeal.Objects[0].HealObjectInfo.Status
|
||||
}
|
||||
}(configBucket, configFile, idx)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// Return any found error
|
||||
for _, err := range configsErrs {
|
||||
if err != nil {
|
||||
return healthy, err
|
||||
}
|
||||
}
|
||||
|
||||
// Reduce and return heal status
|
||||
return reduceHealStatus(configsHealStatus), nil
|
||||
}
|
||||
|
||||
// ListBucketsHeal - Find all buckets that need to be healed
|
||||
func (xl xlObjects) ListBucketsHeal() ([]BucketInfo, error) {
|
||||
listBuckets := []BucketInfo{}
|
||||
// List all buckets that can be found in all disks
|
||||
buckets, occ, err := listAllBuckets(xl.storageDisks)
|
||||
if err != nil {
|
||||
return listBuckets, err
|
||||
}
|
||||
// Iterate over all buckets
|
||||
for _, currBucket := range buckets {
|
||||
// Check the status of bucket metadata
|
||||
bucketHealStatus, err := xl.bucketHealStatus(currBucket.Name)
|
||||
if err != nil {
|
||||
return []BucketInfo{}, err
|
||||
}
|
||||
// If all metadata are sane, check if the bucket directory is present in all disks
|
||||
if bucketHealStatus == healthy && occ[currBucket.Name] != len(xl.storageDisks) {
|
||||
// Current bucket is missing in some of the storage disks
|
||||
bucketHealStatus = canHeal
|
||||
}
|
||||
// Add current bucket to the returned result if not healthy
|
||||
if bucketHealStatus != healthy {
|
||||
listBuckets = append(listBuckets,
|
||||
BucketInfo{
|
||||
Name: currBucket.Name,
|
||||
Created: currBucket.Created,
|
||||
HealBucketInfo: &HealBucketInfo{Status: bucketHealStatus},
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
// Sort found buckets
|
||||
sort.Sort(byBucketName(listBuckets))
|
||||
return listBuckets, nil
|
||||
}
|
||||
|
||||
// This function is meant for all the healing that needs to be done
|
||||
@ -196,7 +296,7 @@ func listBucketNames(storageDisks []StorageAPI) (bucketNames map[string]struct{}
|
||||
// - add support for healing dangling `xl.json`.
|
||||
func quickHeal(storageDisks []StorageAPI, writeQuorum int, readQuorum int) error {
|
||||
// List all bucket names from all disks.
|
||||
bucketNames, err := listBucketNames(storageDisks)
|
||||
bucketNames, _, err := listAllBuckets(storageDisks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -423,3 +423,66 @@ func TestQuickHeal(t *testing.T) {
|
||||
t.Fatal("Got an unexpected error: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestListBucketsHeal lists buckets heal result
|
||||
func TestListBucketsHeal(t *testing.T) {
|
||||
root, err := newTestConfig("us-east-1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer removeAll(root)
|
||||
|
||||
nDisks := 16
|
||||
fsDirs, err := getRandomDisks(nDisks)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer removeRoots(fsDirs)
|
||||
|
||||
endpoints, err := parseStorageEndpoints(fsDirs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
obj, _, err := initObjectLayer(endpoints)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a bucket that won't get corrupted
|
||||
saneBucket := "sanebucket"
|
||||
if err = obj.MakeBucket(saneBucket); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a bucket that will be removed in some disks
|
||||
corruptedBucketName := getRandomBucketName()
|
||||
if err = obj.MakeBucket(corruptedBucketName); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
xl := obj.(*xlObjects)
|
||||
|
||||
// Remove bucket in disk 0, 1 and 2
|
||||
for i := 0; i <= 2; i++ {
|
||||
if err = xl.storageDisks[i].DeleteVol(corruptedBucketName); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// List the missing buckets.
|
||||
buckets, err := xl.ListBucketsHeal()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check the number of buckets in list buckets heal result
|
||||
if len(buckets) != 1 {
|
||||
t.Fatalf("Length of missing buckets is incorrect, expected: 1, found: %d", len(buckets))
|
||||
}
|
||||
|
||||
// Check the name of bucket in list buckets heal result
|
||||
if buckets[0].Name != corruptedBucketName {
|
||||
t.Fatalf("Name of missing bucket is incorrect, expected: %s, found: %s", corruptedBucketName, buckets[0].Name)
|
||||
}
|
||||
}
|
||||
|
@ -159,11 +159,11 @@ func (xl xlObjects) listObjectsHeal(bucket, prefix, marker, delimiter string, ma
|
||||
if xlShouldHeal(partsMetadata, errs) {
|
||||
healStat := xlHealStat(xl, partsMetadata, errs)
|
||||
result.Objects = append(result.Objects, ObjectInfo{
|
||||
Name: objInfo.Name,
|
||||
ModTime: objInfo.ModTime,
|
||||
Size: objInfo.Size,
|
||||
IsDir: false,
|
||||
HealInfo: &healStat,
|
||||
Name: objInfo.Name,
|
||||
ModTime: objInfo.ModTime,
|
||||
Size: objInfo.Size,
|
||||
IsDir: false,
|
||||
HealObjectInfo: &healStat,
|
||||
})
|
||||
}
|
||||
objectLock.RUnlock()
|
||||
|
@ -112,3 +112,9 @@
|
||||
- ErrInvalidBucketName
|
||||
- ErrInvalidObjectName
|
||||
- ErrInvalidDuration
|
||||
|
||||
### Healing
|
||||
|
||||
* ListBucketsHeal
|
||||
- GET /?heal
|
||||
- x-minio-operation: list-buckets
|
||||
|
@ -171,8 +171,8 @@ __Example__
|
||||
log.Fatalln(err)
|
||||
return
|
||||
}
|
||||
if object.HealInfo != nil {
|
||||
switch healInfo := *object.HealInfo; healInfo.Status {
|
||||
if object.HealObjectInfo != nil {
|
||||
switch healInfo := *object.HealObjectInfo; healInfo.Status {
|
||||
case madmin.CanHeal:
|
||||
fmt.Println(object.Key, " can be healed.")
|
||||
case madmin.QuorumUnavailable:
|
||||
@ -185,6 +185,34 @@ __Example__
|
||||
}
|
||||
```
|
||||
|
||||
<a name="ListBucketsList"></a>
|
||||
### ListBucketsList() error
|
||||
If successful returns information on the list of buckets that need healing.
|
||||
|
||||
__Example__
|
||||
|
||||
``` go
|
||||
// List buckets that need healing
|
||||
healBucketsList, err := madmClnt.ListBucketsHeal()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
for bucket := range healBucketsList {
|
||||
if bucket.HealBucketInfo != nil {
|
||||
switch healInfo := *object.HealBucketInfo; healInfo.Status {
|
||||
case madmin.CanHeal:
|
||||
fmt.Println(bucket.Key, " can be healed.")
|
||||
case madmin.QuorumUnavailable:
|
||||
fmt.Println(bucket.Key, " can't be healed until quorum is available.")
|
||||
case madmin.Corrupted:
|
||||
fmt.Println(bucket.Key, " can't be healed, not enough information.")
|
||||
}
|
||||
}
|
||||
fmt.Println("bucket: ", bucket)
|
||||
}
|
||||
```
|
||||
|
||||
<a name="HealBucket"></a>
|
||||
### HealBucket(bucket string, isDryRun bool) error
|
||||
If bucket is successfully healed returns nil, otherwise returns error indicating the reason for failure. If isDryRun is true, then the bucket is not healed, but heal bucket request is validated by the server. e.g, if the bucket exists, if bucket name is valid etc.
|
||||
|
60
pkg/madmin/examples/heal-buckets-list.go
Normal file
60
pkg/madmin/examples/heal-buckets-list.go
Normal file
@ -0,0 +1,60 @@
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 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.
|
||||
*
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// New returns an Minio Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// List buckets that need healing
|
||||
healBucketsList, err := madmClnt.ListBucketsHeal()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
for _, bucket := range healBucketsList {
|
||||
if bucket.HealBucketInfo != nil {
|
||||
switch healInfo := *bucket.HealBucketInfo; healInfo.Status {
|
||||
case madmin.CanHeal:
|
||||
fmt.Println(bucket.Name, " can be healed.")
|
||||
case madmin.QuorumUnavailable:
|
||||
fmt.Println(bucket.Name, " can't be healed until quorum is available.")
|
||||
case madmin.Corrupted:
|
||||
fmt.Println(bucket.Name, " can't be healed, not enough information.")
|
||||
}
|
||||
}
|
||||
fmt.Println("bucket: ", bucket)
|
||||
}
|
||||
}
|
@ -63,20 +63,65 @@ type commonPrefix struct {
|
||||
Prefix string
|
||||
}
|
||||
|
||||
// Owner - bucket owner/principal
|
||||
type Owner struct {
|
||||
ID string
|
||||
DisplayName string
|
||||
}
|
||||
|
||||
// Bucket container for bucket metadata
|
||||
type Bucket struct {
|
||||
Name string
|
||||
CreationDate string // time string of format "2006-01-02T15:04:05.000Z"
|
||||
|
||||
HealBucketInfo *HealBucketInfo `xml:"HealBucketInfo,omitempty"`
|
||||
}
|
||||
|
||||
// ListBucketsHealResponse - format for list buckets response
|
||||
type ListBucketsHealResponse struct {
|
||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListAllMyBucketsResult" json:"-"`
|
||||
|
||||
Owner Owner
|
||||
|
||||
// Container for one or more buckets.
|
||||
Buckets struct {
|
||||
Buckets []Bucket `xml:"Bucket"`
|
||||
} // Buckets are nested
|
||||
}
|
||||
|
||||
// HealStatus - represents different states of healing an object could be in.
|
||||
type healStatus int
|
||||
|
||||
const (
|
||||
// Healthy - Object that is already healthy
|
||||
Healthy healStatus = iota
|
||||
// CanHeal - Object can be healed
|
||||
CanHeal healStatus = iota
|
||||
CanHeal
|
||||
// Corrupted - Object can't be healed
|
||||
Corrupted
|
||||
// QuorumUnavailable - Object can't be healed until read quorum is available
|
||||
QuorumUnavailable
|
||||
)
|
||||
|
||||
// HealInfo - represents healing related information of an object.
|
||||
type HealInfo struct {
|
||||
// HealBucketInfo - represents healing related information of a bucket.
|
||||
type HealBucketInfo struct {
|
||||
Status healStatus
|
||||
}
|
||||
|
||||
// BucketInfo - represents bucket metadata.
|
||||
type BucketInfo struct {
|
||||
// Name of the bucket.
|
||||
Name string
|
||||
|
||||
// Date and time when the bucket was created.
|
||||
Created time.Time
|
||||
|
||||
// Healing information
|
||||
HealBucketInfo *HealBucketInfo `xml:"HealBucketInfo,omitempty"`
|
||||
}
|
||||
|
||||
// HealObjectInfo - represents healing related information of an object.
|
||||
type HealObjectInfo struct {
|
||||
Status healStatus
|
||||
MissingDataCount int
|
||||
MissingPartityCount int
|
||||
@ -108,8 +153,8 @@ type ObjectInfo struct {
|
||||
StorageClass string `json:"storageClass"`
|
||||
|
||||
// Error
|
||||
Err error `json:"-"`
|
||||
HealInfo *HealInfo `json:"healInfo,omitempty"`
|
||||
Err error `json:"-"`
|
||||
HealObjectInfo *HealObjectInfo `json:"healObjectInfo,omitempty"`
|
||||
}
|
||||
|
||||
type healQueryKey string
|
||||
@ -143,7 +188,7 @@ func (adm *AdminClient) listObjectsHeal(bucket, prefix, delimiter, marker string
|
||||
queryVal := mkHealQueryVal(bucket, prefix, marker, delimiter, maxKeyStr)
|
||||
|
||||
hdrs := make(http.Header)
|
||||
hdrs.Set(minioAdminOpHeader, "list")
|
||||
hdrs.Set(minioAdminOpHeader, "list-objects")
|
||||
|
||||
reqData := requestData{
|
||||
queryValues: queryVal,
|
||||
@ -240,6 +285,58 @@ func (adm *AdminClient) ListObjectsHeal(bucket, prefix string, recursive bool, d
|
||||
return objectStatCh, nil
|
||||
}
|
||||
|
||||
const timeFormatAMZLong = "2006-01-02T15:04:05.000Z" // Reply date format with nanosecond precision.
|
||||
|
||||
// ListBucketsHeal - issues heal bucket list API request
|
||||
func (adm *AdminClient) ListBucketsHeal() ([]BucketInfo, error) {
|
||||
queryVal := url.Values{}
|
||||
queryVal.Set("heal", "")
|
||||
|
||||
hdrs := make(http.Header)
|
||||
hdrs.Set(minioAdminOpHeader, "list-buckets")
|
||||
|
||||
reqData := requestData{
|
||||
queryValues: queryVal,
|
||||
customHeaders: hdrs,
|
||||
}
|
||||
|
||||
// Execute GET on /?heal to list objects needing heal.
|
||||
resp, err := adm.executeMethod("GET", reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return []BucketInfo{}, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return []BucketInfo{}, errors.New("Got HTTP Status: " + resp.Status)
|
||||
}
|
||||
|
||||
var listBucketsHealResult ListBucketsHealResponse
|
||||
|
||||
err = xml.NewDecoder(resp.Body).Decode(&listBucketsHealResult)
|
||||
if err != nil {
|
||||
return []BucketInfo{}, err
|
||||
}
|
||||
|
||||
var bucketsToBeHealed []BucketInfo
|
||||
|
||||
for _, bucket := range listBucketsHealResult.Buckets.Buckets {
|
||||
creationDate, err := time.Parse(timeFormatAMZLong, bucket.CreationDate)
|
||||
if err != nil {
|
||||
return []BucketInfo{}, err
|
||||
}
|
||||
bucketsToBeHealed = append(bucketsToBeHealed,
|
||||
BucketInfo{
|
||||
Name: bucket.Name,
|
||||
Created: creationDate,
|
||||
HealBucketInfo: bucket.HealBucketInfo,
|
||||
})
|
||||
}
|
||||
|
||||
return bucketsToBeHealed, nil
|
||||
}
|
||||
|
||||
// HealBucket - Heal the given bucket
|
||||
func (adm *AdminClient) HealBucket(bucket string, dryrun bool) error {
|
||||
// Construct query params.
|
||||
|
Loading…
Reference in New Issue
Block a user