Removed heal backend (#3188)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
@@ -1,375 +0,0 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/websocket"
|
||||
)
|
||||
|
||||
// An alias of string to represent the health color code of an object
|
||||
type col string
|
||||
|
||||
const (
|
||||
colGrey col = "Grey"
|
||||
colRed col = "Red"
|
||||
colYellow col = "Yellow"
|
||||
colGreen col = "Green"
|
||||
)
|
||||
|
||||
var (
|
||||
hColOrder = []col{colRed, colYellow, colGreen}
|
||||
hColTable = map[int][]int{
|
||||
1: {0, -1, 1},
|
||||
2: {0, 1, 2},
|
||||
3: {1, 2, 3},
|
||||
4: {1, 2, 4},
|
||||
5: {1, 3, 5},
|
||||
6: {2, 4, 6},
|
||||
7: {2, 4, 7},
|
||||
8: {2, 5, 8},
|
||||
}
|
||||
)
|
||||
|
||||
type healItemStatus struct {
|
||||
Status string `json:"status"`
|
||||
Error string `json:"errors,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Before struct {
|
||||
Color string `json:"color"`
|
||||
Offline int `json:"offline"`
|
||||
Online int `json:"online"`
|
||||
Missing int `json:"missing"`
|
||||
Corrupted int `json:"corrupted"`
|
||||
Drives []madmin.HealDriveInfo `json:"drives"`
|
||||
} `json:"before"`
|
||||
After struct {
|
||||
Color string `json:"color"`
|
||||
Offline int `json:"offline"`
|
||||
Online int `json:"online"`
|
||||
Missing int `json:"missing"`
|
||||
Corrupted int `json:"corrupted"`
|
||||
Drives []madmin.HealDriveInfo `json:"drives"`
|
||||
} `json:"after"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
type healStatus struct {
|
||||
// Total time since heal start in seconds
|
||||
HealDuration float64 `json:"healDuration"`
|
||||
|
||||
// Accumulated statistics of heal result records
|
||||
BytesScanned int64 `json:"bytesScanned"`
|
||||
|
||||
// Counter for objects, and another counter for all kinds of
|
||||
// items
|
||||
ObjectsScanned int64 `json:"objectsScanned"`
|
||||
ItemsScanned int64 `json:"itemsScanned"`
|
||||
|
||||
// Counters for healed objects and all kinds of healed items
|
||||
ObjectsHealed int64 `json:"objectsHealed"`
|
||||
ItemsHealed int64 `json:"itemsHealed"`
|
||||
|
||||
ItemsHealthStatus []healItemStatus `json:"itemsHealthStatus"`
|
||||
// Map of health color code to number of objects with that
|
||||
// health color code.
|
||||
HealthBeforeCols map[col]int64 `json:"healthBeforeCols"`
|
||||
HealthAfterCols map[col]int64 `json:"healthAfterCols"`
|
||||
}
|
||||
|
||||
type healOptions struct {
|
||||
BucketName string
|
||||
Prefix string
|
||||
ForceStart bool
|
||||
ForceStop bool
|
||||
madmin.HealOpts
|
||||
}
|
||||
|
||||
// startHeal starts healing of the servers based on heal options
|
||||
func startHeal(ctx context.Context, conn WSConn, client MinioAdmin, hOpts *healOptions) error {
|
||||
// Initialize heal
|
||||
healStart, _, err := client.heal(ctx, hOpts.BucketName, hOpts.Prefix, hOpts.HealOpts, "", hOpts.ForceStart, hOpts.ForceStop)
|
||||
if err != nil {
|
||||
LogError("error initializing healing: %v", err)
|
||||
return err
|
||||
}
|
||||
if hOpts.ForceStop {
|
||||
return nil
|
||||
}
|
||||
clientToken := healStart.ClientToken
|
||||
hs := healStatus{
|
||||
HealthBeforeCols: make(map[col]int64),
|
||||
HealthAfterCols: make(map[col]int64),
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
_, res, err := client.heal(ctx, hOpts.BucketName, hOpts.Prefix, hOpts.HealOpts, clientToken, hOpts.ForceStart, hOpts.ForceStop)
|
||||
if err != nil {
|
||||
LogError("error on heal: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
hs.writeStatus(&res, conn)
|
||||
|
||||
if res.Summary == "finished" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if res.Summary == "stopped" {
|
||||
return fmt.Errorf("heal had an errors - %s", res.FailureDetail)
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *healStatus) writeStatus(s *madmin.HealTaskStatus, conn WSConn) error {
|
||||
// Update state
|
||||
h.updateDuration(s)
|
||||
for _, item := range s.Items {
|
||||
err := h.updateStats(item)
|
||||
if err != nil {
|
||||
LogError("error on updateStats: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize message to be sent
|
||||
infoBytes, err := json.Marshal(h)
|
||||
if err != nil {
|
||||
LogError("error on json.Marshal: %v", err)
|
||||
return err
|
||||
}
|
||||
// Send Message through websocket connection
|
||||
err = conn.writeMessage(websocket.TextMessage, infoBytes)
|
||||
if err != nil {
|
||||
LogError("error writeMessage: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *healStatus) updateDuration(s *madmin.HealTaskStatus) {
|
||||
h.HealDuration = time.Now().UTC().Sub(s.StartTime).Round(time.Second).Seconds()
|
||||
}
|
||||
|
||||
func (h *healStatus) updateStats(i madmin.HealResultItem) error {
|
||||
// update general status
|
||||
if i.Type == madmin.HealItemObject {
|
||||
// Objects whose size could not be found have -1 size
|
||||
// returned.
|
||||
if i.ObjectSize >= 0 {
|
||||
h.BytesScanned += i.ObjectSize
|
||||
}
|
||||
h.ObjectsScanned++
|
||||
}
|
||||
h.ItemsScanned++
|
||||
|
||||
beforeUp, afterUp := i.GetOnlineCounts()
|
||||
if afterUp > beforeUp {
|
||||
if i.Type == madmin.HealItemObject {
|
||||
h.ObjectsHealed++
|
||||
}
|
||||
h.ItemsHealed++
|
||||
}
|
||||
// update per item status
|
||||
itemStatus := healItemStatus{}
|
||||
// get color health status
|
||||
var beforeColor, afterColor col
|
||||
var err error
|
||||
switch i.Type {
|
||||
case madmin.HealItemMetadata, madmin.HealItemBucket:
|
||||
beforeColor, afterColor, err = getReplicatedFileHCCChange(i)
|
||||
default:
|
||||
if i.Type == madmin.HealItemObject {
|
||||
itemStatus.Size = i.ObjectSize
|
||||
}
|
||||
beforeColor, afterColor, err = getObjectHCCChange(i)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
itemStatus.Status = "success"
|
||||
itemStatus.Before.Color = strings.ToLower(string(beforeColor))
|
||||
itemStatus.After.Color = strings.ToLower(string(afterColor))
|
||||
itemStatus.Type, itemStatus.Name = getHRITypeAndName(i)
|
||||
itemStatus.Before.Online, itemStatus.After.Online = beforeUp, afterUp
|
||||
itemStatus.Before.Missing, itemStatus.After.Missing = i.GetMissingCounts()
|
||||
itemStatus.Before.Corrupted, itemStatus.After.Corrupted = i.GetCorruptedCounts()
|
||||
itemStatus.Before.Offline, itemStatus.After.Offline = i.GetOfflineCounts()
|
||||
itemStatus.Before.Drives = i.Before.Drives
|
||||
itemStatus.After.Drives = i.After.Drives
|
||||
h.ItemsHealthStatus = append(h.ItemsHealthStatus, itemStatus)
|
||||
h.HealthBeforeCols[beforeColor]++
|
||||
h.HealthAfterCols[afterColor]++
|
||||
return nil
|
||||
}
|
||||
|
||||
// getObjectHCCChange - returns before and after color change for
|
||||
// objects
|
||||
func getObjectHCCChange(h madmin.HealResultItem) (b, a col, err error) {
|
||||
parityShards := h.ParityBlocks
|
||||
dataShards := h.DataBlocks
|
||||
|
||||
onlineBefore, onlineAfter := h.GetOnlineCounts()
|
||||
surplusShardsBeforeHeal := onlineBefore - dataShards
|
||||
surplusShardsAfterHeal := onlineAfter - dataShards
|
||||
b, err = getHColCode(surplusShardsBeforeHeal, parityShards)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
a, err = getHColCode(surplusShardsAfterHeal, parityShards)
|
||||
return
|
||||
}
|
||||
|
||||
// getReplicatedFileHCCChange - fetches health color code for metadata
|
||||
// files that are replicated.
|
||||
func getReplicatedFileHCCChange(h madmin.HealResultItem) (b, a col, err error) {
|
||||
getColCode := func(numAvail int) (c col, err error) {
|
||||
// calculate color code for replicated object similar
|
||||
// to erasure coded objects
|
||||
quorum := h.DiskCount/h.SetCount/2 + 1
|
||||
surplus := numAvail/h.SetCount - quorum
|
||||
parity := h.DiskCount/h.SetCount - quorum
|
||||
c, err = getHColCode(surplus, parity)
|
||||
return
|
||||
}
|
||||
|
||||
onlineBefore, onlineAfter := h.GetOnlineCounts()
|
||||
b, err = getColCode(onlineBefore)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
a, err = getColCode(onlineAfter)
|
||||
return
|
||||
}
|
||||
|
||||
func getHColCode(surplusShards, parityShards int) (c col, err error) {
|
||||
if parityShards < 1 || parityShards > 8 || surplusShards > parityShards {
|
||||
return c, fmt.Errorf("invalid parity shard count/surplus shard count given")
|
||||
}
|
||||
if surplusShards < 0 {
|
||||
return colGrey, err
|
||||
}
|
||||
colRow := hColTable[parityShards]
|
||||
for index, val := range colRow {
|
||||
if val != -1 && surplusShards <= val {
|
||||
return hColOrder[index], err
|
||||
}
|
||||
}
|
||||
return c, fmt.Errorf("cannot get a heal color code")
|
||||
}
|
||||
|
||||
func getHRITypeAndName(i madmin.HealResultItem) (typ, name string) {
|
||||
name = fmt.Sprintf("%s/%s", i.Bucket, i.Object)
|
||||
switch i.Type {
|
||||
case madmin.HealItemMetadata:
|
||||
typ = "system"
|
||||
name = i.Detail
|
||||
case madmin.HealItemBucketMetadata:
|
||||
typ = "system"
|
||||
name = "bucket-metadata:" + name
|
||||
case madmin.HealItemBucket:
|
||||
typ = "bucket"
|
||||
case madmin.HealItemObject:
|
||||
typ = "object"
|
||||
default:
|
||||
typ = fmt.Sprintf("!! Unknown heal result record %#v !!", i)
|
||||
name = typ
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getHealOptionsFromReq return options from request for healing process
|
||||
// path come as : `/heal/<namespace>/<tenantName>/bucket1`
|
||||
// and query params come on request form
|
||||
func getHealOptionsFromReq(req *http.Request) (*healOptions, error) {
|
||||
hOptions := healOptions{}
|
||||
re := regexp.MustCompile(`(/heal/)(.*?)(\?.*?$|$)`)
|
||||
matches := re.FindAllSubmatch([]byte(req.URL.Path), -1)
|
||||
// matches comes as e.g.
|
||||
// [["...", "/heal/", "bucket1"]]
|
||||
// [["/heal/" "/heal/" ""]]
|
||||
|
||||
if len(matches) == 0 || len(matches[0]) < 3 {
|
||||
return nil, fmt.Errorf("invalid url: %s", req.URL.Path)
|
||||
}
|
||||
hOptions.BucketName = strings.TrimSpace(string(matches[0][2]))
|
||||
hOptions.Prefix = req.FormValue("prefix")
|
||||
hOptions.HealOpts.ScanMode = transformScanStr(req.FormValue("scan"))
|
||||
|
||||
if req.FormValue("force-start") != "" {
|
||||
boolVal, err := strconv.ParseBool(req.FormValue("force-start"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hOptions.ForceStart = boolVal
|
||||
}
|
||||
if req.FormValue("force-stop") != "" {
|
||||
boolVal, err := strconv.ParseBool(req.FormValue("force-stop"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hOptions.ForceStop = boolVal
|
||||
}
|
||||
// heal recursively
|
||||
if req.FormValue("recursive") != "" {
|
||||
boolVal, err := strconv.ParseBool(req.FormValue("recursive"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hOptions.HealOpts.Recursive = boolVal
|
||||
}
|
||||
// remove dangling objects in heal sequence
|
||||
if req.FormValue("remove") != "" {
|
||||
boolVal, err := strconv.ParseBool(req.FormValue("remove"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hOptions.HealOpts.Remove = boolVal
|
||||
}
|
||||
// only inspect data
|
||||
if req.FormValue("dry-run") != "" {
|
||||
boolVal, err := strconv.ParseBool(req.FormValue("dry-run"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hOptions.HealOpts.DryRun = boolVal
|
||||
}
|
||||
return &hOptions, nil
|
||||
}
|
||||
|
||||
func transformScanStr(scanStr string) madmin.HealScanMode {
|
||||
if scanStr == "deep" {
|
||||
return madmin.HealDeepScan
|
||||
}
|
||||
return madmin.HealNormalScan
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHeal(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client := AdminClientMock{}
|
||||
mockWSConn := mockConn{}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
function := "startHeal()"
|
||||
mockResultItem1 := madmin.HealResultItem{
|
||||
Type: madmin.HealItemObject,
|
||||
SetCount: 1,
|
||||
DiskCount: 4,
|
||||
ParityBlocks: 2,
|
||||
DataBlocks: 2,
|
||||
Before: struct {
|
||||
Drives []madmin.HealDriveInfo `json:"drives"`
|
||||
}{
|
||||
Drives: []madmin.HealDriveInfo{
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateMissing,
|
||||
},
|
||||
},
|
||||
},
|
||||
After: struct {
|
||||
Drives []madmin.HealDriveInfo `json:"drives"`
|
||||
}{
|
||||
Drives: []madmin.HealDriveInfo{
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
mockResultItem2 := madmin.HealResultItem{
|
||||
Type: madmin.HealItemBucket,
|
||||
SetCount: 1,
|
||||
DiskCount: 4,
|
||||
ParityBlocks: 2,
|
||||
DataBlocks: 2,
|
||||
Before: struct {
|
||||
Drives []madmin.HealDriveInfo `json:"drives"`
|
||||
}{
|
||||
Drives: []madmin.HealDriveInfo{
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateMissing,
|
||||
},
|
||||
},
|
||||
},
|
||||
After: struct {
|
||||
Drives []madmin.HealDriveInfo `json:"drives"`
|
||||
}{
|
||||
Drives: []madmin.HealDriveInfo{
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
{
|
||||
State: madmin.DriveStateOk,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
mockHealTaskStatus := madmin.HealTaskStatus{
|
||||
StartTime: time.Now().UTC().Truncate(time.Second * 2), // mock 2 sec duration
|
||||
Items: []madmin.HealResultItem{
|
||||
mockResultItem1,
|
||||
mockResultItem2,
|
||||
},
|
||||
Summary: "finished",
|
||||
}
|
||||
|
||||
testStreamSize := 1
|
||||
testReceiver := make(chan healStatus, testStreamSize)
|
||||
isClosed := false // testReceiver is closed?
|
||||
|
||||
testOptions := &healOptions{
|
||||
BucketName: "testbucket",
|
||||
Prefix: "",
|
||||
ForceStart: false,
|
||||
ForceStop: false,
|
||||
}
|
||||
// Test-1: startHeal send simple stream of data, no errors
|
||||
minioHealMock = func(ctx context.Context, bucket, prefix string, healOpts madmin.HealOpts, clientToken string,
|
||||
forceStart, forceStop bool,
|
||||
) (healStart madmin.HealStartSuccess, healTaskStatus madmin.HealTaskStatus, err error) {
|
||||
return healStart, mockHealTaskStatus, nil
|
||||
}
|
||||
writesCount := 1
|
||||
// mock connection WriteMessage() no error
|
||||
connWriteMessageMock = func(messageType int, data []byte) error {
|
||||
// emulate that receiver gets the message written
|
||||
var t healStatus
|
||||
_ = json.Unmarshal(data, &t)
|
||||
testReceiver <- t
|
||||
if writesCount == testStreamSize {
|
||||
// for testing we need to close the receiver channel
|
||||
if !isClosed {
|
||||
close(testReceiver)
|
||||
isClosed = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
writesCount++
|
||||
return nil
|
||||
}
|
||||
if err := startHeal(ctx, mockWSConn, client, testOptions); err != nil {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
|
||||
}
|
||||
// check that the TestReceiver got the same number of data from Console.
|
||||
for i := range testReceiver {
|
||||
assert.Equal(int64(1), i.ObjectsScanned)
|
||||
assert.Equal(int64(1), i.ObjectsHealed)
|
||||
assert.Equal(int64(2), i.ItemsScanned)
|
||||
assert.Equal(int64(2), i.ItemsHealed)
|
||||
assert.Equal(int64(0), i.HealthBeforeCols[colGreen])
|
||||
assert.Equal(int64(1), i.HealthBeforeCols[colYellow])
|
||||
assert.Equal(int64(1), i.HealthBeforeCols[colRed])
|
||||
assert.Equal(int64(0), i.HealthBeforeCols[colGrey])
|
||||
assert.Equal(int64(2), i.HealthAfterCols[colGreen])
|
||||
assert.Equal(int64(0), i.HealthAfterCols[colYellow])
|
||||
assert.Equal(int64(0), i.HealthAfterCols[colRed])
|
||||
assert.Equal(int64(0), i.HealthAfterCols[colGrey])
|
||||
}
|
||||
|
||||
// Test-2: startHeal error on init
|
||||
minioHealMock = func(ctx context.Context, bucket, prefix string, healOpts madmin.HealOpts, clientToken string,
|
||||
forceStart, forceStop bool,
|
||||
) (healStart madmin.HealStartSuccess, healTaskStatus madmin.HealTaskStatus, err error) {
|
||||
return healStart, mockHealTaskStatus, errors.New("error")
|
||||
}
|
||||
|
||||
if err := startHeal(ctx, mockWSConn, client, testOptions); assert.Error(err) {
|
||||
assert.Equal("error", err.Error())
|
||||
}
|
||||
|
||||
// Test-3: getHealOptionsFromReq return heal options from request
|
||||
u, _ := url.Parse("http://localhost/api/v1/heal/bucket1?prefix=file/&recursive=true&force-start=true&force-stop=true&remove=true&dry-run=true&scan=deep")
|
||||
req := &http.Request{
|
||||
URL: u,
|
||||
}
|
||||
opts, err := getHealOptionsFromReq(req)
|
||||
if assert.NoError(err) {
|
||||
expectedOptions := healOptions{
|
||||
BucketName: "bucket1",
|
||||
ForceStart: true,
|
||||
ForceStop: true,
|
||||
Prefix: "file/",
|
||||
HealOpts: madmin.HealOpts{
|
||||
Recursive: true,
|
||||
DryRun: true,
|
||||
ScanMode: madmin.HealDeepScan,
|
||||
},
|
||||
}
|
||||
assert.Equal(expectedOptions.BucketName, opts.BucketName)
|
||||
assert.Equal(expectedOptions.Prefix, opts.Prefix)
|
||||
assert.Equal(expectedOptions.Recursive, opts.Recursive)
|
||||
assert.Equal(expectedOptions.ForceStart, opts.ForceStart)
|
||||
assert.Equal(expectedOptions.DryRun, opts.DryRun)
|
||||
assert.Equal(expectedOptions.ScanMode, opts.ScanMode)
|
||||
}
|
||||
|
||||
// Test-4: getHealOptionsFromReq return error if boolean value not valid
|
||||
u, _ = url.Parse("http://localhost/api/v1/heal/bucket1?prefix=file/&recursive=nonbool&force-start=true&force-stop=true&remove=true&dry-run=true&scan=deep")
|
||||
req = &http.Request{
|
||||
URL: u,
|
||||
}
|
||||
_, err = getHealOptionsFromReq(req)
|
||||
if assert.Error(err) {
|
||||
assert.Equal("strconv.ParseBool: parsing \"nonbool\": invalid syntax", err.Error())
|
||||
}
|
||||
// Test-5: getHealOptionsFromReq return error if boolean value not valid
|
||||
u, _ = url.Parse("http://localhost/api/v1/heal/bucket1?prefix=file/&recursive=true&force-start=true&force-stop=true&remove=nonbool&dry-run=true&scan=deep")
|
||||
req = &http.Request{
|
||||
URL: u,
|
||||
}
|
||||
_, err = getHealOptionsFromReq(req)
|
||||
if assert.Error(err) {
|
||||
assert.Equal("strconv.ParseBool: parsing \"nonbool\": invalid syntax", err.Error())
|
||||
}
|
||||
// Test-6: getHealOptionsFromReq return error if boolean value not valid
|
||||
u, _ = url.Parse("http://localhost/api/v1/heal/bucket1?prefix=file/&recursive=true&force-start=nonbool&force-stop=true&remove=true&dry-run=true&scan=deep")
|
||||
req = &http.Request{
|
||||
URL: u,
|
||||
}
|
||||
_, err = getHealOptionsFromReq(req)
|
||||
if assert.Error(err) {
|
||||
assert.Equal("strconv.ParseBool: parsing \"nonbool\": invalid syntax", err.Error())
|
||||
}
|
||||
// Test-7: getHealOptionsFromReq return error if boolean value not valid
|
||||
u, _ = url.Parse("http://localhost/api/v1/heal/bucket1?prefix=file/&recursive=true&force-start=true&force-stop=nonbool&remove=true&dry-run=true&scan=deep")
|
||||
req = &http.Request{
|
||||
URL: u,
|
||||
}
|
||||
_, err = getHealOptionsFromReq(req)
|
||||
if assert.Error(err) {
|
||||
assert.Equal("strconv.ParseBool: parsing \"nonbool\": invalid syntax", err.Error())
|
||||
}
|
||||
// Test-8: getHealOptionsFromReq return error if boolean value not valid
|
||||
u, _ = url.Parse("http://localhost/api/v1/heal/bucket1?prefix=file/&recursive=true&force-start=true&force-stop=true&remove=true&dry-run=nonbool&scan=deep")
|
||||
req = &http.Request{
|
||||
URL: u,
|
||||
}
|
||||
_, err = getHealOptionsFromReq(req)
|
||||
if assert.Error(err) {
|
||||
assert.Equal("strconv.ParseBool: parsing \"nonbool\": invalid syntax", err.Error())
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,6 @@ type wsAdminClient struct {
|
||||
// ConsoleWebsocket interface of a Websocket Client
|
||||
type ConsoleWebsocket interface {
|
||||
watch(options watchOptions)
|
||||
heal(opts healOptions)
|
||||
}
|
||||
|
||||
type wsS3Client struct {
|
||||
@@ -245,20 +244,6 @@ func serveWS(w http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
go wsAdminClient.healthInfo(ctx, deadline)
|
||||
case strings.HasPrefix(wsPath, `/heal`):
|
||||
hOptions, err := getHealOptionsFromReq(req)
|
||||
if err != nil {
|
||||
ErrorWithContext(ctx, fmt.Errorf("error getting heal options: %v", err))
|
||||
closeWsConn(conn)
|
||||
return
|
||||
}
|
||||
wsAdminClient, err := newWebSocketAdminClient(conn, session)
|
||||
if err != nil {
|
||||
ErrorWithContext(ctx, err)
|
||||
closeWsConn(conn)
|
||||
return
|
||||
}
|
||||
go wsAdminClient.heal(ctx, hOptions)
|
||||
case strings.HasPrefix(wsPath, `/watch`):
|
||||
wOptions, err := getWatchOptionsFromReq(req)
|
||||
if err != nil {
|
||||
@@ -506,21 +491,6 @@ func (wsc *wsS3Client) watch(ctx context.Context, params *watchOptions) {
|
||||
sendWsCloseMessage(wsc.conn, err)
|
||||
}
|
||||
|
||||
func (wsc *wsAdminClient) heal(ctx context.Context, opts *healOptions) {
|
||||
defer func() {
|
||||
LogInfo("heal stopped")
|
||||
// close connection after return
|
||||
wsc.conn.close()
|
||||
}()
|
||||
LogInfo("heal started")
|
||||
|
||||
ctx = wsReadClientCtx(ctx, wsc.conn)
|
||||
|
||||
err := startHeal(ctx, wsc.conn, wsc.client, opts)
|
||||
|
||||
sendWsCloseMessage(wsc.conn, err)
|
||||
}
|
||||
|
||||
func (wsc *wsAdminClient) healthInfo(ctx context.Context, deadline *time.Duration) {
|
||||
defer func() {
|
||||
LogInfo("health info stopped")
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Action": ["admin:Heal"],
|
||||
"Effect": "Allow",
|
||||
"Sid": ""
|
||||
},
|
||||
{
|
||||
"Action": ["s3:ListBucket"],
|
||||
"Effect": "Allow",
|
||||
"Resource": ["arn:aws:s3:::*"],
|
||||
"Sid": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -30,7 +30,6 @@ create_policies() {
|
||||
mc admin policy create minio dashboard-$TIMESTAMP web-app/tests/policies/dashboard.json
|
||||
mc admin policy create minio diagnostics-$TIMESTAMP web-app/tests/policies/diagnostics.json
|
||||
mc admin policy create minio groups-$TIMESTAMP web-app/tests/policies/groups.json
|
||||
mc admin policy create minio heal-$TIMESTAMP web-app/tests/policies/heal.json
|
||||
mc admin policy create minio iampolicies-$TIMESTAMP web-app/tests/policies/iamPolicies.json
|
||||
mc admin policy create minio logs-$TIMESTAMP web-app/tests/policies/logs.json
|
||||
mc admin policy create minio notificationendpoints-$TIMESTAMP web-app/tests/policies/notificationEndpoints.json
|
||||
|
||||
Reference in New Issue
Block a user