Remove control command from minio binary (Fixes #3264) (#3265)

This commit is contained in:
Aditya Manthramurthy 2016-11-15 13:39:02 -08:00 committed by Harshavardhana
parent 7abcededf2
commit e216201901
15 changed files with 10 additions and 2053 deletions

View File

@ -1,277 +0,0 @@
/*
* 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"
"sync"
"time"
)
// errServerNotInitialized - server not initialized.
var errServerNotInitialized = errors.New("Server not initialized, please try again.")
// errServerVersionMismatch - server versions do not match.
var errServerVersionMismatch = errors.New("Server versions do not match.")
// errServerTimeMismatch - server times are too far apart.
var errServerTimeMismatch = errors.New("Server times are too far apart.")
/// Auth operations
// Login - login handler.
func (c *controlAPIHandlers) LoginHandler(args *RPCLoginArgs, reply *RPCLoginReply) error {
jwt, err := newJWT(defaultInterNodeJWTExpiry)
if err != nil {
return err
}
if err = jwt.Authenticate(args.Username, args.Password); err != nil {
return err
}
token, err := jwt.GenerateToken(args.Username)
if err != nil {
return err
}
reply.Token = token
reply.Timestamp = time.Now().UTC()
reply.ServerVersion = Version
return nil
}
// HealListArgs - argument for ListObjects RPC.
type HealListArgs struct {
// Authentication token generated by Login.
GenericArgs
Bucket string
Prefix string
Marker string
Delimiter string
MaxKeys int
}
// HealListReply - reply object by ListObjects RPC.
type HealListReply struct {
IsTruncated bool
NextMarker string
Objects []ObjectInfo
}
// ListObjects - list all objects that needs healing.
func (c *controlAPIHandlers) ListObjectsHealHandler(args *HealListArgs, reply *HealListReply) error {
objAPI := c.ObjectAPI()
if objAPI == nil {
return errServerNotInitialized
}
if !isRPCTokenValid(args.Token) {
return errInvalidToken
}
if !c.IsXL {
return nil
}
info, err := objAPI.ListObjectsHeal(args.Bucket, args.Prefix, args.Marker, args.Delimiter, args.MaxKeys)
if err != nil {
return err
}
reply.IsTruncated = info.IsTruncated
reply.NextMarker = info.NextMarker
reply.Objects = info.Objects
return nil
}
// HealBucketArgs - arguments for HealBucket RPC.
type HealBucketArgs struct {
// Authentication token generated by Login.
GenericArgs
// Bucket to be healed.
Bucket string
}
// Heals missing buckets across disks, if we have enough quorum.
func (c *controlAPIHandlers) HealBucketHandler(args *HealBucketArgs, reply *GenericReply) error {
objAPI := c.ObjectAPI()
if objAPI == nil {
return errServerNotInitialized
}
if !isRPCTokenValid(args.Token) {
return errInvalidToken
}
if !c.IsXL {
return nil
}
// Proceed to heal the bucket.
return objAPI.HealBucket(args.Bucket)
}
// HealObjectArgs - argument for HealObject RPC.
type HealObjectArgs struct {
// Authentication token generated by Login.
GenericArgs
// Name of the bucket where the object
// needs to be healed.
Bucket string
// Name of the object to be healed.
Objects []ObjectInfo
}
// HealObjectReply - reply by HealObject RPC.
type HealObjectReply struct {
Results []string
}
// HealObject heals 1000 objects at a time for missing chunks, missing metadata on a given bucket.
func (c *controlAPIHandlers) HealObjectsHandler(args *HealObjectArgs, reply *HealObjectReply) error {
objAPI := c.ObjectAPI()
if objAPI == nil {
return errServerNotInitialized
}
if !isRPCTokenValid(args.Token) {
return errInvalidToken
}
if !c.IsXL {
return nil
}
// Heal all objects that need healing.
var errs = make([]error, len(args.Objects))
for idx, objInfo := range args.Objects {
errs[idx] = objAPI.HealObject(args.Bucket, objInfo.Name)
}
// Get all the error causes.
var causes = make([]string, len(args.Objects))
for id, err := range errs {
if err != nil {
causes[id] = err.Error()
}
}
// Save the causes.
reply.Results = causes
return nil
}
// Heals backend storage format.
func (c *controlAPIHandlers) HealFormatHandler(args *GenericArgs, reply *GenericReply) error {
if !isRPCTokenValid(args.Token) {
return errInvalidToken
}
if !c.IsXL {
return nil
}
err := healFormatXL(c.StorageDisks)
if err != nil {
return err
}
go func() {
globalWakeupCh <- struct{}{}
}()
return err
}
// ServiceArgs - argument for Service RPC.
type ServiceArgs struct {
// Authentication token generated by Login.
GenericArgs
// Represents the type of operation server is requested
// to perform. Currently supported signals are
// stop, restart and status.
Signal serviceSignal
}
// ServiceReply - represents service operation success info.
type ServiceReply struct {
StorageInfo StorageInfo
}
// Remote procedure call, calls serviceMethod with given input args.
func (c *controlAPIHandlers) remoteServiceCall(args *ServiceArgs, replies []*ServiceReply) error {
var wg sync.WaitGroup
var errs = make([]error, len(c.RemoteControls))
// Send remote call to all neighboring peers to restart minio servers.
for index, clnt := range c.RemoteControls {
wg.Add(1)
go func(index int, client *AuthRPCClient) {
defer wg.Done()
errs[index] = client.Call("Control.ServiceHandler", args, replies[index])
errorIf(errs[index], "Unable to initiate control service request to remote node %s", client.Node())
}(index, clnt)
}
wg.Wait()
for _, err := range errs {
if err != nil {
return err
}
}
return nil
}
// Service - handler for sending service signals across many servers.
func (c *controlAPIHandlers) ServiceHandler(args *ServiceArgs, reply *ServiceReply) error {
if !isRPCTokenValid(args.Token) {
return errInvalidToken
}
objAPI := c.ObjectAPI()
if objAPI == nil {
return errServerNotInitialized
}
if args.Signal == serviceStatus {
reply.StorageInfo = objAPI.StorageInfo()
return nil
}
var replies = make([]*ServiceReply, len(c.RemoteControls))
switch args.Signal {
case serviceRestart:
if args.Remote {
// Set remote as false for remote calls.
args.Remote = false
if err := c.remoteServiceCall(args, replies); err != nil {
return err
}
}
globalServiceSignalCh <- serviceRestart
case serviceStop:
if args.Remote {
// Set remote as false for remote calls.
args.Remote = false
if err := c.remoteServiceCall(args, replies); err != nil {
return err
}
}
globalServiceSignalCh <- serviceStop
}
return nil
}
// TryInitHandler - generic RPC control handler
func (c *controlAPIHandlers) TryInitHandler(args *GenericArgs, reply *GenericReply) error {
if !isRPCTokenValid(args.Token) {
return errInvalidToken
}
if !c.IsXL {
return nil
}
go func() {
globalWakeupCh <- struct{}{}
}()
*reply = GenericReply{}
return nil
}

View File

@ -1,227 +0,0 @@
/*
* 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}} http[s]://[access_key[:secret_key]@]server_ip:port/
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))
accessKey := serverConfig.GetCredential().AccessKeyID
secretKey := serverConfig.GetCredential().SecretAccessKey
// Username and password specified in URL will override prior configuration
if parsedURL.User != nil {
accessKey = parsedURL.User.Username()
if key, set := parsedURL.User.Password(); set {
secretKey = key
}
}
authCfg := &authConfig{
accessKey: accessKey,
secretKey: secretKey,
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()
}

View File

@ -1,235 +0,0 @@
/*
* 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"
"net/url"
"path"
"strings"
"time"
"github.com/minio/cli"
"github.com/minio/mc/pkg/console"
)
var lockFlags = []cli.Flag{
cli.StringFlag{
Name: "older-than",
Usage: "Include locks older than given time.",
Value: "24h",
},
cli.BoolFlag{
Name: "verbose",
Usage: "Lists more information about locks.",
},
cli.BoolFlag{
Name: "recursive",
Usage: "Recursively clear locks.",
},
}
var lockCmd = cli.Command{
Name: "lock",
Usage: "Prints current lock information.",
Action: lockControl,
Flags: append(lockFlags, globalFlags...),
CustomHelpTemplate: `NAME:
minio control {{.Name}} - {{.Usage}}
USAGE:
minio control {{.Name}} [list|clear] http[s]://[access_key[:secret_key]@]server_ip:port/
FLAGS:
{{range .Flags}}{{.}}
{{end}}
EAMPLES:
1. List all currently active locks from all nodes. Defaults to list locks held longer than 24hrs.
$ minio control {{.Name}} list http://localhost:9000/
2. List all currently active locks from all nodes. Request locks older than 1minute.
$ minio control {{.Name}} --older-than=1m list http://localhost:9000/
3. Clear lock named 'bucket/object' (exact match).
$ minio control {{.Name}} clear http://localhost:9000/bucket/object
4. Clear all locks with names that start with 'bucket/prefix' (wildcard match).
$ minio control {{.Name}} --recursive clear http://localhost:9000/bucket/prefix
5. Clear all locks older than 10minutes.
$ minio control {{.Name}} --older-than=10m clear http://localhost:9000/
6. Clear all locks with names that start with 'bucket/a' and that are older than 1hour.
$ minio control {{.Name}} --recursive --older-than=1h clear http://localhost:9000/bucket/a
`,
}
// printLockStateVerbose - pretty prints systemLockState, additionally this filters out based on a given duration.
func printLockStateVerbose(lkStateRep map[string]SystemLockState, olderThan time.Duration) {
console.Println("Duration Server LockType LockAcquired Status LockOrigin Resource")
for server, lockState := range lkStateRep {
for _, lockInfo := range lockState.LocksInfoPerObject {
lockedResource := path.Join(lockInfo.Bucket, lockInfo.Object)
for _, lockDetails := range lockInfo.LockDetailsOnObject {
if lockDetails.Duration < olderThan {
continue
}
console.Println(lockDetails.Duration, server,
lockDetails.LockType, lockDetails.Since,
lockDetails.Status, lockDetails.LockOrigin,
lockedResource)
}
}
}
}
// printLockState - pretty prints systemLockState, additionally this filters out based on a given duration.
func printLockState(lkStateRep map[string]SystemLockState, olderThan time.Duration) {
console.Println("Duration Server LockType Resource")
for server, lockState := range lkStateRep {
for _, lockInfo := range lockState.LocksInfoPerObject {
lockedResource := path.Join(lockInfo.Bucket, lockInfo.Object)
for _, lockDetails := range lockInfo.LockDetailsOnObject {
if lockDetails.Duration < olderThan {
continue
}
console.Println(lockDetails.Duration, server,
lockDetails.LockType, lockedResource)
}
}
}
}
// clearLockState - clear locks based on a filter for a given duration and a name or prefix to match
func clearLockState(f func(bucket, object string), lkStateRep map[string]SystemLockState, olderThan time.Duration, match string, recursive bool) {
console.Println("Status Duration Server LockType Resource")
for server, lockState := range lkStateRep {
for _, lockInfo := range lockState.LocksInfoPerObject {
lockedResource := path.Join(lockInfo.Bucket, lockInfo.Object)
for _, lockDetails := range lockInfo.LockDetailsOnObject {
if lockDetails.Duration < olderThan {
continue
}
if match != "" {
if recursive {
if !strings.HasPrefix(lockedResource, match) {
continue
}
} else if lockedResource != match {
continue
}
}
f(lockInfo.Bucket, lockInfo.Object)
console.Println("CLEARED", lockDetails.Duration, server,
lockDetails.LockType, lockedResource)
}
}
}
}
// "minio control lock" entry point.
func lockControl(c *cli.Context) {
if !c.Args().Present() && len(c.Args()) != 2 {
cli.ShowCommandHelpAndExit(c, "lock", 1)
}
parsedURL, err := url.Parse(c.Args().Get(1))
fatalIf(err, "Unable to parse URL.")
accessKey := serverConfig.GetCredential().AccessKeyID
secretKey := serverConfig.GetCredential().SecretAccessKey
// Username and password specified in URL will override prior configuration
if parsedURL.User != nil {
accessKey = parsedURL.User.Username()
if key, set := parsedURL.User.Password(); set {
secretKey = key
}
}
// Parse older than string.
olderThanStr := c.String("older-than")
olderThan, err := time.ParseDuration(olderThanStr)
fatalIf(err, "Unable to parse older-than time duration.")
// Verbose flag.
verbose := c.Bool("verbose")
// Recursive flag.
recursive := c.Bool("recursive")
authCfg := &authConfig{
accessKey: accessKey,
secretKey: secretKey,
secureConn: parsedURL.Scheme == "https",
address: parsedURL.Host,
path: path.Join(reservedBucket, controlPath),
loginMethod: "Control.LoginHandler",
}
client := newAuthClient(authCfg)
args := &GenericArgs{
// This is necessary so that the remotes,
// don't end up sending requests back and forth.
Remote: true,
}
subCommand := c.Args().Get(0)
switch subCommand {
case "list":
lkStateRep := make(map[string]SystemLockState)
// Request lock info, fetches from all the nodes in the cluster.
err = client.Call("Control.LockInfo", args, &lkStateRep)
fatalIf(err, "Unable to fetch system lockInfo.")
if !verbose {
printLockState(lkStateRep, olderThan)
} else {
printLockStateVerbose(lkStateRep, olderThan)
}
case "clear":
path := parsedURL.Path
if strings.HasPrefix(path, "/") {
path = path[1:] // Strip leading slash
}
if path == "" && c.NumFlags() == 0 {
fatalIf(errors.New("Bad arguments"), "Need to either pass a path or older-than argument")
}
if !c.IsSet("older-than") { // If not set explicitly, change default to 0 instead of 24h
olderThan = 0
}
lkStateRep := make(map[string]SystemLockState)
// Request lock info, fetches from all the nodes in the cluster.
err = client.Call("Control.LockInfo", args, &lkStateRep)
fatalIf(err, "Unable to fetch system lockInfo.")
// Helper function to call server for actual removal of lock
f := func(bucket, object string) {
args := LockClearArgs{
Bucket: bucket,
Object: object,
}
reply := GenericReply{}
// Call server to clear the lock based on the name of the object.
err := client.Call("Control.LockClear", &args, &reply)
fatalIf(err, "Unable to clear lock.")
}
// Loop over all locks and determine whether to clear or not.
clearLockState(f, lkStateRep, olderThan, path, recursive)
default:
fatalIf(errInvalidArgument, "Unsupported lock control operation %s", c.Args().Get(0))
}
}

View File

@ -1,181 +0,0 @@
/*
* 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 (
"testing"
"time"
)
// Test print systemState.
func TestPrintLockState(t *testing.T) {
testLock := nsMutex.NewNSLock("testbucket", "1.txt")
testLock.Lock()
sysLockState, err := getSystemLockState()
if err != nil {
t.Fatal(err)
}
testLock.Unlock()
sysLockStateMap := map[string]SystemLockState{}
sysLockStateMap["bucket"] = sysLockState
// Print lock state.
printLockState(sysLockStateMap, 0)
// Print lock state verbose.
printLockStateVerbose(sysLockStateMap, 0)
// Does not print any lock state in normal print mode.
printLockState(sysLockStateMap, 10*time.Second)
// Does not print any lock state in debug print mode.
printLockStateVerbose(sysLockStateMap, 10*time.Second)
}
// Helper function to test equality of locks (without taking timing info into account)
func testLockStateEquality(vliLeft, vliRight VolumeLockInfo) bool {
if vliLeft.Bucket != vliRight.Bucket ||
vliLeft.Object != vliRight.Object ||
vliLeft.LocksOnObject != vliRight.LocksOnObject ||
vliLeft.LocksAcquiredOnObject != vliRight.LocksAcquiredOnObject ||
vliLeft.TotalBlockedLocks != vliRight.TotalBlockedLocks {
return false
}
return true
}
// Test clearing of locks.
func TestLockStateClear(t *testing.T) {
// Helper function to circumvent RPC call to LockClear and call msMutex.ForceUnlock immediately.
f := func(bucket, object string) {
nsMutex.ForceUnlock(bucket, object)
}
testLock := nsMutex.NewNSLock("testbucket", "1.txt")
testLock.Lock()
sysLockState, err := getSystemLockState()
if err != nil {
t.Fatal(err)
}
expectedVli := VolumeLockInfo{
Bucket: "testbucket",
Object: "1.txt",
LocksOnObject: 1,
LocksAcquiredOnObject: 1,
TotalBlockedLocks: 0,
}
// Test initial condition.
if !testLockStateEquality(expectedVli, sysLockState.LocksInfoPerObject[0]) {
t.Errorf("Expected %#v, got %#v", expectedVli, sysLockState.LocksInfoPerObject[0])
}
sysLockStateMap := map[string]SystemLockState{}
sysLockStateMap["testnode1"] = sysLockState
// Clear locks that are 10 seconds old (which is a no-op in this case)
clearLockState(f, sysLockStateMap, 10*time.Second, "", false)
if sysLockState, err = getSystemLockState(); err != nil {
t.Fatal(err)
}
if !testLockStateEquality(expectedVli, sysLockState.LocksInfoPerObject[0]) {
t.Errorf("Expected %#v, got %#v", expectedVli, sysLockState.LocksInfoPerObject[0])
}
// Clear all locks (older than 0 seconds)
clearLockState(f, sysLockStateMap, 0, "", false)
// Verify that there are no locks
if sysLockState, err = getSystemLockState(); err != nil {
t.Fatal(err)
}
if len(sysLockState.LocksInfoPerObject) != 0 {
t.Errorf("Expected no locks, got %#v", sysLockState.LocksInfoPerObject)
}
// Create another lock
blobLock := nsMutex.NewNSLock("testbucket", "blob.txt")
blobLock.RLock()
if sysLockState, err = getSystemLockState(); err != nil {
t.Fatal(err)
}
sysLockStateMap["testnode1"] = sysLockState
// Correct wildcard match but bad age.
clearLockState(f, sysLockStateMap, 10*time.Second, "testbucket/blob", true)
// Ensure lock is still there.
if sysLockState, err = getSystemLockState(); err != nil {
t.Fatal(err)
}
expectedVli.Object = "blob.txt"
if !testLockStateEquality(expectedVli, sysLockState.LocksInfoPerObject[0]) {
t.Errorf("Expected %#v, got %#v", expectedVli, sysLockState.LocksInfoPerObject[0])
}
// Clear lock based on wildcard match.
clearLockState(f, sysLockStateMap, 0, "testbucket/blob", true)
// Verify that there are no locks
if sysLockState, err = getSystemLockState(); err != nil {
t.Fatal(err)
}
if len(sysLockState.LocksInfoPerObject) != 0 {
t.Errorf("Expected no locks, got %#v", sysLockState.LocksInfoPerObject)
}
// Create yet another lock
exactLock := nsMutex.NewNSLock("testbucket", "exact.txt")
exactLock.RLock()
if sysLockState, err = getSystemLockState(); err != nil {
t.Fatal(err)
}
sysLockStateMap["testnode1"] = sysLockState
// Make sure that exact match can fail.
clearLockState(f, sysLockStateMap, 0, "testbucket/exact.txT", false)
// Ensure lock is still there.
if sysLockState, err = getSystemLockState(); err != nil {
t.Fatal(err)
}
expectedVli.Object = "exact.txt"
if !testLockStateEquality(expectedVli, sysLockState.LocksInfoPerObject[0]) {
t.Errorf("Expected %#v, got %#v", expectedVli, sysLockState.LocksInfoPerObject[0])
}
// Clear lock based on exact match.
clearLockState(f, sysLockStateMap, 0, "testbucket/exact.txt", false)
// Verify that there are no locks
if sysLockState, err = getSystemLockState(); err != nil {
t.Fatal(err)
}
if len(sysLockState.LocksInfoPerObject) != 0 {
t.Errorf("Expected no locks, got %#v", sysLockState.LocksInfoPerObject)
}
// reset lock states for further tests
initNSLock(false)
}

View File

@ -1,49 +0,0 @@
/*
* 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 "github.com/minio/cli"
// "minio control" command.
var controlCmd = cli.Command{
Name: "control",
Usage: "Control and manage minio server.",
Flags: globalFlags,
Action: mainControl,
Subcommands: []cli.Command{
lockCmd,
healCmd,
serviceCmd,
},
CustomHelpTemplate: `NAME:
{{.Name}} - {{.Usage}}
USAGE:
{{.Name}} [FLAGS] COMMAND
FLAGS:
{{range .Flags}}{{.}}
{{end}}
COMMANDS:
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
{{end}}
`,
}
func mainControl(ctx *cli.Context) {
cli.ShowAppHelp(ctx)
}

View File

@ -1,249 +0,0 @@
/*
* 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 (
"bytes"
"crypto/rand"
"os"
"path"
"testing"
"github.com/minio/cli"
)
// Test to call healControl() in control-heal-main.go
func TestControlHealMain(t *testing.T) {
// create cli app for testing
app := cli.NewApp()
app.Commands = []cli.Command{controlCmd}
// start test server
testServer := StartTestServer(t, "XL")
// schedule cleanup at the end
defer testServer.Stop()
// fetch http server endpoint
url := testServer.Server.URL
// create args to call
args := []string{"./minio", "control", "heal", url}
// run app
err := app.Run(args)
if err != nil {
t.Errorf("Control-Heal-Format-Main test failed with - %s", err.Error())
}
obj := newObjectLayerFn()
// Create "bucket"
err = obj.MakeBucket("bucket")
if err != nil {
t.Fatal(err)
}
bucket := "bucket"
object := "object"
data := make([]byte, 1*1024*1024)
length := int64(len(data))
_, err = rand.Read(data)
if err != nil {
t.Fatal(err)
}
_, err = obj.PutObject(bucket, object, length, bytes.NewReader(data), nil, "")
if err != nil {
t.Fatal(err)
}
// Remove the object - to simulate the case where the disk was down when the object was created.
err = os.RemoveAll(path.Join(testServer.Disks[0].Path, bucket, object))
if err != nil {
t.Fatal(err)
}
args = []string{"./minio", "control", "heal", url + "/bucket"}
// run app
err = app.Run(args)
if err != nil {
t.Errorf("Control-Heal-Bucket-Main test failed with - %s", err.Error())
}
args = []string{"./minio", "control", "heal", url + "/bucket/object"}
// run app
err = app.Run(args)
if err != nil {
t.Errorf("Control-Heal-Bucket-With-Prefix-Main test failed with - %s", err.Error())
}
}
// Test to call lockControl() in control-lock-main.go
func TestControlLockMain(t *testing.T) {
// Create cli app for testing
app := cli.NewApp()
app.Commands = []cli.Command{controlCmd}
// Start test server
testServer := StartTestServer(t, "XL")
// Schedule cleanup at the end
defer testServer.Stop()
// Fetch http server endpoint
url := testServer.Server.URL
// Create args to call
args := []string{"./minio", "control", "lock", "list", url}
// Run app
err := app.Run(args)
if err != nil {
t.Errorf("Control-Lock-Main test failed with - %s", err.Error())
}
}
// Test to call serviceControl(stop) in control-service-main.go
func TestControlServiceStopMain(t *testing.T) {
// create cli app for testing
app := cli.NewApp()
app.Commands = []cli.Command{controlCmd}
// Initialize done channel specifically for each tests.
globalServiceDoneCh = make(chan struct{}, 1)
// Initialize signal channel specifically for each tests.
globalServiceSignalCh = make(chan serviceSignal, 1)
// start test server
testServer := StartTestServer(t, "XL")
// schedule cleanup at the end
defer testServer.Stop()
// fetch http server endpoint
url := testServer.Server.URL
// create args to call
args := []string{"./minio", "control", "service", "stop", url}
// run app
err := app.Run(args)
if err != nil {
t.Errorf("Control-Service-Stop-Main test failed with - %s", err)
}
}
// Test to call serviceControl(status) in control-service-main.go
func TestControlServiceStatusMain(t *testing.T) {
// create cli app for testing
app := cli.NewApp()
app.Commands = []cli.Command{controlCmd}
// Initialize done channel specifically for each tests.
globalServiceDoneCh = make(chan struct{}, 1)
// Initialize signal channel specifically for each tests.
globalServiceSignalCh = make(chan serviceSignal, 1)
// start test server
testServer := StartTestServer(t, "XL")
// schedule cleanup at the end
defer testServer.Stop()
// fetch http server endpoint
url := testServer.Server.URL
// Create args to call
args := []string{"./minio", "control", "service", "status", url}
// run app
err := app.Run(args)
if err != nil {
t.Errorf("Control-Service-Status-Main test failed with - %s", err)
}
// Create args to call
args = []string{"./minio", "control", "service", "stop", url}
// run app
err = app.Run(args)
if err != nil {
t.Errorf("Control-Service-Stop-Main test failed with - %s", err)
}
}
// Test to call serviceControl(restart) in control-service-main.go
func TestControlServiceRestartMain(t *testing.T) {
// create cli app for testing
app := cli.NewApp()
app.Commands = []cli.Command{controlCmd}
// Initialize done channel specifically for each tests.
globalServiceDoneCh = make(chan struct{}, 1)
// Initialize signal channel specifically for each tests.
globalServiceSignalCh = make(chan serviceSignal, 1)
// start test server
testServer := StartTestServer(t, "XL")
// schedule cleanup at the end
defer testServer.Stop()
// fetch http server endpoint
url := testServer.Server.URL
// Create args to call
args := []string{"./minio", "control", "service", "restart", url}
// run app
err := app.Run(args)
if err != nil {
t.Errorf("Control-Service-Restart-Main test failed with - %s", err)
}
// Initialize done channel specifically for each tests.
globalServiceDoneCh = make(chan struct{}, 1)
// Initialize signal channel specifically for each tests.
globalServiceSignalCh = make(chan serviceSignal, 1)
// Create args to call
args = []string{"./minio", "control", "service", "stop", url}
// run app
err = app.Run(args)
if err != nil {
t.Errorf("Control-Service-Stop-Main test failed with - %s", err)
}
}
// NOTE: This test practically always passes, but its the only way to
// execute mainControl in a test situation
func TestControlMain(t *testing.T) {
// create cli app for testing
app := cli.NewApp()
app.Commands = []cli.Command{controlCmd}
// create args to call
args := []string{"./minio", "control"}
// run app
err := app.Run(args)
if err != nil {
t.Errorf("Control-Main test failed with - %s", err)
}
}

View File

@ -1,90 +0,0 @@
/*
* 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 (
"net/rpc"
"path"
router "github.com/gorilla/mux"
)
// Routes paths for "minio control" commands.
const (
controlPath = "/control"
)
// Initializes remote control clients for making remote requests.
func initRemoteControlClients(srvCmdConfig serverCmdConfig) []*AuthRPCClient {
if !globalIsDistXL {
return nil
}
// Initialize auth rpc clients.
var remoteControlClnts []*AuthRPCClient
localMap := make(map[string]int)
for _, ep := range srvCmdConfig.endpoints {
// Validates if remote disk is local.
if isLocalStorage(ep) {
continue
}
if localMap[ep.Host] == 1 {
continue
}
localMap[ep.Host]++
remoteControlClnts = append(remoteControlClnts, newAuthClient(&authConfig{
accessKey: serverConfig.GetCredential().AccessKeyID,
secretKey: serverConfig.GetCredential().SecretAccessKey,
secureConn: isSSL(),
address: ep.Host,
path: path.Join(reservedBucket, controlPath),
loginMethod: "Control.LoginHandler",
}))
}
return remoteControlClnts
}
// Represents control object which provides handlers for control
// operations on server.
type controlAPIHandlers struct {
ObjectAPI func() ObjectLayer
IsXL bool
RemoteControls []*AuthRPCClient
LocalNode string
StorageDisks []StorageAPI
}
// Register control RPC handlers.
func registerControlRPCRouter(mux *router.Router, srvCmdConfig serverCmdConfig) (err error) {
// Initialize Control.
ctrlHandlers := &controlAPIHandlers{
ObjectAPI: newObjectLayerFn,
IsXL: globalIsDistXL || len(srvCmdConfig.storageDisks) > 1,
RemoteControls: initRemoteControlClients(srvCmdConfig),
LocalNode: getLocalAddress(srvCmdConfig),
StorageDisks: srvCmdConfig.storageDisks,
}
ctrlRPCServer := rpc.NewServer()
err = ctrlRPCServer.RegisterName("Control", ctrlHandlers)
if err != nil {
return traceError(err)
}
ctrlRouter := mux.NewRoute().PathPrefix(reservedBucket).Subrouter()
ctrlRouter.Path(controlPath).Handler(ctrlRPCServer)
return nil
}

View File

@ -1,94 +0,0 @@
/*
* 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 (
"net/url"
"testing"
)
// Tests initialization of remote controller clients.
func TestInitRemoteControlClients(t *testing.T) {
rootPath, err := newTestConfig("us-east-1")
if err != nil {
t.Fatal("Unable to initialize config", err)
}
defer removeAll(rootPath)
testCases := []struct {
isDistXL bool
srvCmdConfig serverCmdConfig
totalClients int
}{
// Test - 1 no allocation if server config is not distributed XL.
{
isDistXL: false,
srvCmdConfig: serverCmdConfig{},
totalClients: 0,
},
// Test - 2 two clients allocated with 4 disks with 2 disks on same node each.
{
isDistXL: true,
srvCmdConfig: serverCmdConfig{
endpoints: []*url.URL{{
Scheme: "http",
Host: "10.1.10.1:9000",
Path: "/mnt/disk1",
}, {
Scheme: "http",
Host: "10.1.10.1:9000", Path: "/mnt/disk2",
}, {
Scheme: "http",
Host: "10.1.10.2:9000", Path: "/mnt/disk1",
}, {
Scheme: "http",
Host: "10.1.10.2:9000", Path: "/mnt/disk2"},
},
},
totalClients: 2,
},
// Test - 3 4 clients allocated with 4 disks with 1 disk on each node.
{
isDistXL: true,
srvCmdConfig: serverCmdConfig{
endpoints: []*url.URL{{
Scheme: "http",
Host: "10.1.10.1:9000", Path: "/mnt/disk1",
}, {
Scheme: "http",
Host: "10.1.10.2:9000", Path: "/mnt/disk2",
}, {
Scheme: "http",
Host: "10.1.10.3:9000", Path: "/mnt/disk1",
}, {
Scheme: "http",
Host: "10.1.10.4:9000", Path: "/mnt/disk2"},
},
},
totalClients: 4,
},
}
// Evaluate and validate all test cases.
for i, testCase := range testCases {
globalIsDistXL = testCase.isDistXL
rclients := initRemoteControlClients(testCase.srvCmdConfig)
if len(rclients) != testCase.totalClients {
t.Errorf("Test %d, Expected %d, got %d RPC clients.", i+1, testCase.totalClients, len(rclients))
}
}
}

View File

@ -1,106 +0,0 @@
/*
* 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 (
"net/url"
"path"
"github.com/minio/cli"
"github.com/minio/mc/pkg/console"
)
var serviceCmd = cli.Command{
Name: "service",
Usage: "Service command line to manage Minio server.",
Action: serviceControl,
Flags: globalFlags,
CustomHelpTemplate: `NAME:
minio control {{.Name}} - {{.Usage}}
USAGE:
minio control {{.Name}} [status|restart|stop] http[s]://[access_key[:secret_key]@]server_ip:port/
FLAGS:
{{range .Flags}}{{.}}
{{end}}
EXAMPLES:
1. Prints current status information of the cluster.
$ minio control service status http://10.1.10.92:9000/
2. Restarts the url and all the servers in the cluster.
$ minio control service restart http://localhost:9000/
3. Shuts down the url and all the servers in the cluster.
$ minio control service stop http://localhost:9000/
`,
}
// "minio control service" entry point.
func serviceControl(c *cli.Context) {
if !c.Args().Present() && len(c.Args()) != 2 {
cli.ShowCommandHelpAndExit(c, "service", 1)
}
var signal serviceSignal
switch c.Args().Get(0) {
case "status":
signal = serviceStatus
case "restart":
signal = serviceRestart
case "stop":
signal = serviceStop
default:
fatalIf(errInvalidArgument, "Unrecognized service %s", c.Args().Get(0))
}
parsedURL, err := url.Parse(c.Args().Get(1))
fatalIf(err, "Unable to parse URL %s", c.Args().Get(1))
accessKey := serverConfig.GetCredential().AccessKeyID
secretKey := serverConfig.GetCredential().SecretAccessKey
// Username and password specified in URL will override prior configuration
if parsedURL.User != nil {
accessKey = parsedURL.User.Username()
if key, set := parsedURL.User.Password(); set {
secretKey = key
}
}
authCfg := &authConfig{
accessKey: accessKey,
secretKey: secretKey,
secureConn: parsedURL.Scheme == "https",
address: parsedURL.Host,
path: path.Join(reservedBucket, controlPath),
loginMethod: "Control.LoginHandler",
}
client := newAuthClient(authCfg)
args := &ServiceArgs{
Signal: signal,
}
// This is necessary so that the remotes,
// don't end up sending requests back and forth.
args.Remote = true
reply := &ServiceReply{}
err = client.Call("Control.ServiceHandler", args, reply)
fatalIf(err, "Service command %s failed for %s", c.Args().Get(0), parsedURL.Host)
if signal == serviceStatus {
console.Println(getStorageInfoMsg(reply.StorageInfo))
}
}

View File

@ -1,388 +0,0 @@
/*
* Minio Cloud Storage, (C) 2015, 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 (
"path"
"strconv"
"strings"
"sync"
"testing"
"time"
)
// API suite container common to both FS and XL.
type TestRPCControlSuite struct {
serverType string
testServer TestServer
testAuthConf *authConfig
}
// Setting up the test suite.
// Starting the Test server with temporary FS backend.
func (s *TestRPCControlSuite) SetUpSuite(c *testing.T) {
s.testServer = StartTestControlRPCServer(c, s.serverType)
s.testAuthConf = &authConfig{
address: s.testServer.Server.Listener.Addr().String(),
accessKey: s.testServer.AccessKey,
secretKey: s.testServer.SecretKey,
path: path.Join(reservedBucket, controlPath),
loginMethod: "Control.LoginHandler",
}
}
// No longer used with gocheck, but used in explicit teardown code in
// each test function. // Called implicitly by "gopkg.in/check.v1"
// after all tests are run.
func (s *TestRPCControlSuite) TearDownSuite(c *testing.T) {
s.testServer.Stop()
}
func TestRPCControlLock(t *testing.T) {
//setup code
s := &TestRPCControlSuite{serverType: "XL"}
s.SetUpSuite(t)
//run test
s.testRPCControlLock(t)
//teardown code
s.TearDownSuite(t)
}
// Tests to validate the correctness of lock instrumentation control RPC end point.
func (s *TestRPCControlSuite) testRPCControlLock(c *testing.T) {
expectedResult := []lockStateCase{
// Test case - 1.
// Case where 10 read locks are held.
// Entry for any of the 10 reads locks has to be found.
// Since they held in a loop, Lock origin for first 10 read locks (opsID 0-9) should be the same.
{
volume: "my-bucket",
path: "my-object",
opsID: "0",
readLock: true,
lockOrigin: "[lock held] in github.com/minio/minio/cmd.TestLockStats[/Users/hackintoshrao/mycode/go/src/github.com/minio/minio/cmd/namespace-lock_test.go:298]",
// expected metrics.
expectedErr: nil,
expectedLockStatus: "Running",
expectedGlobalLockCount: 10,
expectedRunningLockCount: 10,
expectedBlockedLockCount: 0,
expectedVolPathLockCount: 10,
expectedVolPathRunningCount: 10,
expectedVolPathBlockCount: 0,
},
// Test case 2.
// Testing the existence of entry for the last read lock (read lock with opsID "9").
{
volume: "my-bucket",
path: "my-object",
opsID: "9",
readLock: true,
lockOrigin: "[lock held] in github.com/minio/minio/cmd.TestLockStats[/Users/hackintoshrao/mycode/go/src/github.com/minio/minio/cmd/namespace-lock_test.go:298]",
// expected metrics.
expectedErr: nil,
expectedLockStatus: "Running",
expectedGlobalLockCount: 10,
expectedRunningLockCount: 10,
expectedBlockedLockCount: 0,
expectedVolPathLockCount: 10,
expectedVolPathRunningCount: 10,
expectedVolPathBlockCount: 0,
},
// Test case 3.
// Hold a write lock, and it should block since 10 read locks
// on <"my-bucket", "my-object"> are still held.
{
volume: "my-bucket",
path: "my-object",
opsID: "10",
readLock: false,
lockOrigin: "[lock held] in github.com/minio/minio/cmd.TestLockStats[/Users/hackintoshrao/mycode/go/src/github.com/minio/minio/cmd/namespace-lock_test.go:298]",
// expected metrics.
expectedErr: nil,
expectedLockStatus: "Blocked",
expectedGlobalLockCount: 11,
expectedRunningLockCount: 10,
expectedBlockedLockCount: 1,
expectedVolPathLockCount: 11,
expectedVolPathRunningCount: 10,
expectedVolPathBlockCount: 1,
},
// Test case 4.
// Expected result when all the read locks are released and the blocked write lock acquires the lock.
{
volume: "my-bucket",
path: "my-object",
opsID: "10",
readLock: false,
lockOrigin: "[lock held] in github.com/minio/minio/cmd.TestLockStats[/Users/hackintoshrao/mycode/go/src/github.com/minio/minio/cmd/namespace-lock_test.go:298]",
// expected metrics.
expectedErr: nil,
expectedLockStatus: "Running",
expectedGlobalLockCount: 1,
expectedRunningLockCount: 1,
expectedBlockedLockCount: 0,
expectedVolPathLockCount: 1,
expectedVolPathRunningCount: 1,
expectedVolPathBlockCount: 0,
},
// Test case - 5.
// At the end after locks are released, its verified whether the counters are set to 0.
{
volume: "my-bucket",
path: "my-object",
// expected metrics.
expectedErr: nil,
expectedLockStatus: "Blocked",
expectedGlobalLockCount: 0,
expectedRunningLockCount: 0,
expectedBlockedLockCount: 0,
},
}
// used to make sure that the tests don't end till locks held in other go routines are released.
var wg sync.WaitGroup
// Hold 5 read locks. We should find the info about these in the RPC response.
// hold 10 read locks.
// Then call the RPC control end point for obtaining lock instrumentation info.
for i := 0; i < 10; i++ {
nsMutex.RLock("my-bucket", "my-object", strconv.Itoa(i))
}
client := newAuthClient(s.testAuthConf)
defer client.Close()
args := &GenericArgs{}
reply := make(map[string]*SystemLockState)
// Call the lock instrumentation RPC end point.
err := client.Call("Control.LockInfo", args, &reply)
if err != nil {
c.Errorf("Add: expected no error but got string %q", err.Error())
}
// expected lock info.
expectedLockStats := expectedResult[0]
// verify the actual lock info with the expected one.
// verify the existence entry for first read lock (read lock with opsID "0").
verifyRPCLockInfoResponse(expectedLockStats, reply, c, 1)
expectedLockStats = expectedResult[1]
// verify the actual lock info with the expected one.
// verify the existence entry for last read lock (read lock with opsID "9").
verifyRPCLockInfoResponse(expectedLockStats, reply, c, 2)
// now hold a write lock in a different go routine and it should block since 10 read locks are
// still held.
wg.Add(1)
go func() {
defer wg.Done()
// blocks till all read locks are released.
nsMutex.Lock("my-bucket", "my-object", strconv.Itoa(10))
// Once the above attempt to lock is unblocked/acquired, we verify the stats and release the lock.
expectedWLockStats := expectedResult[3]
// Since the write lock acquired here, the number of blocked locks should reduce by 1 and
// count of running locks should increase by 1.
// Call the RPC control handle to fetch the lock instrumentation info.
reply = make(map[string]*SystemLockState)
// Call the lock instrumentation RPC end point.
err = client.Call("Control.LockInfo", args, &reply)
if err != nil {
c.Errorf("Add: expected no error but got string %q", err.Error())
}
verifyRPCLockInfoResponse(expectedWLockStats, reply, c, 4)
// release the write lock.
nsMutex.Unlock("my-bucket", "my-object", strconv.Itoa(10))
}()
// waiting for a second so that the attempt to acquire the write lock in
// the above go routines gets blocked.
time.Sleep(1 * time.Second)
// The write lock should have got blocked by now,
// check whether the entry for one blocked lock exists.
expectedLockStats = expectedResult[2]
// Call the RPC control handle to fetch the lock instrumentation info.
reply = make(map[string]*SystemLockState)
// Call the lock instrumentation RPC end point.
err = client.Call("Control.LockInfo", args, &reply)
if err != nil {
c.Errorf("Add: expected no error but got string %q", err.Error())
}
verifyRPCLockInfoResponse(expectedLockStats, reply, c, 3)
// Release all the read locks held.
// the blocked write lock in the above go routines should get unblocked.
for i := 0; i < 10; i++ {
nsMutex.RUnlock("my-bucket", "my-object", strconv.Itoa(i))
}
wg.Wait()
// Since all the locks are released. There should not be any entry in the lock info.
// and all the counters should be set to 0.
reply = make(map[string]*SystemLockState)
// Call the lock instrumentation RPC end point.
err = client.Call("Control.LockInfo", args, &reply)
if err != nil {
c.Errorf("Add: expected no error but got string %q", err.Error())
}
for _, rpcLockInfo := range reply {
if rpcLockInfo.TotalAcquiredLocks != 0 && rpcLockInfo.TotalLocks != 0 && rpcLockInfo.TotalBlockedLocks != 0 {
c.Fatalf("The counters are not reset properly after all locks are released")
}
if len(rpcLockInfo.LocksInfoPerObject) != 0 {
c.Fatalf("Since all locks are released there shouldn't have been any lock info entry, but found %d", len(rpcLockInfo.LocksInfoPerObject))
}
}
}
func TestControlHealDiskMetadataH(t *testing.T) {
// Setup code
s := &TestRPCControlSuite{serverType: "XL"}
s.SetUpSuite(t)
// Run test
s.testControlHealFormatH(t)
// Teardown code
s.TearDownSuite(t)
}
// TestControlHandlerHealFormat - Registers and call the `HealFormatHandler`, asserts to validate the success.
func (s *TestRPCControlSuite) testControlHealFormatH(c *testing.T) {
// The suite has already started the test RPC server, just send RPC calls.
client := newAuthClient(s.testAuthConf)
defer client.Close()
args := &GenericArgs{}
reply := &GenericReply{}
err := client.Call("Control.HealFormatHandler", args, reply)
if err != nil {
c.Errorf("Test failed with <ERROR> %s", err)
}
}
func TestControlHealObjectH(t *testing.T) {
// Setup code
s := &TestRPCControlSuite{serverType: "XL"}
s.SetUpSuite(t)
// Run test
s.testControlHealObjectsH(t)
// Teardown code
s.TearDownSuite(t)
}
func (s *TestRPCControlSuite) testControlHealObjectsH(t *testing.T) {
client := newAuthClient(s.testAuthConf)
defer client.Close()
objAPI := newObjectLayerFn()
err := objAPI.MakeBucket("testbucket")
if err != nil {
t.Fatalf("Create bucket failed with <ERROR> %s", err)
}
datum := strings.NewReader("a")
_, err = objAPI.PutObject("testbucket", "testobject1", 1, datum, nil, "")
if err != nil {
t.Fatalf("Put object failed with <ERROR> %s", err)
}
datum = strings.NewReader("a")
_, err = objAPI.PutObject("testbucket", "testobject2", 1, datum, nil, "")
if err != nil {
t.Fatalf("Put object failed with <ERROR> %s", err)
}
args := &HealObjectArgs{
Bucket: "testbucket",
Objects: []ObjectInfo{
{
Name: "testobject1",
}, {
Name: "testobject2",
},
},
}
reply := &HealObjectReply{}
err = client.Call("Control.HealObjectsHandler", args, reply)
if err != nil {
t.Errorf("Test failed with <ERROR> %s", err)
}
}
func TestControlListObjectsHealH(t *testing.T) {
// Setup code
s := &TestRPCControlSuite{serverType: "XL"}
s.SetUpSuite(t)
// Run test
s.testControlListObjectsHealH(t)
// Teardown code
s.TearDownSuite(t)
}
func (s *TestRPCControlSuite) testControlListObjectsHealH(t *testing.T) {
client := newAuthClient(s.testAuthConf)
defer client.Close()
objAPI := newObjectLayerFn()
// Create a bucket
err := objAPI.MakeBucket("testbucket")
if err != nil {
t.Fatalf("Create bucket failed - %s", err)
}
r := strings.NewReader("0")
_, err = objAPI.PutObject("testbucket", "testObj-0", 1, r, nil, "")
if err != nil {
t.Fatalf("Object creation failed - %s", err)
}
args := &HealListArgs{
GenericArgs{}, "testbucket", "testObj-",
"", "", 100,
}
reply := &GenericReply{}
err = client.Call("Control.ListObjectsHealHandler", args, reply)
if err != nil {
t.Errorf("Test failed - %s", err)
}
}

View File

@ -16,10 +16,7 @@
package cmd
import (
"sync"
"time"
)
import "time"
// SystemLockState - Structure to fill the lock state of entire object storage.
// That is the total locks held, total calls blocked on locks and state of all the locks for the entire system.
@ -94,93 +91,3 @@ func getSystemLockState() (SystemLockState, error) {
}
return lockState, nil
}
// Remote procedure call, calls LockInfo handler with given input args.
func (c *controlAPIHandlers) remoteLockInfoCall(args *GenericArgs, replies []SystemLockState) error {
var wg sync.WaitGroup
var errs = make([]error, len(c.RemoteControls))
// Send remote call to all neighboring peers fetch control lock info.
for index, clnt := range c.RemoteControls {
wg.Add(1)
go func(index int, client *AuthRPCClient) {
defer wg.Done()
errs[index] = client.Call("Control.RemoteLockInfo", args, &replies[index])
errorIf(errs[index], "Unable to initiate control lockInfo request to remote node %s", client.Node())
}(index, clnt)
}
wg.Wait()
for _, err := range errs {
if err != nil {
return err
}
}
return nil
}
// RemoteLockInfo - RPC control handler for `minio control lock`, used internally by LockInfo to
// make calls to neighboring peers.
func (c *controlAPIHandlers) RemoteLockInfo(args *GenericArgs, reply *SystemLockState) error {
if !isRPCTokenValid(args.Token) {
return errInvalidToken
}
// Obtain the lock state information of the local system.
lockState, err := getSystemLockState()
// In case of error, return err to the RPC client.
if err != nil {
return err
}
*reply = lockState
return nil
}
// LockInfo - RPC control handler for `minio control lock list`. Returns the info of the locks held in the cluster.
func (c *controlAPIHandlers) LockInfo(args *GenericArgs, reply *map[string]SystemLockState) error {
if !isRPCTokenValid(args.Token) {
return errInvalidToken
}
var replies = make([]SystemLockState, len(c.RemoteControls))
if args.Remote {
// Fetch lock states from all the remote peers.
args.Remote = false
if err := c.remoteLockInfoCall(args, replies); err != nil {
return err
}
}
rep := make(map[string]SystemLockState)
// The response containing the lock info.
for index, client := range c.RemoteControls {
rep[client.Node()] = replies[index]
}
// Obtain the lock state information of the local system.
lockState, err := getSystemLockState()
// In case of error, return err to the RPC client.
if err != nil {
return err
}
// Save the local node lock state.
rep[c.LocalNode] = lockState
// Set the reply.
*reply = rep
// Success.
return nil
}
// LockClearArgs - arguments for LockClear handler
type LockClearArgs struct {
GenericArgs
Bucket string
Object string
}
// LockClear - RPC control handler for `minio control lock clear`.
func (c *controlAPIHandlers) LockClear(args *LockClearArgs, reply *GenericReply) error {
if !isRPCTokenValid(args.Token) {
return errInvalidToken
}
nsMutex.ForceUnlock(args.Bucket, args.Object)
*reply = GenericReply{}
return nil
}

View File

@ -114,7 +114,6 @@ func registerApp() *cli.App {
registerCommand(serverCmd)
registerCommand(versionCmd)
registerCommand(updateCmd)
registerCommand(controlCmd)
// Set up app.
app := cli.NewApp()

View File

@ -101,12 +101,6 @@ func configureServerHandler(srvCmdConfig serverCmdConfig) (http.Handler, error)
return nil, err
}
// Register controller rpc router.
err = registerControlRPCRouter(mux, srvCmdConfig)
if err != nil {
return nil, err
}
// Register RPC router for web related calls.
if err = registerBrowserPeerRPCRouter(mux); err != nil {
return nil, err

View File

@ -420,62 +420,6 @@ func StartTestPeersRPCServer(t TestErrHandler, instanceType string) TestServer {
return testRPCServer
}
// Initializes control RPC endpoints.
// The object Layer will be a temp back used for testing purpose.
func initTestControlRPCEndPoint(srvCmdConfig serverCmdConfig) http.Handler {
// Initialize router.
muxRouter := router.NewRouter()
registerControlRPCRouter(muxRouter, srvCmdConfig)
return muxRouter
}
// StartTestControlRPCServer - Creates a temp XL/FS backend and initializes control RPC end points,
// then starts a test server with those control RPC end points registered.
func StartTestControlRPCServer(t TestErrHandler, instanceType string) TestServer {
// create temporary backend for the test server.
nDisks := 16
disks, err := getRandomDisks(nDisks)
if err != nil {
t.Fatal("Failed to create disks for the backend")
}
endpoints, err := parseStorageEndpoints(disks)
if err != nil {
t.Fatalf("%s", err)
}
root, err := newTestConfig("us-east-1")
if err != nil {
t.Fatalf("%s", err)
}
// create an instance of TestServer.
testRPCServer := TestServer{}
// Get credential.
credentials := serverConfig.GetCredential()
testRPCServer.Root = root
testRPCServer.Disks = endpoints
testRPCServer.AccessKey = credentials.AccessKeyID
testRPCServer.SecretKey = credentials.SecretAccessKey
// create temporary backend for the test server.
objLayer, storageDisks, err := initObjectLayer(endpoints)
if err != nil {
t.Fatalf("Failed obtaining Temp Backend: <ERROR> %s", err)
}
globalObjLayerMutex.Lock()
globalObjectAPI = objLayer
globalObjLayerMutex.Unlock()
// Run TestServer.
testRPCServer.Server = httptest.NewServer(initTestControlRPCEndPoint(serverCmdConfig{
storageDisks: storageDisks,
}))
return testRPCServer
}
// Configure the server for the test run.
func newTestConfig(bucketLocation string) (rootPath string, err error) {
// Get test root.

View File

@ -41,3 +41,12 @@ var errDataTooLarge = errors.New("Object size larger than allowed limit")
// When upload object size is less than what was expected.
var errDataTooSmall = errors.New("Object size smaller than expected")
// errServerNotInitialized - server not initialized.
var errServerNotInitialized = errors.New("Server not initialized, please try again.")
// errServerVersionMismatch - server versions do not match.
var errServerVersionMismatch = errors.New("Server versions do not match.")
// errServerTimeMismatch - server times are too far apart.
var errServerTimeMismatch = errors.New("Server times are too far apart.")