mirror of
https://github.com/minio/minio.git
synced 2024-12-24 06:05:55 -05:00
Adopt dsync interface changes and major cleanup on RPC server/client.
* Rename GenericArgs to AuthRPCArgs * Rename GenericReply to AuthRPCReply * Remove authConfig.loginMethod and add authConfig.ServiceName * Rename loginServer to AuthRPCServer * Rename RPCLoginArgs to LoginRPCArgs * Rename RPCLoginReply to LoginRPCReply * Version and RequestTime are added to LoginRPCArgs and verified by server side, not client side. * Fix data race in lockMaintainence loop.
This commit is contained in:
parent
cde6496172
commit
6d10f4c19a
@ -55,15 +55,15 @@ func (lc localAdminClient) Restart() error {
|
|||||||
|
|
||||||
// Stop - Sends stop command to remote server via RPC.
|
// Stop - Sends stop command to remote server via RPC.
|
||||||
func (rc remoteAdminClient) Stop() error {
|
func (rc remoteAdminClient) Stop() error {
|
||||||
args := GenericArgs{}
|
args := AuthRPCArgs{}
|
||||||
reply := GenericReply{}
|
reply := AuthRPCReply{}
|
||||||
return rc.Call("Service.Shutdown", &args, &reply)
|
return rc.Call("Service.Shutdown", &args, &reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart - Sends restart command to remote server via RPC.
|
// Restart - Sends restart command to remote server via RPC.
|
||||||
func (rc remoteAdminClient) Restart() error {
|
func (rc remoteAdminClient) Restart() error {
|
||||||
args := GenericArgs{}
|
args := AuthRPCArgs{}
|
||||||
reply := GenericReply{}
|
reply := AuthRPCReply{}
|
||||||
return rc.Call("Service.Restart", &args, &reply)
|
return rc.Call("Service.Restart", &args, &reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +90,7 @@ func makeAdminPeers(eps []*url.URL) adminPeers {
|
|||||||
})
|
})
|
||||||
seenAddr[globalMinioAddr] = true
|
seenAddr[globalMinioAddr] = true
|
||||||
|
|
||||||
|
serverCred := serverConfig.GetCredential()
|
||||||
// iterate over endpoints to find new remote peers and add
|
// iterate over endpoints to find new remote peers and add
|
||||||
// them to ret.
|
// them to ret.
|
||||||
for _, ep := range eps {
|
for _, ep := range eps {
|
||||||
@ -100,17 +101,17 @@ func makeAdminPeers(eps []*url.URL) adminPeers {
|
|||||||
// Check if the remote host has been added already
|
// Check if the remote host has been added already
|
||||||
if !seenAddr[ep.Host] {
|
if !seenAddr[ep.Host] {
|
||||||
cfg := authConfig{
|
cfg := authConfig{
|
||||||
accessKey: serverConfig.GetCredential().AccessKey,
|
accessKey: serverCred.AccessKey,
|
||||||
secretKey: serverConfig.GetCredential().SecretKey,
|
secretKey: serverCred.SecretKey,
|
||||||
address: ep.Host,
|
serverAddr: ep.Host,
|
||||||
secureConn: isSSL(),
|
secureConn: isSSL(),
|
||||||
path: path.Join(reservedBucket, servicePath),
|
serviceEndpoint: path.Join(reservedBucket, servicePath),
|
||||||
loginMethod: "Service.LoginHandler",
|
serviceName: "Service",
|
||||||
}
|
}
|
||||||
|
|
||||||
servicePeers = append(servicePeers, adminPeer{
|
servicePeers = append(servicePeers, adminPeer{
|
||||||
addr: ep.Host,
|
addr: ep.Host,
|
||||||
svcClnt: &remoteAdminClient{newAuthClient(&cfg)},
|
svcClnt: &remoteAdminClient{newAuthRPCClient(cfg)},
|
||||||
})
|
})
|
||||||
seenAddr[ep.Host] = true
|
seenAddr[ep.Host] = true
|
||||||
}
|
}
|
||||||
|
@ -27,23 +27,25 @@ const servicePath = "/admin/service"
|
|||||||
// serviceCmd - exports RPC methods for service status, stop and
|
// serviceCmd - exports RPC methods for service status, stop and
|
||||||
// restart commands.
|
// restart commands.
|
||||||
type serviceCmd struct {
|
type serviceCmd struct {
|
||||||
loginServer
|
AuthRPCServer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown - Shutdown this instance of minio server.
|
// Shutdown - Shutdown this instance of minio server.
|
||||||
func (s *serviceCmd) Shutdown(args *GenericArgs, reply *GenericReply) error {
|
func (s *serviceCmd) Shutdown(args *AuthRPCArgs, reply *AuthRPCReply) error {
|
||||||
if !isAuthTokenValid(args.Token) {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return errInvalidToken
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
globalServiceSignalCh <- serviceStop
|
globalServiceSignalCh <- serviceStop
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart - Restart this instance of minio server.
|
// Restart - Restart this instance of minio server.
|
||||||
func (s *serviceCmd) Restart(args *GenericArgs, reply *GenericReply) error {
|
func (s *serviceCmd) Restart(args *AuthRPCArgs, reply *AuthRPCReply) error {
|
||||||
if !isAuthTokenValid(args.Token) {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return errInvalidToken
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
globalServiceSignalCh <- serviceRestart
|
globalServiceSignalCh <- serviceRestart
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -30,9 +30,14 @@ func testAdminCmd(cmd cmdType, t *testing.T) {
|
|||||||
|
|
||||||
adminServer := serviceCmd{}
|
adminServer := serviceCmd{}
|
||||||
creds := serverConfig.GetCredential()
|
creds := serverConfig.GetCredential()
|
||||||
reply := RPCLoginReply{}
|
args := LoginRPCArgs{
|
||||||
args := RPCLoginArgs{Username: creds.AccessKey, Password: creds.SecretKey}
|
Username: creds.AccessKey,
|
||||||
err = adminServer.LoginHandler(&args, &reply)
|
Password: creds.SecretKey,
|
||||||
|
Version: Version,
|
||||||
|
RequestTime: time.Now().UTC(),
|
||||||
|
}
|
||||||
|
reply := LoginRPCReply{}
|
||||||
|
err = adminServer.Login(&args, &reply)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to login to admin server - %v", err)
|
t.Fatalf("Failed to login to admin server - %v", err)
|
||||||
}
|
}
|
||||||
@ -42,37 +47,16 @@ func testAdminCmd(cmd cmdType, t *testing.T) {
|
|||||||
<-globalServiceSignalCh
|
<-globalServiceSignalCh
|
||||||
}()
|
}()
|
||||||
|
|
||||||
validToken := reply.Token
|
ga := AuthRPCArgs{AuthToken: reply.AuthToken, RequestTime: time.Now().UTC()}
|
||||||
timeNow := time.Now().UTC()
|
genReply := AuthRPCReply{}
|
||||||
testCases := []struct {
|
switch cmd {
|
||||||
ga GenericArgs
|
case stopCmd:
|
||||||
expectedErr error
|
if err = adminServer.Shutdown(&ga, &genReply); err != nil {
|
||||||
}{
|
t.Errorf("stopCmd: Expected: <nil>, got: %v", err)
|
||||||
// Valid case
|
}
|
||||||
{
|
case restartCmd:
|
||||||
ga: GenericArgs{Token: validToken, Timestamp: timeNow},
|
if err = adminServer.Restart(&ga, &genReply); err != nil {
|
||||||
expectedErr: nil,
|
t.Errorf("restartCmd: Expected: <nil>, got: %v", err)
|
||||||
},
|
|
||||||
// Invalid token
|
|
||||||
{
|
|
||||||
ga: GenericArgs{Token: "invalidToken", Timestamp: timeNow},
|
|
||||||
expectedErr: errInvalidToken,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
genReply := GenericReply{}
|
|
||||||
for i, test := range testCases {
|
|
||||||
switch cmd {
|
|
||||||
case stopCmd:
|
|
||||||
err = adminServer.Shutdown(&test.ga, &genReply)
|
|
||||||
if err != test.expectedErr {
|
|
||||||
t.Errorf("Test %d: Expected error %v but received %v", i+1, test.expectedErr, err)
|
|
||||||
}
|
|
||||||
case restartCmd:
|
|
||||||
err = adminServer.Restart(&test.ga, &genReply)
|
|
||||||
if err != test.expectedErr {
|
|
||||||
t.Errorf("Test %d: Expected error %v but received %v", i+1, test.expectedErr, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,151 +26,98 @@ import (
|
|||||||
// giving up on the remote RPC entirely.
|
// giving up on the remote RPC entirely.
|
||||||
const globalAuthRPCRetryThreshold = 1
|
const globalAuthRPCRetryThreshold = 1
|
||||||
|
|
||||||
// GenericReply represents any generic RPC reply.
|
// authConfig requires to make new AuthRPCClient.
|
||||||
type GenericReply struct{}
|
|
||||||
|
|
||||||
// GenericArgs represents any generic RPC arguments.
|
|
||||||
type GenericArgs struct {
|
|
||||||
Token string // Used to authenticate every RPC call.
|
|
||||||
// Used to verify if the RPC call was issued between
|
|
||||||
// the same Login() and disconnect event pair.
|
|
||||||
Timestamp time.Time
|
|
||||||
|
|
||||||
// Indicates if args should be sent to remote peers as well.
|
|
||||||
Remote bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetToken - sets the token to the supplied value.
|
|
||||||
func (ga *GenericArgs) SetToken(token string) {
|
|
||||||
ga.Token = token
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTimestamp - sets the timestamp to the supplied value.
|
|
||||||
func (ga *GenericArgs) SetTimestamp(tstamp time.Time) {
|
|
||||||
ga.Timestamp = tstamp
|
|
||||||
}
|
|
||||||
|
|
||||||
// RPCLoginArgs - login username and password for RPC.
|
|
||||||
type RPCLoginArgs struct {
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
}
|
|
||||||
|
|
||||||
// RPCLoginReply - login reply provides generated token to be used
|
|
||||||
// with subsequent requests.
|
|
||||||
type RPCLoginReply struct {
|
|
||||||
Token string
|
|
||||||
Timestamp time.Time
|
|
||||||
ServerVersion string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auth config represents authentication credentials and Login method name to be used
|
|
||||||
// for fetching JWT tokens from the RPC server.
|
|
||||||
type authConfig struct {
|
type authConfig struct {
|
||||||
accessKey string // Username for the server.
|
accessKey string // Access key (like username) for authentication.
|
||||||
secretKey string // Password for the server.
|
secretKey string // Secret key (like Password) for authentication.
|
||||||
secureConn bool // Ask for a secured connection
|
serverAddr string // RPC server address.
|
||||||
address string // Network address path of RPC server.
|
serviceEndpoint string // Endpoint on the server to make any RPC call.
|
||||||
path string // Network path for HTTP dial.
|
secureConn bool // Make TLS connection to RPC server or not.
|
||||||
loginMethod string // RPC service name for authenticating using JWT
|
serviceName string // Service name of auth server.
|
||||||
|
disableReconnect bool // Disable reconnect on failure or not.
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthRPCClient is a wrapper type for RPCClient which provides JWT based authentication across reconnects.
|
// AuthRPCClient is a authenticated RPC client which does authentication before doing Call().
|
||||||
type AuthRPCClient struct {
|
type AuthRPCClient struct {
|
||||||
mu sync.Mutex
|
sync.Mutex // Mutex to lock this object.
|
||||||
config *authConfig
|
rpcClient *RPCClient // Reconnectable RPC client to make any RPC call.
|
||||||
rpc *RPCClient // reconnect'able rpc client built on top of net/rpc Client
|
config authConfig // Authentication configuration information.
|
||||||
serverToken string // Disk rpc JWT based token.
|
authToken string // Authentication token.
|
||||||
serverVersion string // Server version exchanged by the RPC.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newAuthClient - returns a jwt based authenticated (go) rpc client, which does automatic reconnect.
|
// newAuthRPCClient - returns a JWT based authenticated (go) rpc client, which does automatic reconnect.
|
||||||
func newAuthClient(cfg *authConfig) *AuthRPCClient {
|
func newAuthRPCClient(config authConfig) *AuthRPCClient {
|
||||||
return &AuthRPCClient{
|
return &AuthRPCClient{
|
||||||
// Save the config.
|
rpcClient: newRPCClient(config.serverAddr, config.serviceEndpoint, config.secureConn),
|
||||||
config: cfg,
|
config: config,
|
||||||
// Initialize a new reconnectable rpc client.
|
|
||||||
rpc: newRPCClient(cfg.address, cfg.path, cfg.secureConn),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close - closes underlying rpc connection.
|
|
||||||
func (authClient *AuthRPCClient) Close() error {
|
|
||||||
authClient.mu.Lock()
|
|
||||||
// reset token on closing a connection
|
|
||||||
authClient.serverToken = ""
|
|
||||||
authClient.mu.Unlock()
|
|
||||||
return authClient.rpc.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login - a jwt based authentication is performed with rpc server.
|
// Login - a jwt based authentication is performed with rpc server.
|
||||||
func (authClient *AuthRPCClient) Login() (err error) {
|
func (authClient *AuthRPCClient) Login() (err error) {
|
||||||
authClient.mu.Lock()
|
authClient.Lock()
|
||||||
// As soon as the function returns unlock,
|
defer authClient.Unlock()
|
||||||
defer authClient.mu.Unlock()
|
|
||||||
|
|
||||||
// Return if already logged in.
|
// Return if already logged in.
|
||||||
if authClient.serverToken != "" {
|
if authClient.authToken != "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
reply := RPCLoginReply{}
|
// Call login.
|
||||||
if err = authClient.rpc.Call(authClient.config.loginMethod, RPCLoginArgs{
|
args := LoginRPCArgs{
|
||||||
Username: authClient.config.accessKey,
|
Username: authClient.config.accessKey,
|
||||||
Password: authClient.config.secretKey,
|
Password: authClient.config.secretKey,
|
||||||
}, &reply); err != nil {
|
Version: Version,
|
||||||
|
RequestTime: time.Now().UTC(),
|
||||||
|
}
|
||||||
|
|
||||||
|
reply := LoginRPCReply{}
|
||||||
|
serviceMethod := authClient.config.serviceName + loginMethodName
|
||||||
|
if err = authClient.rpcClient.Call(serviceMethod, &args, &reply); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate if version do indeed match.
|
// Logged in successfully.
|
||||||
if reply.ServerVersion != Version {
|
authClient.authToken = reply.AuthToken
|
||||||
return errServerVersionMismatch
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate if server timestamp is skewed.
|
|
||||||
curTime := time.Now().UTC()
|
|
||||||
if curTime.Sub(reply.Timestamp) > globalMaxSkewTime {
|
|
||||||
return errServerTimeMismatch
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set token, time stamp as received from a successful login call.
|
|
||||||
authClient.serverToken = reply.Token
|
|
||||||
authClient.serverVersion = reply.ServerVersion
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call - If rpc connection isn't established yet since previous disconnect,
|
// call makes a RPC call after logs into the server.
|
||||||
// connection is established, a jwt authenticated login is performed and then
|
func (authClient *AuthRPCClient) call(serviceMethod string, args interface {
|
||||||
// the call is performed.
|
SetAuthToken(authToken string)
|
||||||
func (authClient *AuthRPCClient) Call(serviceMethod string, args interface {
|
SetRequestTime(requestTime time.Time)
|
||||||
SetToken(token string)
|
|
||||||
SetTimestamp(tstamp time.Time)
|
|
||||||
}, reply interface{}) (err error) {
|
}, reply interface{}) (err error) {
|
||||||
loginAndCallFn := func() error {
|
// On successful login, execute RPC call.
|
||||||
// On successful login, proceed to attempt the requested service method.
|
if err = authClient.Login(); err == nil {
|
||||||
if err = authClient.Login(); err == nil {
|
// Set token and timestamp before the rpc call.
|
||||||
// Set token and timestamp before the rpc call.
|
args.SetAuthToken(authClient.authToken)
|
||||||
args.SetToken(authClient.serverToken)
|
args.SetRequestTime(time.Now().UTC())
|
||||||
args.SetTimestamp(time.Now().UTC())
|
|
||||||
|
|
||||||
// Finally make the network call using net/rpc client.
|
// Do RPC call.
|
||||||
err = authClient.rpc.Call(serviceMethod, args, reply)
|
err = authClient.rpcClient.Call(serviceMethod, args, reply)
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call executes RPC call till success or globalAuthRPCRetryThreshold on ErrShutdown.
|
||||||
|
func (authClient *AuthRPCClient) Call(serviceMethod string, args interface {
|
||||||
|
SetAuthToken(authToken string)
|
||||||
|
SetRequestTime(requestTime time.Time)
|
||||||
|
}, reply interface{}) (err error) {
|
||||||
doneCh := make(chan struct{})
|
doneCh := make(chan struct{})
|
||||||
defer close(doneCh)
|
defer close(doneCh)
|
||||||
for i := range newRetryTimer(time.Second, time.Second*30, MaxJitter, doneCh) {
|
for i := range newRetryTimer(time.Second, 30*time.Second, MaxJitter, doneCh) {
|
||||||
// Invalidate token, and mark it for re-login and
|
if err = authClient.call(serviceMethod, args, reply); err == rpc.ErrShutdown {
|
||||||
// reconnect upon rpc shutdown.
|
// As connection at server side is closed, close the rpc client.
|
||||||
if err = loginAndCallFn(); err == rpc.ErrShutdown {
|
|
||||||
// Close the underlying connection, and proceed to reconnect
|
|
||||||
// if we haven't reached the retry threshold.
|
|
||||||
authClient.Close()
|
authClient.Close()
|
||||||
|
|
||||||
// No need to return error until the retry count threshold has reached.
|
// Retry if reconnect is not disabled.
|
||||||
if i < globalAuthRPCRetryThreshold {
|
if !authClient.config.disableReconnect {
|
||||||
continue
|
// Retry until threshold reaches.
|
||||||
|
if i < globalAuthRPCRetryThreshold {
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@ -178,18 +125,21 @@ func (authClient *AuthRPCClient) Call(serviceMethod string, args interface {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node returns the node (network address) of the connection
|
// Close closes underlying RPC Client.
|
||||||
func (authClient *AuthRPCClient) Node() (node string) {
|
func (authClient *AuthRPCClient) Close() error {
|
||||||
if authClient.rpc != nil {
|
authClient.Lock()
|
||||||
node = authClient.rpc.node
|
defer authClient.Unlock()
|
||||||
}
|
|
||||||
return node
|
authClient.authToken = ""
|
||||||
|
return authClient.rpcClient.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RPCPath returns the RPC path of the connection
|
// ServerAddr returns the serverAddr (network address) of the connection.
|
||||||
func (authClient *AuthRPCClient) RPCPath() (rpcPath string) {
|
func (authClient *AuthRPCClient) ServerAddr() string {
|
||||||
if authClient.rpc != nil {
|
return authClient.config.serverAddr
|
||||||
rpcPath = authClient.rpc.rpcPath
|
}
|
||||||
}
|
|
||||||
return rpcPath
|
// ServiceEndpoint returns the RPC service endpoint of the connection.
|
||||||
|
func (authClient *AuthRPCClient) ServiceEndpoint() string {
|
||||||
|
return authClient.config.serviceEndpoint
|
||||||
}
|
}
|
||||||
|
@ -20,32 +20,32 @@ import "testing"
|
|||||||
|
|
||||||
// Tests authorized RPC client.
|
// Tests authorized RPC client.
|
||||||
func TestAuthRPCClient(t *testing.T) {
|
func TestAuthRPCClient(t *testing.T) {
|
||||||
authCfg := &authConfig{
|
authCfg := authConfig{
|
||||||
|
accessKey: "123",
|
||||||
|
secretKey: "123",
|
||||||
|
serverAddr: "localhost:9000",
|
||||||
|
serviceEndpoint: "/rpc/disk",
|
||||||
|
secureConn: false,
|
||||||
|
serviceName: "MyPackage",
|
||||||
|
}
|
||||||
|
authRPC := newAuthRPCClient(authCfg)
|
||||||
|
if authRPC.ServerAddr() != authCfg.serverAddr {
|
||||||
|
t.Fatalf("Unexpected node value %s, but expected %s", authRPC.ServerAddr(), authCfg.serverAddr)
|
||||||
|
}
|
||||||
|
if authRPC.ServiceEndpoint() != authCfg.serviceEndpoint {
|
||||||
|
t.Fatalf("Unexpected node value %s, but expected %s", authRPC.ServiceEndpoint(), authCfg.serviceEndpoint)
|
||||||
|
}
|
||||||
|
authCfg = authConfig{
|
||||||
accessKey: "123",
|
accessKey: "123",
|
||||||
secretKey: "123",
|
secretKey: "123",
|
||||||
secureConn: false,
|
secureConn: false,
|
||||||
address: "localhost:9000",
|
serviceName: "MyPackage",
|
||||||
path: "/rpc/disk",
|
|
||||||
loginMethod: "MyPackage.LoginHandler",
|
|
||||||
}
|
}
|
||||||
authRPC := newAuthClient(authCfg)
|
authRPC = newAuthRPCClient(authCfg)
|
||||||
if authRPC.Node() != authCfg.address {
|
if authRPC.ServerAddr() != authCfg.serverAddr {
|
||||||
t.Fatalf("Unexpected node value %s, but expected %s", authRPC.Node(), authCfg.address)
|
t.Fatalf("Unexpected node value %s, but expected %s", authRPC.ServerAddr(), authCfg.serverAddr)
|
||||||
}
|
}
|
||||||
if authRPC.RPCPath() != authCfg.path {
|
if authRPC.ServiceEndpoint() != authCfg.serviceEndpoint {
|
||||||
t.Fatalf("Unexpected node value %s, but expected %s", authRPC.RPCPath(), authCfg.path)
|
t.Fatalf("Unexpected node value %s, but expected %s", authRPC.ServiceEndpoint(), authCfg.serviceEndpoint)
|
||||||
}
|
|
||||||
authCfg = &authConfig{
|
|
||||||
accessKey: "123",
|
|
||||||
secretKey: "123",
|
|
||||||
secureConn: false,
|
|
||||||
loginMethod: "MyPackage.LoginHandler",
|
|
||||||
}
|
|
||||||
authRPC = newAuthClient(authCfg)
|
|
||||||
if authRPC.Node() != authCfg.address {
|
|
||||||
t.Fatalf("Unexpected node value %s, but expected %s", authRPC.Node(), authCfg.address)
|
|
||||||
}
|
|
||||||
if authRPC.RPCPath() != authCfg.path {
|
|
||||||
t.Fatalf("Unexpected node value %s, but expected %s", authRPC.RPCPath(), authCfg.path)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,20 +16,28 @@
|
|||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import "time"
|
// Base login method name. It should be used along with service name.
|
||||||
|
const loginMethodName = ".Login"
|
||||||
|
|
||||||
type loginServer struct {
|
// AuthRPCServer RPC server authenticates using JWT.
|
||||||
|
type AuthRPCServer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoginHandler - Handles JWT based RPC logic.
|
// Login - Handles JWT based RPC login.
|
||||||
func (b loginServer) LoginHandler(args *RPCLoginArgs, reply *RPCLoginReply) error {
|
func (b AuthRPCServer) Login(args *LoginRPCArgs, reply *LoginRPCReply) error {
|
||||||
|
// Validate LoginRPCArgs
|
||||||
|
if err := args.IsValid(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate using JWT.
|
||||||
token, err := authenticateNode(args.Username, args.Password)
|
token, err := authenticateNode(args.Username, args.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.Token = token
|
// Return the token.
|
||||||
reply.Timestamp = time.Now().UTC()
|
reply.AuthToken = token
|
||||||
reply.ServerVersion = Version
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
117
cmd/auth-rpc-server_test.go
Normal file
117
cmd/auth-rpc-server_test.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLogin(t *testing.T) {
|
||||||
|
rootPath, err := newTestConfig("us-east-1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create test config - %v", err)
|
||||||
|
}
|
||||||
|
defer removeAll(rootPath)
|
||||||
|
creds := serverConfig.GetCredential()
|
||||||
|
ls := AuthRPCServer{}
|
||||||
|
testCases := []struct {
|
||||||
|
args LoginRPCArgs
|
||||||
|
skewTime time.Duration
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
// Valid case.
|
||||||
|
{
|
||||||
|
args: LoginRPCArgs{
|
||||||
|
Username: creds.AccessKey,
|
||||||
|
Password: creds.SecretKey,
|
||||||
|
Version: Version,
|
||||||
|
},
|
||||||
|
skewTime: 0,
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
// Valid username, password and request time, not version.
|
||||||
|
{
|
||||||
|
args: LoginRPCArgs{
|
||||||
|
Username: creds.AccessKey,
|
||||||
|
Password: creds.SecretKey,
|
||||||
|
Version: "INVALID-" + Version,
|
||||||
|
},
|
||||||
|
skewTime: 0,
|
||||||
|
expectedErr: errServerVersionMismatch,
|
||||||
|
},
|
||||||
|
// Valid username, password and version, not request time
|
||||||
|
{
|
||||||
|
args: LoginRPCArgs{
|
||||||
|
Username: creds.AccessKey,
|
||||||
|
Password: creds.SecretKey,
|
||||||
|
Version: Version,
|
||||||
|
},
|
||||||
|
skewTime: 20 * time.Minute,
|
||||||
|
expectedErr: errServerTimeMismatch,
|
||||||
|
},
|
||||||
|
// Invalid username length
|
||||||
|
{
|
||||||
|
args: LoginRPCArgs{
|
||||||
|
Username: "aaa",
|
||||||
|
Password: "minio123",
|
||||||
|
Version: Version,
|
||||||
|
},
|
||||||
|
skewTime: 0,
|
||||||
|
expectedErr: errInvalidAccessKeyLength,
|
||||||
|
},
|
||||||
|
// Invalid password length
|
||||||
|
{
|
||||||
|
args: LoginRPCArgs{
|
||||||
|
Username: "minio",
|
||||||
|
Password: "aaa",
|
||||||
|
Version: Version,
|
||||||
|
},
|
||||||
|
skewTime: 0,
|
||||||
|
expectedErr: errInvalidSecretKeyLength,
|
||||||
|
},
|
||||||
|
// Invalid username
|
||||||
|
{
|
||||||
|
args: LoginRPCArgs{
|
||||||
|
Username: "aaaaa",
|
||||||
|
Password: creds.SecretKey,
|
||||||
|
Version: Version,
|
||||||
|
},
|
||||||
|
skewTime: 0,
|
||||||
|
expectedErr: errInvalidAccessKeyID,
|
||||||
|
},
|
||||||
|
// Invalid password
|
||||||
|
{
|
||||||
|
args: LoginRPCArgs{
|
||||||
|
Username: creds.AccessKey,
|
||||||
|
Password: "aaaaaaaa",
|
||||||
|
Version: Version,
|
||||||
|
},
|
||||||
|
skewTime: 0,
|
||||||
|
expectedErr: errAuthentication,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, test := range testCases {
|
||||||
|
reply := LoginRPCReply{}
|
||||||
|
test.args.RequestTime = time.Now().Add(test.skewTime).UTC()
|
||||||
|
err := ls.Login(&test.args, &reply)
|
||||||
|
if err != test.expectedErr {
|
||||||
|
t.Errorf("Test %d: Expected error %v but received %v",
|
||||||
|
i+1, test.expectedErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,22 +24,28 @@ import (
|
|||||||
|
|
||||||
// Login handler implements JWT login token generator, which upon login request
|
// Login handler implements JWT login token generator, which upon login request
|
||||||
// along with username and password is generated.
|
// along with username and password is generated.
|
||||||
func (br *browserPeerAPIHandlers) LoginHandler(args *RPCLoginArgs, reply *RPCLoginReply) error {
|
func (br *browserPeerAPIHandlers) Login(args *LoginRPCArgs, reply *LoginRPCReply) error {
|
||||||
|
// Validate LoginRPCArgs
|
||||||
|
if err := args.IsValid(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate using JWT.
|
||||||
token, err := authenticateWeb(args.Username, args.Password)
|
token, err := authenticateWeb(args.Username, args.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.Token = token
|
// Return the token.
|
||||||
reply.ServerVersion = Version
|
reply.AuthToken = token
|
||||||
reply.Timestamp = time.Now().UTC()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAuthPeerArgs - Arguments collection for SetAuth RPC call
|
// SetAuthPeerArgs - Arguments collection for SetAuth RPC call
|
||||||
type SetAuthPeerArgs struct {
|
type SetAuthPeerArgs struct {
|
||||||
// For Auth
|
// For Auth
|
||||||
GenericArgs
|
AuthRPCArgs
|
||||||
|
|
||||||
// New credentials that receiving peer should update to.
|
// New credentials that receiving peer should update to.
|
||||||
Creds credential
|
Creds credential
|
||||||
@ -52,10 +58,9 @@ type SetAuthPeerArgs struct {
|
|||||||
// will be forced to re-establish connections. Connections will be
|
// will be forced to re-establish connections. Connections will be
|
||||||
// re-established only when the sending client has also updated its
|
// re-established only when the sending client has also updated its
|
||||||
// credentials.
|
// credentials.
|
||||||
func (br *browserPeerAPIHandlers) SetAuthPeer(args SetAuthPeerArgs, reply *GenericReply) error {
|
func (br *browserPeerAPIHandlers) SetAuthPeer(args SetAuthPeerArgs, reply *AuthRPCReply) error {
|
||||||
// Check auth
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
if !isAuthTokenValid(args.Token) {
|
return err
|
||||||
return errInvalidToken
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update credentials in memory
|
// Update credentials in memory
|
||||||
@ -82,6 +87,7 @@ func updateCredsOnPeers(creds credential) map[string]error {
|
|||||||
errs := make([]error, len(peers))
|
errs := make([]error, len(peers))
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
serverCred := serverConfig.GetCredential()
|
||||||
// Launch go routines to send request to each peer in parallel.
|
// Launch go routines to send request to each peer in parallel.
|
||||||
for ix := range peers {
|
for ix := range peers {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
@ -96,13 +102,13 @@ func updateCredsOnPeers(creds credential) map[string]error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize client
|
// Initialize client
|
||||||
client := newAuthClient(&authConfig{
|
client := newAuthRPCClient(authConfig{
|
||||||
accessKey: serverConfig.GetCredential().AccessKey,
|
accessKey: serverCred.AccessKey,
|
||||||
secretKey: serverConfig.GetCredential().SecretKey,
|
secretKey: serverCred.SecretKey,
|
||||||
address: peers[ix],
|
serverAddr: peers[ix],
|
||||||
secureConn: isSSL(),
|
secureConn: isSSL(),
|
||||||
path: path.Join(reservedBucket, browserPeerPath),
|
serviceEndpoint: path.Join(reservedBucket, browserPeerPath),
|
||||||
loginMethod: "Browser.LoginHandler",
|
serviceName: "Browser",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Construct RPC call arguments.
|
// Construct RPC call arguments.
|
||||||
@ -110,14 +116,14 @@ func updateCredsOnPeers(creds credential) map[string]error {
|
|||||||
|
|
||||||
// Make RPC call - we only care about error
|
// Make RPC call - we only care about error
|
||||||
// response and not the reply.
|
// response and not the reply.
|
||||||
err := client.Call("Browser.SetAuthPeer", &args, &GenericReply{})
|
err := client.Call("Browser.SetAuthPeer", &args, &AuthRPCReply{})
|
||||||
|
|
||||||
// We try a bit hard (3 attempts with 1 second delay)
|
// We try a bit hard (3 attempts with 1 second delay)
|
||||||
// to set creds on peers in case of failure.
|
// to set creds on peers in case of failure.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; i < 2; i++ {
|
||||||
time.Sleep(1 * time.Second) // 1 second delay.
|
time.Sleep(1 * time.Second) // 1 second delay.
|
||||||
err = client.Call("Browser.SetAuthPeer", &args, &GenericReply{})
|
err = client.Call("Browser.SetAuthPeer", &args, &AuthRPCReply{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -19,24 +19,25 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// API suite container common to both FS and XL.
|
// API suite container common to both FS and XL.
|
||||||
type TestRPCBrowserPeerSuite struct {
|
type TestRPCBrowserPeerSuite struct {
|
||||||
serverType string
|
serverType string
|
||||||
testServer TestServer
|
testServer TestServer
|
||||||
testAuthConf *authConfig
|
testAuthConf authConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setting up the test suite and starting the Test server.
|
// Setting up the test suite and starting the Test server.
|
||||||
func (s *TestRPCBrowserPeerSuite) SetUpSuite(c *testing.T) {
|
func (s *TestRPCBrowserPeerSuite) SetUpSuite(c *testing.T) {
|
||||||
s.testServer = StartTestBrowserPeerRPCServer(c, s.serverType)
|
s.testServer = StartTestBrowserPeerRPCServer(c, s.serverType)
|
||||||
s.testAuthConf = &authConfig{
|
s.testAuthConf = authConfig{
|
||||||
address: s.testServer.Server.Listener.Addr().String(),
|
serverAddr: s.testServer.Server.Listener.Addr().String(),
|
||||||
accessKey: s.testServer.AccessKey,
|
accessKey: s.testServer.AccessKey,
|
||||||
secretKey: s.testServer.SecretKey,
|
secretKey: s.testServer.SecretKey,
|
||||||
path: path.Join(reservedBucket, browserPeerPath),
|
serviceEndpoint: path.Join(reservedBucket, browserPeerPath),
|
||||||
loginMethod: "BrowserPeer.LoginHandler",
|
serviceName: "BrowserPeer",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,10 +70,10 @@ func (s *TestRPCBrowserPeerSuite) testBrowserPeerRPC(t *testing.T) {
|
|||||||
|
|
||||||
// Validate for invalid token.
|
// Validate for invalid token.
|
||||||
args := SetAuthPeerArgs{Creds: creds}
|
args := SetAuthPeerArgs{Creds: creds}
|
||||||
args.Token = "garbage"
|
args.AuthToken = "garbage"
|
||||||
rclient := newRPCClient(s.testAuthConf.address, s.testAuthConf.path, false)
|
rclient := newRPCClient(s.testAuthConf.serverAddr, s.testAuthConf.serviceEndpoint, false)
|
||||||
defer rclient.Close()
|
defer rclient.Close()
|
||||||
err := rclient.Call("BrowserPeer.SetAuthPeer", &args, &GenericReply{})
|
err := rclient.Call("BrowserPeer.SetAuthPeer", &args, &AuthRPCReply{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() != errInvalidToken.Error() {
|
if err.Error() != errInvalidToken.Error() {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -81,22 +82,24 @@ func (s *TestRPCBrowserPeerSuite) testBrowserPeerRPC(t *testing.T) {
|
|||||||
|
|
||||||
// Validate for successful Peer update.
|
// Validate for successful Peer update.
|
||||||
args = SetAuthPeerArgs{Creds: creds}
|
args = SetAuthPeerArgs{Creds: creds}
|
||||||
client := newAuthClient(s.testAuthConf)
|
client := newAuthRPCClient(s.testAuthConf)
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
err = client.Call("BrowserPeer.SetAuthPeer", &args, &GenericReply{})
|
err = client.Call("BrowserPeer.SetAuthPeer", &args, &AuthRPCReply{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate for failure in login handler with previous credentials.
|
// Validate for failure in login handler with previous credentials.
|
||||||
rclient = newRPCClient(s.testAuthConf.address, s.testAuthConf.path, false)
|
rclient = newRPCClient(s.testAuthConf.serverAddr, s.testAuthConf.serviceEndpoint, false)
|
||||||
defer rclient.Close()
|
defer rclient.Close()
|
||||||
rargs := &RPCLoginArgs{
|
rargs := &LoginRPCArgs{
|
||||||
Username: s.testAuthConf.accessKey,
|
Username: creds.AccessKey,
|
||||||
Password: s.testAuthConf.secretKey,
|
Password: creds.SecretKey,
|
||||||
|
Version: Version,
|
||||||
|
RequestTime: time.Now().UTC(),
|
||||||
}
|
}
|
||||||
rreply := &RPCLoginReply{}
|
rreply := &LoginRPCReply{}
|
||||||
err = rclient.Call("BrowserPeer.LoginHandler", rargs, rreply)
|
err = rclient.Call("BrowserPeer"+loginMethodName, rargs, rreply)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() != errInvalidAccessKeyID.Error() {
|
if err.Error() != errInvalidAccessKeyID.Error() {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -104,20 +107,19 @@ func (s *TestRPCBrowserPeerSuite) testBrowserPeerRPC(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate for success in loing handled with valid credetnails.
|
// Validate for success in loing handled with valid credetnails.
|
||||||
rargs = &RPCLoginArgs{
|
rargs = &LoginRPCArgs{
|
||||||
Username: creds.AccessKey,
|
Username: creds.AccessKey,
|
||||||
Password: creds.SecretKey,
|
Password: creds.SecretKey,
|
||||||
|
Version: Version,
|
||||||
|
RequestTime: time.Now().UTC(),
|
||||||
}
|
}
|
||||||
rreply = &RPCLoginReply{}
|
rreply = &LoginRPCReply{}
|
||||||
err = rclient.Call("BrowserPeer.LoginHandler", rargs, rreply)
|
err = rclient.Call("BrowserPeer"+loginMethodName, rargs, rreply)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// Validate all the replied fields after successful login.
|
// Validate all the replied fields after successful login.
|
||||||
if rreply.Token == "" {
|
if rreply.AuthToken == "" {
|
||||||
t.Fatalf("Generated token cannot be empty %s", errInvalidToken)
|
t.Fatalf("Generated token cannot be empty %s", errInvalidToken)
|
||||||
}
|
}
|
||||||
if rreply.Timestamp.IsZero() {
|
|
||||||
t.Fatal("Time stamp returned cannot be zero")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// The Type exporting methods exposed for RPC calls.
|
// The Type exporting methods exposed for RPC calls.
|
||||||
type browserPeerAPIHandlers struct{}
|
type browserPeerAPIHandlers struct {
|
||||||
|
AuthRPCServer
|
||||||
|
}
|
||||||
|
|
||||||
// Register RPC router
|
// Register RPC router
|
||||||
func registerBrowserPeerRPCRouter(mux *router.Router) error {
|
func registerBrowserPeerRPCRouter(mux *router.Router) error {
|
||||||
|
@ -108,27 +108,27 @@ type remoteBucketMetaState struct {
|
|||||||
// remoteBucketMetaState.UpdateBucketNotification - sends bucket notification
|
// remoteBucketMetaState.UpdateBucketNotification - sends bucket notification
|
||||||
// change to remote peer via RPC call.
|
// change to remote peer via RPC call.
|
||||||
func (rc *remoteBucketMetaState) UpdateBucketNotification(args *SetBucketNotificationPeerArgs) error {
|
func (rc *remoteBucketMetaState) UpdateBucketNotification(args *SetBucketNotificationPeerArgs) error {
|
||||||
reply := GenericReply{}
|
reply := AuthRPCReply{}
|
||||||
return rc.Call("S3.SetBucketNotificationPeer", args, &reply)
|
return rc.Call("S3.SetBucketNotificationPeer", args, &reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
// remoteBucketMetaState.UpdateBucketListener - sends bucket listener change to
|
// remoteBucketMetaState.UpdateBucketListener - sends bucket listener change to
|
||||||
// remote peer via RPC call.
|
// remote peer via RPC call.
|
||||||
func (rc *remoteBucketMetaState) UpdateBucketListener(args *SetBucketListenerPeerArgs) error {
|
func (rc *remoteBucketMetaState) UpdateBucketListener(args *SetBucketListenerPeerArgs) error {
|
||||||
reply := GenericReply{}
|
reply := AuthRPCReply{}
|
||||||
return rc.Call("S3.SetBucketListenerPeer", args, &reply)
|
return rc.Call("S3.SetBucketListenerPeer", args, &reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
// remoteBucketMetaState.UpdateBucketPolicy - sends bucket policy change to remote
|
// remoteBucketMetaState.UpdateBucketPolicy - sends bucket policy change to remote
|
||||||
// peer via RPC call.
|
// peer via RPC call.
|
||||||
func (rc *remoteBucketMetaState) UpdateBucketPolicy(args *SetBucketPolicyPeerArgs) error {
|
func (rc *remoteBucketMetaState) UpdateBucketPolicy(args *SetBucketPolicyPeerArgs) error {
|
||||||
reply := GenericReply{}
|
reply := AuthRPCReply{}
|
||||||
return rc.Call("S3.SetBucketPolicyPeer", args, &reply)
|
return rc.Call("S3.SetBucketPolicyPeer", args, &reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
// remoteBucketMetaState.SendEvent - sends event for bucket listener to remote
|
// remoteBucketMetaState.SendEvent - sends event for bucket listener to remote
|
||||||
// peer via RPC call.
|
// peer via RPC call.
|
||||||
func (rc *remoteBucketMetaState) SendEvent(args *EventArgs) error {
|
func (rc *remoteBucketMetaState) SendEvent(args *EventArgs) error {
|
||||||
reply := GenericReply{}
|
reply := AuthRPCReply{}
|
||||||
return rc.Call("S3.Event", args, &reply)
|
return rc.Call("S3.Event", args, &reply)
|
||||||
}
|
}
|
||||||
|
@ -404,8 +404,7 @@ func AddBucketListenerConfig(bucket string, lcfg *listenerConfig, objAPI ObjectL
|
|||||||
func RemoveBucketListenerConfig(bucket string, lcfg *listenerConfig, objAPI ObjectLayer) {
|
func RemoveBucketListenerConfig(bucket string, lcfg *listenerConfig, objAPI ObjectLayer) {
|
||||||
listenerCfgs := globalEventNotifier.GetBucketListenerConfig(bucket)
|
listenerCfgs := globalEventNotifier.GetBucketListenerConfig(bucket)
|
||||||
|
|
||||||
// remove listener with matching ARN - if not found ignore and
|
// remove listener with matching ARN - if not found ignore and exit.
|
||||||
// exit.
|
|
||||||
var updatedLcfgs []listenerConfig
|
var updatedLcfgs []listenerConfig
|
||||||
found := false
|
found := false
|
||||||
for k, configuredLcfg := range listenerCfgs {
|
for k, configuredLcfg := range listenerCfgs {
|
||||||
|
@ -227,6 +227,11 @@ func TestSetNGetBucketNotification(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInitEventNotifier(t *testing.T) {
|
func TestInitEventNotifier(t *testing.T) {
|
||||||
|
currentIsDistXL := globalIsDistXL
|
||||||
|
defer func() {
|
||||||
|
globalIsDistXL = currentIsDistXL
|
||||||
|
}()
|
||||||
|
|
||||||
s := TestPeerRPCServerData{serverType: "XL"}
|
s := TestPeerRPCServerData{serverType: "XL"}
|
||||||
|
|
||||||
// setup and teardown
|
// setup and teardown
|
||||||
@ -323,6 +328,11 @@ func TestInitEventNotifier(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestListenBucketNotification(t *testing.T) {
|
func TestListenBucketNotification(t *testing.T) {
|
||||||
|
currentIsDistXL := globalIsDistXL
|
||||||
|
defer func() {
|
||||||
|
globalIsDistXL = currentIsDistXL
|
||||||
|
}()
|
||||||
|
|
||||||
s := TestPeerRPCServerData{serverType: "XL"}
|
s := TestPeerRPCServerData{serverType: "XL"}
|
||||||
// setup and teardown
|
// setup and teardown
|
||||||
s.Setup(t)
|
s.Setup(t)
|
||||||
|
71
cmd/lock-rpc-client.go
Normal file
71
cmd/lock-rpc-client.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* 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/dsync"
|
||||||
|
|
||||||
|
// LockRPCClient is authenticable lock RPC client compatible to dsync.NetLocker
|
||||||
|
type LockRPCClient struct {
|
||||||
|
*AuthRPCClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// newLockRPCClient returns new lock RPC client object.
|
||||||
|
func newLockRPCClient(config authConfig) *LockRPCClient {
|
||||||
|
return &LockRPCClient{newAuthRPCClient(config)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RLock calls read lock RPC.
|
||||||
|
func (lockRPCClient *LockRPCClient) RLock(args dsync.LockArgs) (reply bool, err error) {
|
||||||
|
lockArgs := newLockArgs(args)
|
||||||
|
err = lockRPCClient.AuthRPCClient.Call("Dsync.RLock", &lockArgs, &reply)
|
||||||
|
return reply, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock calls write lock RPC.
|
||||||
|
func (lockRPCClient *LockRPCClient) Lock(args dsync.LockArgs) (reply bool, err error) {
|
||||||
|
lockArgs := newLockArgs(args)
|
||||||
|
err = lockRPCClient.AuthRPCClient.Call("Dsync.Lock", &lockArgs, &reply)
|
||||||
|
return reply, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RUnlock calls read unlock RPC.
|
||||||
|
func (lockRPCClient *LockRPCClient) RUnlock(args dsync.LockArgs) (reply bool, err error) {
|
||||||
|
lockArgs := newLockArgs(args)
|
||||||
|
err = lockRPCClient.AuthRPCClient.Call("Dsync.RUnlock", &lockArgs, &reply)
|
||||||
|
return reply, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock calls write unlock RPC.
|
||||||
|
func (lockRPCClient *LockRPCClient) Unlock(args dsync.LockArgs) (reply bool, err error) {
|
||||||
|
lockArgs := newLockArgs(args)
|
||||||
|
err = lockRPCClient.AuthRPCClient.Call("Dsync.Unlock", &lockArgs, &reply)
|
||||||
|
return reply, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForceUnlock calls force unlock RPC.
|
||||||
|
func (lockRPCClient *LockRPCClient) ForceUnlock(args dsync.LockArgs) (reply bool, err error) {
|
||||||
|
lockArgs := newLockArgs(args)
|
||||||
|
err = lockRPCClient.AuthRPCClient.Call("Dsync.ForceUnlock", &lockArgs, &reply)
|
||||||
|
return reply, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expired calls expired RPC.
|
||||||
|
func (lockRPCClient *LockRPCClient) Expired(args dsync.LockArgs) (reply bool, err error) {
|
||||||
|
lockArgs := newLockArgs(args)
|
||||||
|
err = lockRPCClient.AuthRPCClient.Call("Dsync.Expired", &lockArgs, &reply)
|
||||||
|
return reply, err
|
||||||
|
}
|
@ -57,20 +57,6 @@ func (l *lockServer) removeEntry(name, uid string, lri *[]lockRequesterInfo) boo
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate lock args.
|
|
||||||
// - validate time stamp.
|
|
||||||
// - validate jwt token.
|
|
||||||
func (l *lockServer) validateLockArgs(args *LockArgs) error {
|
|
||||||
curTime := time.Now().UTC()
|
|
||||||
if curTime.Sub(args.Timestamp) > globalMaxSkewTime || args.Timestamp.Sub(curTime) > globalMaxSkewTime {
|
|
||||||
return errServerTimeMismatch
|
|
||||||
}
|
|
||||||
if !isAuthTokenValid(args.Token) {
|
|
||||||
return errInvalidToken
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getLongLivedLocks returns locks that are older than a certain time and
|
// getLongLivedLocks returns locks that are older than a certain time and
|
||||||
// have not been 'checked' for validity too soon enough
|
// have not been 'checked' for validity too soon enough
|
||||||
func getLongLivedLocks(m map[string][]lockRequesterInfo, interval time.Duration) []nameLockRequesterInfoPair {
|
func getLongLivedLocks(m map[string][]lockRequesterInfo, interval time.Duration) []nameLockRequesterInfoPair {
|
||||||
|
@ -25,32 +25,19 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
router "github.com/gorilla/mux"
|
router "github.com/gorilla/mux"
|
||||||
|
"github.com/minio/dsync"
|
||||||
)
|
)
|
||||||
|
|
||||||
const lockRPCPath = "/minio/lock"
|
const (
|
||||||
const lockMaintenanceLoop = 1 * time.Minute
|
// Lock rpc server endpoint.
|
||||||
const lockCheckValidityInterval = 2 * time.Minute
|
lockRPCPath = "/minio/lock"
|
||||||
|
|
||||||
// LockArgs besides lock name, holds Token and Timestamp for session
|
// Lock maintenance interval.
|
||||||
// authentication and validation server restart.
|
lockMaintenanceInterval = 1 * time.Minute // 1 minute.
|
||||||
type LockArgs struct {
|
|
||||||
Name string
|
|
||||||
Token string
|
|
||||||
Timestamp time.Time
|
|
||||||
Node string
|
|
||||||
RPCPath string
|
|
||||||
UID string
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetToken - sets the token to the supplied value.
|
// Lock validity check interval.
|
||||||
func (l *LockArgs) SetToken(token string) {
|
lockValidityCheckInterval = 2 * time.Minute // 2 minutes.
|
||||||
l.Token = token
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// SetTimestamp - sets the timestamp to the supplied value.
|
|
||||||
func (l *LockArgs) SetTimestamp(tstamp time.Time) {
|
|
||||||
l.Timestamp = tstamp
|
|
||||||
}
|
|
||||||
|
|
||||||
// lockRequesterInfo stores various info from the client for each lock that is requested
|
// lockRequesterInfo stores various info from the client for each lock that is requested
|
||||||
type lockRequesterInfo struct {
|
type lockRequesterInfo struct {
|
||||||
@ -69,43 +56,61 @@ func isWriteLock(lri []lockRequesterInfo) bool {
|
|||||||
|
|
||||||
// lockServer is type for RPC handlers
|
// lockServer is type for RPC handlers
|
||||||
type lockServer struct {
|
type lockServer struct {
|
||||||
loginServer
|
AuthRPCServer
|
||||||
rpcPath string
|
rpcPath string
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
lockMap map[string][]lockRequesterInfo
|
lockMap map[string][]lockRequesterInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start lock maintenance from all lock servers.
|
||||||
|
func startLockMaintainence(lockServers []*lockServer) {
|
||||||
|
for _, locker := range lockServers {
|
||||||
|
// Start loop for stale lock maintenance
|
||||||
|
go func(lk *lockServer) {
|
||||||
|
// Initialize a new ticker with a minute between each ticks.
|
||||||
|
ticker := time.NewTicker(lockMaintenanceInterval)
|
||||||
|
|
||||||
|
// Start with random sleep time, so as to avoid "synchronous checks" between servers
|
||||||
|
time.Sleep(time.Duration(rand.Float64() * float64(lockMaintenanceInterval)))
|
||||||
|
for {
|
||||||
|
// Verifies every minute for locks held more than 2minutes.
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
lk.lockMaintenance(lockValidityCheckInterval)
|
||||||
|
case <-globalServiceDoneCh:
|
||||||
|
// Stop the timer.
|
||||||
|
ticker.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(locker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Register distributed NS lock handlers.
|
// Register distributed NS lock handlers.
|
||||||
func registerDistNSLockRouter(mux *router.Router, serverConfig serverCmdConfig) error {
|
func registerDistNSLockRouter(mux *router.Router, serverConfig serverCmdConfig) error {
|
||||||
|
// Initialize a new set of lock servers.
|
||||||
lockServers := newLockServers(serverConfig)
|
lockServers := newLockServers(serverConfig)
|
||||||
|
|
||||||
|
// Start lock maintenance from all lock servers.
|
||||||
|
startLockMaintainence(lockServers)
|
||||||
|
|
||||||
|
// Register initialized lock servers to their respective rpc endpoints.
|
||||||
return registerStorageLockers(mux, lockServers)
|
return registerStorageLockers(mux, lockServers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create one lock server for every local storage rpc server.
|
// Create one lock server for every local storage rpc server.
|
||||||
func newLockServers(srvConfig serverCmdConfig) (lockServers []*lockServer) {
|
func newLockServers(srvConfig serverCmdConfig) (lockServers []*lockServer) {
|
||||||
for _, ep := range srvConfig.endpoints {
|
for _, ep := range srvConfig.endpoints {
|
||||||
// Not local storage move to the next node.
|
// Initialize new lock server for each local node.
|
||||||
if !isLocalStorage(ep) {
|
if isLocalStorage(ep) {
|
||||||
continue
|
// Create handler for lock RPCs
|
||||||
}
|
locker := &lockServer{
|
||||||
|
rpcPath: getPath(ep),
|
||||||
// Create handler for lock RPCs
|
mutex: sync.Mutex{},
|
||||||
locker := &lockServer{
|
lockMap: make(map[string][]lockRequesterInfo),
|
||||||
rpcPath: getPath(ep),
|
|
||||||
mutex: sync.Mutex{},
|
|
||||||
lockMap: make(map[string][]lockRequesterInfo),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start loop for stale lock maintenance
|
|
||||||
go func() {
|
|
||||||
// Start with random sleep time, so as to avoid "synchronous checks" between servers
|
|
||||||
time.Sleep(time.Duration(rand.Float64() * float64(lockMaintenanceLoop)))
|
|
||||||
for {
|
|
||||||
time.Sleep(lockMaintenanceLoop)
|
|
||||||
locker.lockMaintenance(lockCheckValidityInterval)
|
|
||||||
}
|
}
|
||||||
}()
|
lockServers = append(lockServers, locker)
|
||||||
lockServers = append(lockServers, locker)
|
}
|
||||||
}
|
}
|
||||||
return lockServers
|
return lockServers
|
||||||
}
|
}
|
||||||
@ -114,8 +119,7 @@ func newLockServers(srvConfig serverCmdConfig) (lockServers []*lockServer) {
|
|||||||
func registerStorageLockers(mux *router.Router, lockServers []*lockServer) error {
|
func registerStorageLockers(mux *router.Router, lockServers []*lockServer) error {
|
||||||
for _, lockServer := range lockServers {
|
for _, lockServer := range lockServers {
|
||||||
lockRPCServer := rpc.NewServer()
|
lockRPCServer := rpc.NewServer()
|
||||||
err := lockRPCServer.RegisterName("Dsync", lockServer)
|
if err := lockRPCServer.RegisterName("Dsync", lockServer); err != nil {
|
||||||
if err != nil {
|
|
||||||
return traceError(err)
|
return traceError(err)
|
||||||
}
|
}
|
||||||
lockRouter := mux.PathPrefix(reservedBucket).Subrouter()
|
lockRouter := mux.PathPrefix(reservedBucket).Subrouter()
|
||||||
@ -130,17 +134,17 @@ func registerStorageLockers(mux *router.Router, lockServers []*lockServer) error
|
|||||||
func (l *lockServer) Lock(args *LockArgs, reply *bool) error {
|
func (l *lockServer) Lock(args *LockArgs, reply *bool) error {
|
||||||
l.mutex.Lock()
|
l.mutex.Lock()
|
||||||
defer l.mutex.Unlock()
|
defer l.mutex.Unlock()
|
||||||
if err := l.validateLockArgs(args); err != nil {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, *reply = l.lockMap[args.Name]
|
_, *reply = l.lockMap[args.dsyncLockArgs.Resource]
|
||||||
if !*reply { // No locks held on the given name, so claim write lock
|
if !*reply { // No locks held on the given name, so claim write lock
|
||||||
l.lockMap[args.Name] = []lockRequesterInfo{
|
l.lockMap[args.dsyncLockArgs.Resource] = []lockRequesterInfo{
|
||||||
{
|
{
|
||||||
writer: true,
|
writer: true,
|
||||||
node: args.Node,
|
node: args.dsyncLockArgs.ServerAddr,
|
||||||
rpcPath: args.RPCPath,
|
rpcPath: args.dsyncLockArgs.ServiceEndpoint,
|
||||||
uid: args.UID,
|
uid: args.dsyncLockArgs.UID,
|
||||||
timestamp: time.Now().UTC(),
|
timestamp: time.Now().UTC(),
|
||||||
timeLastCheck: time.Now().UTC(),
|
timeLastCheck: time.Now().UTC(),
|
||||||
},
|
},
|
||||||
@ -154,18 +158,18 @@ func (l *lockServer) Lock(args *LockArgs, reply *bool) error {
|
|||||||
func (l *lockServer) Unlock(args *LockArgs, reply *bool) error {
|
func (l *lockServer) Unlock(args *LockArgs, reply *bool) error {
|
||||||
l.mutex.Lock()
|
l.mutex.Lock()
|
||||||
defer l.mutex.Unlock()
|
defer l.mutex.Unlock()
|
||||||
if err := l.validateLockArgs(args); err != nil {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var lri []lockRequesterInfo
|
var lri []lockRequesterInfo
|
||||||
if lri, *reply = l.lockMap[args.Name]; !*reply { // No lock is held on the given name
|
if lri, *reply = l.lockMap[args.dsyncLockArgs.Resource]; !*reply { // No lock is held on the given name
|
||||||
return fmt.Errorf("Unlock attempted on an unlocked entity: %s", args.Name)
|
return fmt.Errorf("Unlock attempted on an unlocked entity: %s", args.dsyncLockArgs.Resource)
|
||||||
}
|
}
|
||||||
if *reply = isWriteLock(lri); !*reply { // Unless it is a write lock
|
if *reply = isWriteLock(lri); !*reply { // Unless it is a write lock
|
||||||
return fmt.Errorf("Unlock attempted on a read locked entity: %s (%d read locks active)", args.Name, len(lri))
|
return fmt.Errorf("Unlock attempted on a read locked entity: %s (%d read locks active)", args.dsyncLockArgs.Resource, len(lri))
|
||||||
}
|
}
|
||||||
if !l.removeEntry(args.Name, args.UID, &lri) {
|
if !l.removeEntry(args.dsyncLockArgs.Resource, args.dsyncLockArgs.UID, &lri) {
|
||||||
return fmt.Errorf("Unlock unable to find corresponding lock for uid: %s", args.UID)
|
return fmt.Errorf("Unlock unable to find corresponding lock for uid: %s", args.dsyncLockArgs.UID)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -174,23 +178,23 @@ func (l *lockServer) Unlock(args *LockArgs, reply *bool) error {
|
|||||||
func (l *lockServer) RLock(args *LockArgs, reply *bool) error {
|
func (l *lockServer) RLock(args *LockArgs, reply *bool) error {
|
||||||
l.mutex.Lock()
|
l.mutex.Lock()
|
||||||
defer l.mutex.Unlock()
|
defer l.mutex.Unlock()
|
||||||
if err := l.validateLockArgs(args); err != nil {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
lrInfo := lockRequesterInfo{
|
lrInfo := lockRequesterInfo{
|
||||||
writer: false,
|
writer: false,
|
||||||
node: args.Node,
|
node: args.dsyncLockArgs.ServerAddr,
|
||||||
rpcPath: args.RPCPath,
|
rpcPath: args.dsyncLockArgs.ServiceEndpoint,
|
||||||
uid: args.UID,
|
uid: args.dsyncLockArgs.UID,
|
||||||
timestamp: time.Now().UTC(),
|
timestamp: time.Now().UTC(),
|
||||||
timeLastCheck: time.Now().UTC(),
|
timeLastCheck: time.Now().UTC(),
|
||||||
}
|
}
|
||||||
if lri, ok := l.lockMap[args.Name]; ok {
|
if lri, ok := l.lockMap[args.dsyncLockArgs.Resource]; ok {
|
||||||
if *reply = !isWriteLock(lri); *reply { // Unless there is a write lock
|
if *reply = !isWriteLock(lri); *reply { // Unless there is a write lock
|
||||||
l.lockMap[args.Name] = append(l.lockMap[args.Name], lrInfo)
|
l.lockMap[args.dsyncLockArgs.Resource] = append(l.lockMap[args.dsyncLockArgs.Resource], lrInfo)
|
||||||
}
|
}
|
||||||
} else { // No locks held on the given name, so claim (first) read lock
|
} else { // No locks held on the given name, so claim (first) read lock
|
||||||
l.lockMap[args.Name] = []lockRequesterInfo{lrInfo}
|
l.lockMap[args.dsyncLockArgs.Resource] = []lockRequesterInfo{lrInfo}
|
||||||
*reply = true
|
*reply = true
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -200,18 +204,18 @@ func (l *lockServer) RLock(args *LockArgs, reply *bool) error {
|
|||||||
func (l *lockServer) RUnlock(args *LockArgs, reply *bool) error {
|
func (l *lockServer) RUnlock(args *LockArgs, reply *bool) error {
|
||||||
l.mutex.Lock()
|
l.mutex.Lock()
|
||||||
defer l.mutex.Unlock()
|
defer l.mutex.Unlock()
|
||||||
if err := l.validateLockArgs(args); err != nil {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var lri []lockRequesterInfo
|
var lri []lockRequesterInfo
|
||||||
if lri, *reply = l.lockMap[args.Name]; !*reply { // No lock is held on the given name
|
if lri, *reply = l.lockMap[args.dsyncLockArgs.Resource]; !*reply { // No lock is held on the given name
|
||||||
return fmt.Errorf("RUnlock attempted on an unlocked entity: %s", args.Name)
|
return fmt.Errorf("RUnlock attempted on an unlocked entity: %s", args.dsyncLockArgs.Resource)
|
||||||
}
|
}
|
||||||
if *reply = !isWriteLock(lri); !*reply { // A write-lock is held, cannot release a read lock
|
if *reply = !isWriteLock(lri); !*reply { // A write-lock is held, cannot release a read lock
|
||||||
return fmt.Errorf("RUnlock attempted on a write locked entity: %s", args.Name)
|
return fmt.Errorf("RUnlock attempted on a write locked entity: %s", args.dsyncLockArgs.Resource)
|
||||||
}
|
}
|
||||||
if !l.removeEntry(args.Name, args.UID, &lri) {
|
if !l.removeEntry(args.dsyncLockArgs.Resource, args.dsyncLockArgs.UID, &lri) {
|
||||||
return fmt.Errorf("RUnlock unable to find corresponding read lock for uid: %s", args.UID)
|
return fmt.Errorf("RUnlock unable to find corresponding read lock for uid: %s", args.dsyncLockArgs.UID)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -220,14 +224,14 @@ func (l *lockServer) RUnlock(args *LockArgs, reply *bool) error {
|
|||||||
func (l *lockServer) ForceUnlock(args *LockArgs, reply *bool) error {
|
func (l *lockServer) ForceUnlock(args *LockArgs, reply *bool) error {
|
||||||
l.mutex.Lock()
|
l.mutex.Lock()
|
||||||
defer l.mutex.Unlock()
|
defer l.mutex.Unlock()
|
||||||
if err := l.validateLockArgs(args); err != nil {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(args.UID) != 0 {
|
if len(args.dsyncLockArgs.UID) != 0 {
|
||||||
return fmt.Errorf("ForceUnlock called with non-empty UID: %s", args.UID)
|
return fmt.Errorf("ForceUnlock called with non-empty UID: %s", args.dsyncLockArgs.UID)
|
||||||
}
|
}
|
||||||
if _, ok := l.lockMap[args.Name]; ok { // Only clear lock when set
|
if _, ok := l.lockMap[args.dsyncLockArgs.Resource]; ok { // Only clear lock when set
|
||||||
delete(l.lockMap, args.Name) // Remove the lock (irrespective of write or read lock)
|
delete(l.lockMap, args.dsyncLockArgs.Resource) // Remove the lock (irrespective of write or read lock)
|
||||||
}
|
}
|
||||||
*reply = true
|
*reply = true
|
||||||
return nil
|
return nil
|
||||||
@ -237,21 +241,21 @@ func (l *lockServer) ForceUnlock(args *LockArgs, reply *bool) error {
|
|||||||
func (l *lockServer) Expired(args *LockArgs, reply *bool) error {
|
func (l *lockServer) Expired(args *LockArgs, reply *bool) error {
|
||||||
l.mutex.Lock()
|
l.mutex.Lock()
|
||||||
defer l.mutex.Unlock()
|
defer l.mutex.Unlock()
|
||||||
if err := l.validateLockArgs(args); err != nil {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Lock found, proceed to verify if belongs to given uid.
|
// Lock found, proceed to verify if belongs to given uid.
|
||||||
if lri, ok := l.lockMap[args.Name]; ok {
|
if lri, ok := l.lockMap[args.dsyncLockArgs.Resource]; ok {
|
||||||
// Check whether uid is still active
|
// Check whether uid is still active
|
||||||
for _, entry := range lri {
|
for _, entry := range lri {
|
||||||
if entry.uid == args.UID {
|
if entry.uid == args.dsyncLockArgs.UID {
|
||||||
*reply = false // When uid found, lock is still active so return not expired.
|
*reply = false // When uid found, lock is still active so return not expired.
|
||||||
return nil // When uid found *reply is set to true.
|
return nil // When uid found *reply is set to true.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// When we get here lock is no longer active due to either args.Name
|
// When we get here lock is no longer active due to either args.dsyncLockArgs.Resource
|
||||||
// being absent from map or uid not found for given args.Name
|
// being absent from map or uid not found for given args.dsyncLockArgs.Resource
|
||||||
*reply = true
|
*reply = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -276,19 +280,24 @@ func (l *lockServer) lockMaintenance(interval time.Duration) {
|
|||||||
nlripLongLived := getLongLivedLocks(l.lockMap, interval)
|
nlripLongLived := getLongLivedLocks(l.lockMap, interval)
|
||||||
l.mutex.Unlock()
|
l.mutex.Unlock()
|
||||||
|
|
||||||
|
serverCred := serverConfig.GetCredential()
|
||||||
// Validate if long lived locks are indeed clean.
|
// Validate if long lived locks are indeed clean.
|
||||||
for _, nlrip := range nlripLongLived {
|
for _, nlrip := range nlripLongLived {
|
||||||
// Initialize client based on the long live locks.
|
// Initialize client based on the long live locks.
|
||||||
c := newRPCClient(nlrip.lri.node, nlrip.lri.rpcPath, isSSL())
|
c := newLockRPCClient(authConfig{
|
||||||
|
accessKey: serverCred.AccessKey,
|
||||||
var expired bool
|
secretKey: serverCred.SecretKey,
|
||||||
|
serverAddr: nlrip.lri.node,
|
||||||
|
serviceEndpoint: nlrip.lri.rpcPath,
|
||||||
|
secureConn: isSSL(),
|
||||||
|
serviceName: "Dsync",
|
||||||
|
})
|
||||||
|
|
||||||
// Call back to original server verify whether the lock is still active (based on name & uid)
|
// Call back to original server verify whether the lock is still active (based on name & uid)
|
||||||
c.Call("Dsync.Expired", &LockArgs{
|
expired, _ := c.Expired(dsync.LockArgs{UID: nlrip.lri.uid, Resource: nlrip.name})
|
||||||
Name: nlrip.name,
|
|
||||||
UID: nlrip.lri.uid,
|
// Close the connection regardless of the call response.
|
||||||
}, &expired)
|
c.rpcClient.Close()
|
||||||
c.Close() // Close the connection regardless of the call response.
|
|
||||||
|
|
||||||
// For successful response, verify if lock is indeed active or stale.
|
// For successful response, verify if lock is indeed active or stale.
|
||||||
if expired {
|
if expired {
|
||||||
|
@ -22,6 +22,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/dsync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Helper function to test equality of locks (without taking timing info into account)
|
// Helper function to test equality of locks (without taking timing info into account)
|
||||||
@ -49,38 +51,41 @@ func createLockTestServer(t *testing.T) (string, *lockServer, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
locker := &lockServer{
|
locker := &lockServer{
|
||||||
loginServer: loginServer{},
|
AuthRPCServer: AuthRPCServer{},
|
||||||
rpcPath: "rpc-path",
|
rpcPath: "rpc-path",
|
||||||
mutex: sync.Mutex{},
|
mutex: sync.Mutex{},
|
||||||
lockMap: make(map[string][]lockRequesterInfo),
|
lockMap: make(map[string][]lockRequesterInfo),
|
||||||
}
|
}
|
||||||
creds := serverConfig.GetCredential()
|
creds := serverConfig.GetCredential()
|
||||||
loginArgs := RPCLoginArgs{Username: creds.AccessKey, Password: creds.SecretKey}
|
loginArgs := LoginRPCArgs{
|
||||||
loginReply := RPCLoginReply{}
|
Username: creds.AccessKey,
|
||||||
err = locker.LoginHandler(&loginArgs, &loginReply)
|
Password: creds.SecretKey,
|
||||||
|
Version: Version,
|
||||||
|
RequestTime: time.Now().UTC(),
|
||||||
|
}
|
||||||
|
loginReply := LoginRPCReply{}
|
||||||
|
err = locker.Login(&loginArgs, &loginReply)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to login to lock server - %v", err)
|
t.Fatalf("Failed to login to lock server - %v", err)
|
||||||
}
|
}
|
||||||
token := loginReply.Token
|
token := loginReply.AuthToken
|
||||||
|
|
||||||
return testPath, locker, token
|
return testPath, locker, token
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test Lock functionality
|
// Test Lock functionality
|
||||||
func TestLockRpcServerLock(t *testing.T) {
|
func TestLockRpcServerLock(t *testing.T) {
|
||||||
|
|
||||||
timestamp := time.Now().UTC()
|
|
||||||
testPath, locker, token := createLockTestServer(t)
|
testPath, locker, token := createLockTestServer(t)
|
||||||
defer removeAll(testPath)
|
defer removeAll(testPath)
|
||||||
|
|
||||||
la := LockArgs{
|
la := newLockArgs(dsync.LockArgs{
|
||||||
Name: "name",
|
UID: "0123-4567",
|
||||||
Token: token,
|
Resource: "name",
|
||||||
Timestamp: timestamp,
|
ServerAddr: "node",
|
||||||
Node: "node",
|
ServiceEndpoint: "rpc-path",
|
||||||
RPCPath: "rpc-path",
|
})
|
||||||
UID: "0123-4567",
|
la.SetAuthToken(token)
|
||||||
}
|
la.SetRequestTime(time.Now().UTC())
|
||||||
|
|
||||||
// Claim a lock
|
// Claim a lock
|
||||||
var result bool
|
var result bool
|
||||||
@ -107,14 +112,15 @@ func TestLockRpcServerLock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to claim same lock again (will fail)
|
// Try to claim same lock again (will fail)
|
||||||
la2 := LockArgs{
|
la2 := newLockArgs(dsync.LockArgs{
|
||||||
Name: "name",
|
UID: "89ab-cdef",
|
||||||
Token: token,
|
Resource: "name",
|
||||||
Timestamp: timestamp,
|
ServerAddr: "node",
|
||||||
Node: "node",
|
ServiceEndpoint: "rpc-path",
|
||||||
RPCPath: "rpc-path",
|
})
|
||||||
UID: "89ab-cdef",
|
la2.SetAuthToken(token)
|
||||||
}
|
la2.SetRequestTime(time.Now().UTC())
|
||||||
|
|
||||||
err = locker.Lock(&la2, &result)
|
err = locker.Lock(&la2, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected %#v, got %#v", nil, err)
|
t.Errorf("Expected %#v, got %#v", nil, err)
|
||||||
@ -127,19 +133,17 @@ func TestLockRpcServerLock(t *testing.T) {
|
|||||||
|
|
||||||
// Test Unlock functionality
|
// Test Unlock functionality
|
||||||
func TestLockRpcServerUnlock(t *testing.T) {
|
func TestLockRpcServerUnlock(t *testing.T) {
|
||||||
|
|
||||||
timestamp := time.Now().UTC()
|
|
||||||
testPath, locker, token := createLockTestServer(t)
|
testPath, locker, token := createLockTestServer(t)
|
||||||
defer removeAll(testPath)
|
defer removeAll(testPath)
|
||||||
|
|
||||||
la := LockArgs{
|
la := newLockArgs(dsync.LockArgs{
|
||||||
Name: "name",
|
UID: "0123-4567",
|
||||||
Token: token,
|
Resource: "name",
|
||||||
Timestamp: timestamp,
|
ServerAddr: "node",
|
||||||
Node: "node",
|
ServiceEndpoint: "rpc-path",
|
||||||
RPCPath: "rpc-path",
|
})
|
||||||
UID: "0123-4567",
|
la.SetAuthToken(token)
|
||||||
}
|
la.SetRequestTime(time.Now().UTC())
|
||||||
|
|
||||||
// First test return of error when attempting to unlock a lock that does not exist
|
// First test return of error when attempting to unlock a lock that does not exist
|
||||||
var result bool
|
var result bool
|
||||||
@ -149,6 +153,7 @@ func TestLockRpcServerUnlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create lock (so that we can release)
|
// Create lock (so that we can release)
|
||||||
|
la.SetRequestTime(time.Now().UTC())
|
||||||
err = locker.Lock(&la, &result)
|
err = locker.Lock(&la, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected %#v, got %#v", nil, err)
|
t.Errorf("Expected %#v, got %#v", nil, err)
|
||||||
@ -157,6 +162,7 @@ func TestLockRpcServerUnlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Finally test successful release of lock
|
// Finally test successful release of lock
|
||||||
|
la.SetRequestTime(time.Now().UTC())
|
||||||
err = locker.Unlock(&la, &result)
|
err = locker.Unlock(&la, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected %#v, got %#v", nil, err)
|
t.Errorf("Expected %#v, got %#v", nil, err)
|
||||||
@ -175,19 +181,17 @@ func TestLockRpcServerUnlock(t *testing.T) {
|
|||||||
|
|
||||||
// Test RLock functionality
|
// Test RLock functionality
|
||||||
func TestLockRpcServerRLock(t *testing.T) {
|
func TestLockRpcServerRLock(t *testing.T) {
|
||||||
|
|
||||||
timestamp := time.Now().UTC()
|
|
||||||
testPath, locker, token := createLockTestServer(t)
|
testPath, locker, token := createLockTestServer(t)
|
||||||
defer removeAll(testPath)
|
defer removeAll(testPath)
|
||||||
|
|
||||||
la := LockArgs{
|
la := newLockArgs(dsync.LockArgs{
|
||||||
Name: "name",
|
UID: "0123-4567",
|
||||||
Token: token,
|
Resource: "name",
|
||||||
Timestamp: timestamp,
|
ServerAddr: "node",
|
||||||
Node: "node",
|
ServiceEndpoint: "rpc-path",
|
||||||
RPCPath: "rpc-path",
|
})
|
||||||
UID: "0123-4567",
|
la.SetAuthToken(token)
|
||||||
}
|
la.SetRequestTime(time.Now().UTC())
|
||||||
|
|
||||||
// Claim a lock
|
// Claim a lock
|
||||||
var result bool
|
var result bool
|
||||||
@ -214,14 +218,15 @@ func TestLockRpcServerRLock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to claim same again (will succeed)
|
// Try to claim same again (will succeed)
|
||||||
la2 := LockArgs{
|
la2 := newLockArgs(dsync.LockArgs{
|
||||||
Name: "name",
|
UID: "89ab-cdef",
|
||||||
Token: token,
|
Resource: "name",
|
||||||
Timestamp: timestamp,
|
ServerAddr: "node",
|
||||||
Node: "node",
|
ServiceEndpoint: "rpc-path",
|
||||||
RPCPath: "rpc-path",
|
})
|
||||||
UID: "89ab-cdef",
|
la2.SetAuthToken(token)
|
||||||
}
|
la2.SetRequestTime(time.Now().UTC())
|
||||||
|
|
||||||
err = locker.RLock(&la2, &result)
|
err = locker.RLock(&la2, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected %#v, got %#v", nil, err)
|
t.Errorf("Expected %#v, got %#v", nil, err)
|
||||||
@ -234,19 +239,17 @@ func TestLockRpcServerRLock(t *testing.T) {
|
|||||||
|
|
||||||
// Test RUnlock functionality
|
// Test RUnlock functionality
|
||||||
func TestLockRpcServerRUnlock(t *testing.T) {
|
func TestLockRpcServerRUnlock(t *testing.T) {
|
||||||
|
|
||||||
timestamp := time.Now().UTC()
|
|
||||||
testPath, locker, token := createLockTestServer(t)
|
testPath, locker, token := createLockTestServer(t)
|
||||||
defer removeAll(testPath)
|
defer removeAll(testPath)
|
||||||
|
|
||||||
la := LockArgs{
|
la := newLockArgs(dsync.LockArgs{
|
||||||
Name: "name",
|
UID: "0123-4567",
|
||||||
Token: token,
|
Resource: "name",
|
||||||
Timestamp: timestamp,
|
ServerAddr: "node",
|
||||||
Node: "node",
|
ServiceEndpoint: "rpc-path",
|
||||||
RPCPath: "rpc-path",
|
})
|
||||||
UID: "0123-4567",
|
la.SetAuthToken(token)
|
||||||
}
|
la.SetRequestTime(time.Now().UTC())
|
||||||
|
|
||||||
// First test return of error when attempting to unlock a read-lock that does not exist
|
// First test return of error when attempting to unlock a read-lock that does not exist
|
||||||
var result bool
|
var result bool
|
||||||
@ -256,6 +259,7 @@ func TestLockRpcServerRUnlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create first lock ... (so that we can release)
|
// Create first lock ... (so that we can release)
|
||||||
|
la.SetRequestTime(time.Now().UTC())
|
||||||
err = locker.RLock(&la, &result)
|
err = locker.RLock(&la, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected %#v, got %#v", nil, err)
|
t.Errorf("Expected %#v, got %#v", nil, err)
|
||||||
@ -263,14 +267,15 @@ func TestLockRpcServerRUnlock(t *testing.T) {
|
|||||||
t.Errorf("Expected %#v, got %#v", true, result)
|
t.Errorf("Expected %#v, got %#v", true, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
la2 := LockArgs{
|
// Try to claim same again (will succeed)
|
||||||
Name: "name",
|
la2 := newLockArgs(dsync.LockArgs{
|
||||||
Token: token,
|
UID: "89ab-cdef",
|
||||||
Timestamp: timestamp,
|
Resource: "name",
|
||||||
Node: "node",
|
ServerAddr: "node",
|
||||||
RPCPath: "rpc-path",
|
ServiceEndpoint: "rpc-path",
|
||||||
UID: "89ab-cdef",
|
})
|
||||||
}
|
la2.SetAuthToken(token)
|
||||||
|
la2.SetRequestTime(time.Now().UTC())
|
||||||
|
|
||||||
// ... and create a second lock on same resource
|
// ... and create a second lock on same resource
|
||||||
err = locker.RLock(&la2, &result)
|
err = locker.RLock(&la2, &result)
|
||||||
@ -281,6 +286,7 @@ func TestLockRpcServerRUnlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test successful release of first read lock
|
// Test successful release of first read lock
|
||||||
|
la.SetRequestTime(time.Now().UTC())
|
||||||
err = locker.RUnlock(&la, &result)
|
err = locker.RUnlock(&la, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected %#v, got %#v", nil, err)
|
t.Errorf("Expected %#v, got %#v", nil, err)
|
||||||
@ -305,6 +311,7 @@ func TestLockRpcServerRUnlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Finally test successful release of second (and last) read lock
|
// Finally test successful release of second (and last) read lock
|
||||||
|
la2.SetRequestTime(time.Now().UTC())
|
||||||
err = locker.RUnlock(&la2, &result)
|
err = locker.RUnlock(&la2, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected %#v, got %#v", nil, err)
|
t.Errorf("Expected %#v, got %#v", nil, err)
|
||||||
@ -323,19 +330,17 @@ func TestLockRpcServerRUnlock(t *testing.T) {
|
|||||||
|
|
||||||
// Test ForceUnlock functionality
|
// Test ForceUnlock functionality
|
||||||
func TestLockRpcServerForceUnlock(t *testing.T) {
|
func TestLockRpcServerForceUnlock(t *testing.T) {
|
||||||
|
|
||||||
timestamp := time.Now().UTC()
|
|
||||||
testPath, locker, token := createLockTestServer(t)
|
testPath, locker, token := createLockTestServer(t)
|
||||||
defer removeAll(testPath)
|
defer removeAll(testPath)
|
||||||
|
|
||||||
laForce := LockArgs{
|
laForce := newLockArgs(dsync.LockArgs{
|
||||||
Name: "name",
|
UID: "1234-5678",
|
||||||
Token: token,
|
Resource: "name",
|
||||||
Timestamp: timestamp,
|
ServerAddr: "node",
|
||||||
Node: "node",
|
ServiceEndpoint: "rpc-path",
|
||||||
RPCPath: "rpc-path",
|
})
|
||||||
UID: "1234-5678",
|
laForce.SetAuthToken(token)
|
||||||
}
|
laForce.SetRequestTime(time.Now().UTC())
|
||||||
|
|
||||||
// First test that UID should be empty
|
// First test that UID should be empty
|
||||||
var result bool
|
var result bool
|
||||||
@ -345,20 +350,21 @@ func TestLockRpcServerForceUnlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Then test force unlock of a lock that does not exist (not returning an error)
|
// Then test force unlock of a lock that does not exist (not returning an error)
|
||||||
laForce.UID = ""
|
laForce.dsyncLockArgs.UID = ""
|
||||||
|
laForce.SetRequestTime(time.Now().UTC())
|
||||||
err = locker.ForceUnlock(&laForce, &result)
|
err = locker.ForceUnlock(&laForce, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, got %#v", err)
|
t.Errorf("Expected no error, got %#v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
la := LockArgs{
|
la := newLockArgs(dsync.LockArgs{
|
||||||
Name: "name",
|
UID: "0123-4567",
|
||||||
Token: token,
|
Resource: "name",
|
||||||
Timestamp: timestamp,
|
ServerAddr: "node",
|
||||||
Node: "node",
|
ServiceEndpoint: "rpc-path",
|
||||||
RPCPath: "rpc-path",
|
})
|
||||||
UID: "0123-4567",
|
la.SetAuthToken(token)
|
||||||
}
|
la.SetRequestTime(time.Now().UTC())
|
||||||
|
|
||||||
// Create lock ... (so that we can force unlock)
|
// Create lock ... (so that we can force unlock)
|
||||||
err = locker.Lock(&la, &result)
|
err = locker.Lock(&la, &result)
|
||||||
@ -369,12 +375,14 @@ func TestLockRpcServerForceUnlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Forcefully unlock the lock (not returning an error)
|
// Forcefully unlock the lock (not returning an error)
|
||||||
|
laForce.SetRequestTime(time.Now().UTC())
|
||||||
err = locker.ForceUnlock(&laForce, &result)
|
err = locker.ForceUnlock(&laForce, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, got %#v", err)
|
t.Errorf("Expected no error, got %#v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to get lock again (should be granted)
|
// Try to get lock again (should be granted)
|
||||||
|
la.SetRequestTime(time.Now().UTC())
|
||||||
err = locker.Lock(&la, &result)
|
err = locker.Lock(&la, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected %#v, got %#v", nil, err)
|
t.Errorf("Expected %#v, got %#v", nil, err)
|
||||||
@ -383,6 +391,7 @@ func TestLockRpcServerForceUnlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Finally forcefully unlock the lock once again
|
// Finally forcefully unlock the lock once again
|
||||||
|
laForce.SetRequestTime(time.Now().UTC())
|
||||||
err = locker.ForceUnlock(&laForce, &result)
|
err = locker.ForceUnlock(&laForce, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, got %#v", err)
|
t.Errorf("Expected no error, got %#v", err)
|
||||||
@ -391,18 +400,17 @@ func TestLockRpcServerForceUnlock(t *testing.T) {
|
|||||||
|
|
||||||
// Test Expired functionality
|
// Test Expired functionality
|
||||||
func TestLockRpcServerExpired(t *testing.T) {
|
func TestLockRpcServerExpired(t *testing.T) {
|
||||||
timestamp := time.Now().UTC()
|
|
||||||
testPath, locker, token := createLockTestServer(t)
|
testPath, locker, token := createLockTestServer(t)
|
||||||
defer removeAll(testPath)
|
defer removeAll(testPath)
|
||||||
|
|
||||||
la := LockArgs{
|
la := newLockArgs(dsync.LockArgs{
|
||||||
Name: "name",
|
UID: "0123-4567",
|
||||||
Token: token,
|
Resource: "name",
|
||||||
Timestamp: timestamp,
|
ServerAddr: "node",
|
||||||
Node: "node",
|
ServiceEndpoint: "rpc-path",
|
||||||
RPCPath: "rpc-path",
|
})
|
||||||
UID: "0123-4567",
|
la.SetAuthToken(token)
|
||||||
}
|
la.SetRequestTime(time.Now().UTC())
|
||||||
|
|
||||||
// Unknown lock at server will return expired = true
|
// Unknown lock at server will return expired = true
|
||||||
var expired bool
|
var expired bool
|
||||||
@ -417,6 +425,7 @@ func TestLockRpcServerExpired(t *testing.T) {
|
|||||||
|
|
||||||
// Create lock (so that we can test that it is not expired)
|
// Create lock (so that we can test that it is not expired)
|
||||||
var result bool
|
var result bool
|
||||||
|
la.SetRequestTime(time.Now().UTC())
|
||||||
err = locker.Lock(&la, &result)
|
err = locker.Lock(&la, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected %#v, got %#v", nil, err)
|
t.Errorf("Expected %#v, got %#v", nil, err)
|
||||||
@ -424,6 +433,7 @@ func TestLockRpcServerExpired(t *testing.T) {
|
|||||||
t.Errorf("Expected %#v, got %#v", true, result)
|
t.Errorf("Expected %#v, got %#v", true, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
la.SetRequestTime(time.Now().UTC())
|
||||||
err = locker.Expired(&la, &expired)
|
err = locker.Expired(&la, &expired)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, got %#v", err)
|
t.Errorf("Expected no error, got %#v", err)
|
||||||
@ -439,6 +449,12 @@ func TestLockServers(t *testing.T) {
|
|||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentIsDistXL := globalIsDistXL
|
||||||
|
defer func() {
|
||||||
|
globalIsDistXL = currentIsDistXL
|
||||||
|
}()
|
||||||
|
|
||||||
globalMinioHost = ""
|
globalMinioHost = ""
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
isDistXL bool
|
isDistXL bool
|
||||||
|
@ -1,67 +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"
|
|
||||||
|
|
||||||
func TestLoginHandler(t *testing.T) {
|
|
||||||
rootPath, err := newTestConfig("us-east-1")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create test config - %v", err)
|
|
||||||
}
|
|
||||||
defer removeAll(rootPath)
|
|
||||||
creds := serverConfig.GetCredential()
|
|
||||||
ls := loginServer{}
|
|
||||||
testCases := []struct {
|
|
||||||
args RPCLoginArgs
|
|
||||||
expectedErr error
|
|
||||||
}{
|
|
||||||
// Valid username and password
|
|
||||||
{
|
|
||||||
args: RPCLoginArgs{Username: creds.AccessKey, Password: creds.SecretKey},
|
|
||||||
expectedErr: nil,
|
|
||||||
},
|
|
||||||
// Invalid username length
|
|
||||||
{
|
|
||||||
args: RPCLoginArgs{Username: "aaa", Password: "minio123"},
|
|
||||||
expectedErr: errInvalidAccessKeyLength,
|
|
||||||
},
|
|
||||||
// Invalid password length
|
|
||||||
{
|
|
||||||
args: RPCLoginArgs{Username: "minio", Password: "aaa"},
|
|
||||||
expectedErr: errInvalidSecretKeyLength,
|
|
||||||
},
|
|
||||||
// Invalid username
|
|
||||||
{
|
|
||||||
args: RPCLoginArgs{Username: "aaaaa", Password: creds.SecretKey},
|
|
||||||
expectedErr: errInvalidAccessKeyID,
|
|
||||||
},
|
|
||||||
// Invalid password
|
|
||||||
{
|
|
||||||
args: RPCLoginArgs{Username: creds.AccessKey, Password: "aaaaaaaa"},
|
|
||||||
expectedErr: errAuthentication,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i, test := range testCases {
|
|
||||||
reply := RPCLoginReply{}
|
|
||||||
err := ls.LoginHandler(&test.args, &reply)
|
|
||||||
if err != test.expectedErr {
|
|
||||||
t.Errorf("Test %d: Expected error %v but received %v",
|
|
||||||
i+1, test.expectedErr, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -33,27 +33,26 @@ var globalNSMutex *nsLockMap
|
|||||||
func initDsyncNodes(eps []*url.URL) error {
|
func initDsyncNodes(eps []*url.URL) error {
|
||||||
cred := serverConfig.GetCredential()
|
cred := serverConfig.GetCredential()
|
||||||
// Initialize rpc lock client information only if this instance is a distributed setup.
|
// Initialize rpc lock client information only if this instance is a distributed setup.
|
||||||
clnts := make([]dsync.RPC, len(eps))
|
clnts := make([]dsync.NetLocker, len(eps))
|
||||||
myNode := -1
|
myNode := -1
|
||||||
for index, ep := range eps {
|
for index, ep := range eps {
|
||||||
if ep == nil {
|
if ep == nil {
|
||||||
return errInvalidArgument
|
return errInvalidArgument
|
||||||
}
|
}
|
||||||
clnts[index] = newAuthClient(&authConfig{
|
clnts[index] = newLockRPCClient(authConfig{
|
||||||
accessKey: cred.AccessKey,
|
accessKey: cred.AccessKey,
|
||||||
secretKey: cred.SecretKey,
|
secretKey: cred.SecretKey,
|
||||||
// Construct a new dsync server addr.
|
serverAddr: ep.Host,
|
||||||
secureConn: isSSL(),
|
serviceEndpoint: pathutil.Join(lockRPCPath, getPath(ep)),
|
||||||
address: ep.Host,
|
secureConn: isSSL(),
|
||||||
// Construct a new rpc path for the endpoint.
|
serviceName: "Dsync",
|
||||||
path: pathutil.Join(lockRPCPath, getPath(ep)),
|
|
||||||
loginMethod: "Dsync.LoginHandler",
|
|
||||||
})
|
})
|
||||||
if isLocalStorage(ep) && myNode == -1 {
|
if isLocalStorage(ep) && myNode == -1 {
|
||||||
myNode = index
|
myNode = index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dsync.SetNodesWithClients(clnts, myNode)
|
|
||||||
|
return dsync.Init(clnts, myNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initNSLock - initialize name space lock map.
|
// initNSLock - initialize name space lock map.
|
||||||
|
@ -33,79 +33,83 @@ import (
|
|||||||
// defaultDialTimeout is used for non-secure connection.
|
// defaultDialTimeout is used for non-secure connection.
|
||||||
const defaultDialTimeout = 3 * time.Second
|
const defaultDialTimeout = 3 * time.Second
|
||||||
|
|
||||||
// RPCClient is a wrapper type for rpc.Client which provides reconnect on first failure.
|
// RPCClient is a reconnectable RPC client on Call().
|
||||||
type RPCClient struct {
|
type RPCClient struct {
|
||||||
mu sync.Mutex
|
sync.Mutex // Mutex to lock net rpc client.
|
||||||
netRPCClient *rpc.Client
|
netRPCClient *rpc.Client // Base RPC client to make any RPC call.
|
||||||
node string
|
serverAddr string // RPC server address.
|
||||||
rpcPath string
|
serviceEndpoint string // Endpoint on the server to make any RPC call.
|
||||||
secureConn bool
|
secureConn bool // Make TLS connection to RPC server or not.
|
||||||
}
|
}
|
||||||
|
|
||||||
// newClient constructs a RPCClient object with node and rpcPath initialized.
|
// newRPCClient returns new RPCClient object with given serverAddr and serviceEndpoint.
|
||||||
// It does lazy connect to the remote endpoint on Call().
|
// It does lazy connect to the remote endpoint on Call().
|
||||||
func newRPCClient(node, rpcPath string, secureConn bool) *RPCClient {
|
func newRPCClient(serverAddr, serviceEndpoint string, secureConn bool) *RPCClient {
|
||||||
return &RPCClient{
|
return &RPCClient{
|
||||||
node: node,
|
serverAddr: serverAddr,
|
||||||
rpcPath: rpcPath,
|
serviceEndpoint: serviceEndpoint,
|
||||||
secureConn: secureConn,
|
secureConn: secureConn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// dial tries to establish a connection to the server in a safe manner.
|
// dial tries to establish a connection to serverAddr in a safe manner.
|
||||||
// If there is a valid rpc.Cliemt, it returns that else creates a new one.
|
// If there is a valid rpc.Cliemt, it returns that else creates a new one.
|
||||||
func (rpcClient *RPCClient) dial() (*rpc.Client, error) {
|
func (rpcClient *RPCClient) dial() (netRPCClient *rpc.Client, err error) {
|
||||||
rpcClient.mu.Lock()
|
rpcClient.Lock()
|
||||||
defer rpcClient.mu.Unlock()
|
defer rpcClient.Unlock()
|
||||||
|
|
||||||
// Nothing to do as we already have valid connection.
|
// Nothing to do as we already have valid connection.
|
||||||
if rpcClient.netRPCClient != nil {
|
if rpcClient.netRPCClient != nil {
|
||||||
return rpcClient.netRPCClient, nil
|
return rpcClient.netRPCClient, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
var conn net.Conn
|
var conn net.Conn
|
||||||
if rpcClient.secureConn {
|
if rpcClient.secureConn {
|
||||||
hostname, _, splitErr := net.SplitHostPort(rpcClient.node)
|
var hostname string
|
||||||
if splitErr != nil {
|
if hostname, _, err = net.SplitHostPort(rpcClient.serverAddr); err != nil {
|
||||||
err = errors.New("Unable to parse RPC address <" + rpcClient.node + "> : " + splitErr.Error())
|
err = &net.OpError{
|
||||||
return nil, &net.OpError{
|
|
||||||
Op: "dial-http",
|
Op: "dial-http",
|
||||||
Net: rpcClient.node + " " + rpcClient.rpcPath,
|
Net: rpcClient.serverAddr + rpcClient.serviceEndpoint,
|
||||||
Addr: nil,
|
Addr: nil,
|
||||||
Err: err,
|
Err: fmt.Errorf("Unable to parse server address <%s>: %s", rpcClient.serverAddr, err.Error()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
// ServerName in tls.Config needs to be specified to support SNI certificates
|
|
||||||
conn, err = tls.Dial("tcp", rpcClient.node, &tls.Config{ServerName: hostname, RootCAs: globalRootCAs})
|
// ServerName in tls.Config needs to be specified to support SNI certificates.
|
||||||
|
conn, err = tls.Dial("tcp", rpcClient.serverAddr, &tls.Config{ServerName: hostname, RootCAs: globalRootCAs})
|
||||||
} else {
|
} else {
|
||||||
// Dial with 3 seconds timeout.
|
// Dial with a timeout.
|
||||||
conn, err = net.DialTimeout("tcp", rpcClient.node, defaultDialTimeout)
|
conn, err = net.DialTimeout("tcp", rpcClient.serverAddr, defaultDialTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Print RPC connection errors that are worthy to display in log
|
// Print RPC connection errors that are worthy to display in log.
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case x509.HostnameError:
|
case x509.HostnameError:
|
||||||
errorIf(err, "Unable to establish secure connection to %s", rpcClient.node)
|
errorIf(err, "Unable to establish secure connection to %s", rpcClient.serverAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, &net.OpError{
|
return nil, &net.OpError{
|
||||||
Op: "dial-http",
|
Op: "dial-http",
|
||||||
Net: rpcClient.node + " " + rpcClient.rpcPath,
|
Net: rpcClient.serverAddr + rpcClient.serviceEndpoint,
|
||||||
Addr: nil,
|
Addr: nil,
|
||||||
Err: err,
|
Err: err,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
io.WriteString(conn, "CONNECT "+rpcClient.rpcPath+" HTTP/1.0\n\n")
|
io.WriteString(conn, "CONNECT "+rpcClient.serviceEndpoint+" HTTP/1.0\n\n")
|
||||||
|
|
||||||
// Require successful HTTP response before switching to RPC protocol.
|
// Require successful HTTP response before switching to RPC protocol.
|
||||||
resp, err := http.ReadResponse(bufio.NewReader(conn), &http.Request{Method: "CONNECT"})
|
resp, err := http.ReadResponse(bufio.NewReader(conn), &http.Request{Method: "CONNECT"})
|
||||||
if err == nil && resp.Status == "200 Connected to Go RPC" {
|
if err == nil && resp.Status == "200 Connected to Go RPC" {
|
||||||
netRPCClient := rpc.NewClient(conn)
|
netRPCClient := rpc.NewClient(conn)
|
||||||
|
|
||||||
if netRPCClient == nil {
|
if netRPCClient == nil {
|
||||||
return nil, &net.OpError{
|
return nil, &net.OpError{
|
||||||
Op: "dial-http",
|
Op: "dial-http",
|
||||||
Net: rpcClient.node + " " + rpcClient.rpcPath,
|
Net: rpcClient.serverAddr + rpcClient.serviceEndpoint,
|
||||||
Addr: nil,
|
Addr: nil,
|
||||||
Err: fmt.Errorf("Unable to initialize new rpc.Client, %s", errUnexpected),
|
Err: fmt.Errorf("Unable to initialize new rpc.Client, %s", errUnexpected),
|
||||||
}
|
}
|
||||||
@ -116,13 +120,15 @@ func (rpcClient *RPCClient) dial() (*rpc.Client, error) {
|
|||||||
return netRPCClient, nil
|
return netRPCClient, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conn.Close()
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = errors.New("unexpected HTTP response: " + resp.Status)
|
err = errors.New("unexpected HTTP response: " + resp.Status)
|
||||||
}
|
}
|
||||||
conn.Close()
|
|
||||||
return nil, &net.OpError{
|
return nil, &net.OpError{
|
||||||
Op: "dial-http",
|
Op: "dial-http",
|
||||||
Net: rpcClient.node + " " + rpcClient.rpcPath,
|
Net: rpcClient.serverAddr + rpcClient.serviceEndpoint,
|
||||||
Addr: nil,
|
Addr: nil,
|
||||||
Err: err,
|
Err: err,
|
||||||
}
|
}
|
||||||
@ -141,28 +147,18 @@ func (rpcClient *RPCClient) Call(serviceMethod string, args interface{}, reply i
|
|||||||
|
|
||||||
// Close closes underlying rpc.Client.
|
// Close closes underlying rpc.Client.
|
||||||
func (rpcClient *RPCClient) Close() error {
|
func (rpcClient *RPCClient) Close() error {
|
||||||
rpcClient.mu.Lock()
|
rpcClient.Lock()
|
||||||
|
|
||||||
if rpcClient.netRPCClient != nil {
|
if rpcClient.netRPCClient != nil {
|
||||||
// We make a copy of rpc.Client and unlock it immediately so that another
|
// We make a copy of rpc.Client and unlock it immediately so that another
|
||||||
// goroutine could try to dial or close in parallel.
|
// goroutine could try to dial or close in parallel.
|
||||||
netRPCClient := rpcClient.netRPCClient
|
netRPCClient := rpcClient.netRPCClient
|
||||||
rpcClient.netRPCClient = nil
|
rpcClient.netRPCClient = nil
|
||||||
rpcClient.mu.Unlock()
|
rpcClient.Unlock()
|
||||||
|
|
||||||
return netRPCClient.Close()
|
return netRPCClient.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
rpcClient.mu.Unlock()
|
rpcClient.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node returns the node (network address) of the connection
|
|
||||||
func (rpcClient *RPCClient) Node() string {
|
|
||||||
return rpcClient.node
|
|
||||||
}
|
|
||||||
|
|
||||||
// RPCPath returns the RPC path of the connection
|
|
||||||
func (rpcClient *RPCClient) RPCPath() string {
|
|
||||||
return rpcClient.rpcPath
|
|
||||||
}
|
|
||||||
|
@ -233,6 +233,7 @@ func (f retryStorage) reInit() (err error) {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to load format to see if the disk is really
|
// Attempt to load format to see if the disk is really
|
||||||
// a formatted disk and part of the cluster.
|
// a formatted disk and part of the cluster.
|
||||||
_, err = loadFormat(f.remoteStorage)
|
_, err = loadFormat(f.remoteStorage)
|
||||||
@ -244,6 +245,7 @@ func (f retryStorage) reInit() (err error) {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login and loading format was a success, break and proceed forward.
|
// Login and loading format was a success, break and proceed forward.
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
111
cmd/rpc-common.go
Normal file
111
cmd/rpc-common.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* 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 (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/dsync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Allow any RPC call request time should be no more/less than 3 seconds.
|
||||||
|
// 3 seconds is chosen arbitrarily.
|
||||||
|
const rpcSkewTimeAllowed = 3 * time.Second
|
||||||
|
|
||||||
|
func isRequestTimeAllowed(requestTime time.Time) bool {
|
||||||
|
// Check whether request time is within acceptable skew time.
|
||||||
|
utcNow := time.Now().UTC()
|
||||||
|
return !(requestTime.Sub(utcNow) > rpcSkewTimeAllowed ||
|
||||||
|
utcNow.Sub(requestTime) > rpcSkewTimeAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthRPCArgs represents minimum required arguments to make any authenticated RPC call.
|
||||||
|
type AuthRPCArgs struct {
|
||||||
|
// Authentication token to be verified by the server for every RPC call.
|
||||||
|
AuthToken string
|
||||||
|
|
||||||
|
// Request time to be verified by the server for every RPC call.
|
||||||
|
// This is an addition check over Authentication token for time drifting.
|
||||||
|
RequestTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAuthToken - sets the token to the supplied value.
|
||||||
|
func (args *AuthRPCArgs) SetAuthToken(authToken string) {
|
||||||
|
args.AuthToken = authToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequestTime - sets the requestTime to the supplied value.
|
||||||
|
func (args *AuthRPCArgs) SetRequestTime(requestTime time.Time) {
|
||||||
|
args.RequestTime = requestTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAuthenticated - validated whether this auth RPC args are already authenticated or not.
|
||||||
|
func (args AuthRPCArgs) IsAuthenticated() error {
|
||||||
|
// Check whether the token is valid
|
||||||
|
if !isAuthTokenValid(args.AuthToken) {
|
||||||
|
return errInvalidToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the request time is within the allowed skew limit.
|
||||||
|
if !isRequestTimeAllowed(args.RequestTime) {
|
||||||
|
return errServerTimeMismatch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Good to go.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthRPCReply represents minimum required reply for any authenticated RPC call.
|
||||||
|
type AuthRPCReply struct{}
|
||||||
|
|
||||||
|
// LoginRPCArgs - login username and password for RPC.
|
||||||
|
type LoginRPCArgs struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
Version string
|
||||||
|
RequestTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid - validates whether this LoginRPCArgs are valid for authentication.
|
||||||
|
func (args LoginRPCArgs) IsValid() error {
|
||||||
|
// Check if version matches.
|
||||||
|
if args.Version != Version {
|
||||||
|
return errServerVersionMismatch
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isRequestTimeAllowed(args.RequestTime) {
|
||||||
|
return errServerTimeMismatch
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginRPCReply - login reply provides generated token to be used
|
||||||
|
// with subsequent requests.
|
||||||
|
type LoginRPCReply struct {
|
||||||
|
AuthToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
// LockArgs represents arguments for any authenticated lock RPC call.
|
||||||
|
type LockArgs struct {
|
||||||
|
AuthRPCArgs
|
||||||
|
dsyncLockArgs dsync.LockArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLockArgs(args dsync.LockArgs) LockArgs {
|
||||||
|
return LockArgs{dsyncLockArgs: args}
|
||||||
|
}
|
@ -52,6 +52,7 @@ func makeS3Peers(eps []*url.URL) s3Peers {
|
|||||||
})
|
})
|
||||||
seenAddr[globalMinioAddr] = true
|
seenAddr[globalMinioAddr] = true
|
||||||
|
|
||||||
|
serverCred := serverConfig.GetCredential()
|
||||||
// iterate over endpoints to find new remote peers and add
|
// iterate over endpoints to find new remote peers and add
|
||||||
// them to ret.
|
// them to ret.
|
||||||
for _, ep := range eps {
|
for _, ep := range eps {
|
||||||
@ -62,17 +63,17 @@ func makeS3Peers(eps []*url.URL) s3Peers {
|
|||||||
// Check if the remote host has been added already
|
// Check if the remote host has been added already
|
||||||
if !seenAddr[ep.Host] {
|
if !seenAddr[ep.Host] {
|
||||||
cfg := authConfig{
|
cfg := authConfig{
|
||||||
accessKey: serverConfig.GetCredential().AccessKey,
|
accessKey: serverCred.AccessKey,
|
||||||
secretKey: serverConfig.GetCredential().SecretKey,
|
secretKey: serverCred.SecretKey,
|
||||||
address: ep.Host,
|
serverAddr: ep.Host,
|
||||||
secureConn: isSSL(),
|
serviceEndpoint: path.Join(reservedBucket, s3Path),
|
||||||
path: path.Join(reservedBucket, s3Path),
|
secureConn: isSSL(),
|
||||||
loginMethod: "S3.LoginHandler",
|
serviceName: "S3",
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = append(ret, s3Peer{
|
ret = append(ret, s3Peer{
|
||||||
addr: ep.Host,
|
addr: ep.Host,
|
||||||
bmsClient: &remoteBucketMetaState{newAuthClient(&cfg)},
|
bmsClient: &remoteBucketMetaState{newAuthRPCClient(cfg)},
|
||||||
})
|
})
|
||||||
seenAddr[ep.Host] = true
|
seenAddr[ep.Host] = true
|
||||||
}
|
}
|
||||||
|
@ -27,13 +27,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type s3PeerAPIHandlers struct {
|
type s3PeerAPIHandlers struct {
|
||||||
loginServer
|
AuthRPCServer
|
||||||
bms BucketMetaState
|
bms BucketMetaState
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerS3PeerRPCRouter(mux *router.Router) error {
|
func registerS3PeerRPCRouter(mux *router.Router) error {
|
||||||
s3PeerHandlers := &s3PeerAPIHandlers{
|
s3PeerHandlers := &s3PeerAPIHandlers{
|
||||||
loginServer{},
|
AuthRPCServer{},
|
||||||
&localBucketMetaState{
|
&localBucketMetaState{
|
||||||
ObjectAPI: newObjectLayerFn,
|
ObjectAPI: newObjectLayerFn,
|
||||||
},
|
},
|
||||||
|
@ -20,7 +20,7 @@ package cmd
|
|||||||
// call
|
// call
|
||||||
type SetBucketNotificationPeerArgs struct {
|
type SetBucketNotificationPeerArgs struct {
|
||||||
// For Auth
|
// For Auth
|
||||||
GenericArgs
|
AuthRPCArgs
|
||||||
|
|
||||||
Bucket string
|
Bucket string
|
||||||
|
|
||||||
@ -35,10 +35,9 @@ func (s *SetBucketNotificationPeerArgs) BucketUpdate(client BucketMetaState) err
|
|||||||
return client.UpdateBucketNotification(s)
|
return client.UpdateBucketNotification(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s3 *s3PeerAPIHandlers) SetBucketNotificationPeer(args *SetBucketNotificationPeerArgs, reply *GenericReply) error {
|
func (s3 *s3PeerAPIHandlers) SetBucketNotificationPeer(args *SetBucketNotificationPeerArgs, reply *AuthRPCReply) error {
|
||||||
// check auth
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
if !isAuthTokenValid(args.Token) {
|
return err
|
||||||
return errInvalidToken
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return s3.bms.UpdateBucketNotification(args)
|
return s3.bms.UpdateBucketNotification(args)
|
||||||
@ -47,7 +46,7 @@ func (s3 *s3PeerAPIHandlers) SetBucketNotificationPeer(args *SetBucketNotificati
|
|||||||
// SetBucketListenerPeerArgs - Arguments collection to SetBucketListenerPeer RPC call
|
// SetBucketListenerPeerArgs - Arguments collection to SetBucketListenerPeer RPC call
|
||||||
type SetBucketListenerPeerArgs struct {
|
type SetBucketListenerPeerArgs struct {
|
||||||
// For Auth
|
// For Auth
|
||||||
GenericArgs
|
AuthRPCArgs
|
||||||
|
|
||||||
Bucket string
|
Bucket string
|
||||||
|
|
||||||
@ -62,10 +61,9 @@ func (s *SetBucketListenerPeerArgs) BucketUpdate(client BucketMetaState) error {
|
|||||||
return client.UpdateBucketListener(s)
|
return client.UpdateBucketListener(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s3 *s3PeerAPIHandlers) SetBucketListenerPeer(args *SetBucketListenerPeerArgs, reply *GenericReply) error {
|
func (s3 *s3PeerAPIHandlers) SetBucketListenerPeer(args *SetBucketListenerPeerArgs, reply *AuthRPCReply) error {
|
||||||
// check auth
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
if !isAuthTokenValid(args.Token) {
|
return err
|
||||||
return errInvalidToken
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return s3.bms.UpdateBucketListener(args)
|
return s3.bms.UpdateBucketListener(args)
|
||||||
@ -74,7 +72,7 @@ func (s3 *s3PeerAPIHandlers) SetBucketListenerPeer(args *SetBucketListenerPeerAr
|
|||||||
// EventArgs - Arguments collection for Event RPC call
|
// EventArgs - Arguments collection for Event RPC call
|
||||||
type EventArgs struct {
|
type EventArgs struct {
|
||||||
// For Auth
|
// For Auth
|
||||||
GenericArgs
|
AuthRPCArgs
|
||||||
|
|
||||||
// event being sent
|
// event being sent
|
||||||
Event []NotificationEvent
|
Event []NotificationEvent
|
||||||
@ -84,10 +82,9 @@ type EventArgs struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// submit an event to the receiving server.
|
// submit an event to the receiving server.
|
||||||
func (s3 *s3PeerAPIHandlers) Event(args *EventArgs, reply *GenericReply) error {
|
func (s3 *s3PeerAPIHandlers) Event(args *EventArgs, reply *AuthRPCReply) error {
|
||||||
// check auth
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
if !isAuthTokenValid(args.Token) {
|
return err
|
||||||
return errInvalidToken
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return s3.bms.SendEvent(args)
|
return s3.bms.SendEvent(args)
|
||||||
@ -96,7 +93,7 @@ func (s3 *s3PeerAPIHandlers) Event(args *EventArgs, reply *GenericReply) error {
|
|||||||
// SetBucketPolicyPeerArgs - Arguments collection for SetBucketPolicyPeer RPC call
|
// SetBucketPolicyPeerArgs - Arguments collection for SetBucketPolicyPeer RPC call
|
||||||
type SetBucketPolicyPeerArgs struct {
|
type SetBucketPolicyPeerArgs struct {
|
||||||
// For Auth
|
// For Auth
|
||||||
GenericArgs
|
AuthRPCArgs
|
||||||
|
|
||||||
Bucket string
|
Bucket string
|
||||||
|
|
||||||
@ -112,10 +109,9 @@ func (s *SetBucketPolicyPeerArgs) BucketUpdate(client BucketMetaState) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// tell receiving server to update a bucket policy
|
// tell receiving server to update a bucket policy
|
||||||
func (s3 *s3PeerAPIHandlers) SetBucketPolicyPeer(args *SetBucketPolicyPeerArgs, reply *GenericReply) error {
|
func (s3 *s3PeerAPIHandlers) SetBucketPolicyPeer(args *SetBucketPolicyPeerArgs, reply *AuthRPCReply) error {
|
||||||
// check auth
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
if !isAuthTokenValid(args.Token) {
|
return err
|
||||||
return errInvalidToken
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return s3.bms.UpdateBucketPolicy(args)
|
return s3.bms.UpdateBucketPolicy(args)
|
||||||
|
@ -25,19 +25,19 @@ import (
|
|||||||
|
|
||||||
type TestRPCS3PeerSuite struct {
|
type TestRPCS3PeerSuite struct {
|
||||||
testServer TestServer
|
testServer TestServer
|
||||||
testAuthConf *authConfig
|
testAuthConf authConfig
|
||||||
disks []string
|
disks []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the suite and start the test server.
|
// Set up the suite and start the test server.
|
||||||
func (s *TestRPCS3PeerSuite) SetUpSuite(t *testing.T) {
|
func (s *TestRPCS3PeerSuite) SetUpSuite(t *testing.T) {
|
||||||
s.testServer, s.disks = StartTestS3PeerRPCServer(t)
|
s.testServer, s.disks = StartTestS3PeerRPCServer(t)
|
||||||
s.testAuthConf = &authConfig{
|
s.testAuthConf = authConfig{
|
||||||
address: s.testServer.Server.Listener.Addr().String(),
|
serverAddr: s.testServer.Server.Listener.Addr().String(),
|
||||||
accessKey: s.testServer.AccessKey,
|
accessKey: s.testServer.AccessKey,
|
||||||
secretKey: s.testServer.SecretKey,
|
secretKey: s.testServer.SecretKey,
|
||||||
path: path.Join(reservedBucket, s3Path),
|
serviceEndpoint: path.Join(reservedBucket, s3Path),
|
||||||
loginMethod: "S3.LoginHandler",
|
serviceName: "S3",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,10 +62,10 @@ func TestS3PeerRPC(t *testing.T) {
|
|||||||
// Test S3 RPC handlers
|
// Test S3 RPC handlers
|
||||||
func (s *TestRPCS3PeerSuite) testS3PeerRPC(t *testing.T) {
|
func (s *TestRPCS3PeerSuite) testS3PeerRPC(t *testing.T) {
|
||||||
// Validate for invalid token.
|
// Validate for invalid token.
|
||||||
args := GenericArgs{Token: "garbage", Timestamp: time.Now().UTC()}
|
args := AuthRPCArgs{AuthToken: "garbage", RequestTime: time.Now().UTC()}
|
||||||
rclient := newRPCClient(s.testAuthConf.address, s.testAuthConf.path, false)
|
rclient := newRPCClient(s.testAuthConf.serverAddr, s.testAuthConf.serviceEndpoint, false)
|
||||||
defer rclient.Close()
|
defer rclient.Close()
|
||||||
err := rclient.Call("S3.SetBucketNotificationPeer", &args, &GenericReply{})
|
err := rclient.Call("S3.SetBucketNotificationPeer", &args, &AuthRPCReply{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() != errInvalidToken.Error() {
|
if err.Error() != errInvalidToken.Error() {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -74,16 +74,16 @@ func (s *TestRPCS3PeerSuite) testS3PeerRPC(t *testing.T) {
|
|||||||
|
|
||||||
// Check bucket notification call works.
|
// Check bucket notification call works.
|
||||||
BNPArgs := SetBucketNotificationPeerArgs{Bucket: "bucket", NCfg: ¬ificationConfig{}}
|
BNPArgs := SetBucketNotificationPeerArgs{Bucket: "bucket", NCfg: ¬ificationConfig{}}
|
||||||
client := newAuthClient(s.testAuthConf)
|
client := newAuthRPCClient(s.testAuthConf)
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
err = client.Call("S3.SetBucketNotificationPeer", &BNPArgs, &GenericReply{})
|
err = client.Call("S3.SetBucketNotificationPeer", &BNPArgs, &AuthRPCReply{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check bucket listener update call works.
|
// Check bucket listener update call works.
|
||||||
BLPArgs := SetBucketListenerPeerArgs{Bucket: "bucket", LCfg: nil}
|
BLPArgs := SetBucketListenerPeerArgs{Bucket: "bucket", LCfg: nil}
|
||||||
err = client.Call("S3.SetBucketListenerPeer", &BLPArgs, &GenericReply{})
|
err = client.Call("S3.SetBucketListenerPeer", &BLPArgs, &AuthRPCReply{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -95,14 +95,14 @@ func (s *TestRPCS3PeerSuite) testS3PeerRPC(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
BPPArgs := SetBucketPolicyPeerArgs{Bucket: "bucket", PChBytes: pChBytes}
|
BPPArgs := SetBucketPolicyPeerArgs{Bucket: "bucket", PChBytes: pChBytes}
|
||||||
err = client.Call("S3.SetBucketPolicyPeer", &BPPArgs, &GenericReply{})
|
err = client.Call("S3.SetBucketPolicyPeer", &BPPArgs, &AuthRPCReply{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check event send event call works.
|
// Check event send event call works.
|
||||||
evArgs := EventArgs{Event: nil, Arn: "localhost:9000"}
|
evArgs := EventArgs{Event: nil, Arn: "localhost:9000"}
|
||||||
err = client.Call("S3.Event", &evArgs, &GenericReply{})
|
err = client.Call("S3.Event", &evArgs, &AuthRPCReply{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -23,18 +23,14 @@ import (
|
|||||||
"net/rpc"
|
"net/rpc"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/minio/minio/pkg/disk"
|
"github.com/minio/minio/pkg/disk"
|
||||||
)
|
)
|
||||||
|
|
||||||
type networkStorage struct {
|
type networkStorage struct {
|
||||||
networkIOErrCount int32 // ref: https://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
networkIOErrCount int32 // ref: https://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
||||||
netAddr string
|
rpcClient *AuthRPCClient
|
||||||
netPath string
|
|
||||||
rpcClient *storageRPCClient
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -99,104 +95,6 @@ func toStorageErr(err error) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// storageRPCClient is a wrapper type for RPCClient which provides JWT based authentication across reconnects.
|
|
||||||
type storageRPCClient struct {
|
|
||||||
sync.Mutex
|
|
||||||
cfg storageConfig
|
|
||||||
rpc *RPCClient // reconnect'able rpc client built on top of net/rpc Client
|
|
||||||
serverToken string // Disk rpc JWT based token.
|
|
||||||
serverVersion string // Server version exchanged by the RPC.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Storage config represents authentication credentials and Login
|
|
||||||
// method name to be used for fetching JWT tokens from the storage
|
|
||||||
// server.
|
|
||||||
type storageConfig struct {
|
|
||||||
addr string // Network address path of storage RPC server.
|
|
||||||
path string // Network storage path for HTTP dial.
|
|
||||||
secureConn bool // Indicates if this storage RPC is on a secured connection.
|
|
||||||
creds credential
|
|
||||||
}
|
|
||||||
|
|
||||||
// newStorageClient - returns a jwt based authenticated (go) storage rpc client.
|
|
||||||
func newStorageClient(storageCfg storageConfig) *storageRPCClient {
|
|
||||||
return &storageRPCClient{
|
|
||||||
// Save the config.
|
|
||||||
cfg: storageCfg,
|
|
||||||
rpc: newRPCClient(storageCfg.addr, storageCfg.path, storageCfg.secureConn),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close - closes underlying rpc connection.
|
|
||||||
func (storageClient *storageRPCClient) Close() error {
|
|
||||||
storageClient.Lock()
|
|
||||||
// reset token on closing a connection
|
|
||||||
storageClient.serverToken = ""
|
|
||||||
storageClient.Unlock()
|
|
||||||
return storageClient.rpc.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login - a jwt based authentication is performed with rpc server.
|
|
||||||
func (storageClient *storageRPCClient) Login() (err error) {
|
|
||||||
storageClient.Lock()
|
|
||||||
// As soon as the function returns unlock,
|
|
||||||
defer storageClient.Unlock()
|
|
||||||
|
|
||||||
// Return if token is already set.
|
|
||||||
if storageClient.serverToken != "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
reply := RPCLoginReply{}
|
|
||||||
if err = storageClient.rpc.Call("Storage.LoginHandler", RPCLoginArgs{
|
|
||||||
Username: storageClient.cfg.creds.AccessKey,
|
|
||||||
Password: storageClient.cfg.creds.SecretKey,
|
|
||||||
}, &reply); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate if version do indeed match.
|
|
||||||
if reply.ServerVersion != Version {
|
|
||||||
return errServerVersionMismatch
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate if server timestamp is skewed.
|
|
||||||
curTime := time.Now().UTC()
|
|
||||||
if curTime.Sub(reply.Timestamp) > globalMaxSkewTime {
|
|
||||||
return errServerTimeMismatch
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set token, time stamp as received from a successful login call.
|
|
||||||
storageClient.serverToken = reply.Token
|
|
||||||
storageClient.serverVersion = reply.ServerVersion
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call - If rpc connection isn't established yet since previous disconnect,
|
|
||||||
// connection is established, a jwt authenticated login is performed and then
|
|
||||||
// the call is performed.
|
|
||||||
func (storageClient *storageRPCClient) Call(serviceMethod string, args interface {
|
|
||||||
SetToken(token string)
|
|
||||||
SetTimestamp(tstamp time.Time)
|
|
||||||
}, reply interface{}) (err error) {
|
|
||||||
// On successful login, attempt the call.
|
|
||||||
if err = storageClient.Login(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Set token and timestamp before the rpc call.
|
|
||||||
args.SetToken(storageClient.serverToken)
|
|
||||||
args.SetTimestamp(time.Now().UTC())
|
|
||||||
|
|
||||||
// Call the underlying rpc.
|
|
||||||
err = storageClient.rpc.Call(serviceMethod, args, reply)
|
|
||||||
|
|
||||||
// Invalidate token, and mark it for re-login.
|
|
||||||
if err == rpc.ErrShutdown {
|
|
||||||
storageClient.Close()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize new storage rpc client.
|
// Initialize new storage rpc client.
|
||||||
func newStorageRPC(ep *url.URL) (StorageAPI, error) {
|
func newStorageRPC(ep *url.URL) (StorageAPI, error) {
|
||||||
if ep == nil {
|
if ep == nil {
|
||||||
@ -207,38 +105,35 @@ func newStorageRPC(ep *url.URL) (StorageAPI, error) {
|
|||||||
rpcPath := path.Join(storageRPCPath, getPath(ep))
|
rpcPath := path.Join(storageRPCPath, getPath(ep))
|
||||||
rpcAddr := ep.Host
|
rpcAddr := ep.Host
|
||||||
|
|
||||||
// Initialize rpc client with network address and rpc path.
|
serverCred := serverConfig.GetCredential()
|
||||||
accessKey := serverConfig.GetCredential().AccessKey
|
accessKey := serverCred.AccessKey
|
||||||
secretKey := serverConfig.GetCredential().SecretKey
|
secretKey := serverCred.SecretKey
|
||||||
if ep.User != nil {
|
if ep.User != nil {
|
||||||
accessKey = ep.User.Username()
|
accessKey = ep.User.Username()
|
||||||
if key, set := ep.User.Password(); set {
|
if password, ok := ep.User.Password(); ok {
|
||||||
secretKey = key
|
secretKey = password
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize network storage.
|
storageAPI := &networkStorage{
|
||||||
ndisk := &networkStorage{
|
rpcClient: newAuthRPCClient(authConfig{
|
||||||
netAddr: ep.Host,
|
accessKey: accessKey,
|
||||||
netPath: getPath(ep),
|
secretKey: secretKey,
|
||||||
rpcClient: newStorageClient(storageConfig{
|
serverAddr: rpcAddr,
|
||||||
addr: rpcAddr,
|
serviceEndpoint: rpcPath,
|
||||||
path: rpcPath,
|
secureConn: isSSL(),
|
||||||
creds: credential{
|
serviceName: "Storage",
|
||||||
AccessKey: accessKey,
|
disableReconnect: true,
|
||||||
SecretKey: secretKey,
|
|
||||||
},
|
|
||||||
secureConn: isSSL(),
|
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns successfully here.
|
// Returns successfully here.
|
||||||
return ndisk, nil
|
return storageAPI, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stringer interface compatible representation of network device.
|
// Stringer interface compatible representation of network device.
|
||||||
func (n *networkStorage) String() string {
|
func (n *networkStorage) String() string {
|
||||||
return n.netAddr + ":" + n.netPath
|
return n.rpcClient.ServerAddr() + ":" + n.rpcClient.ServiceEndpoint()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Network IO error count is kept at 256 with some simple
|
// Network IO error count is kept at 256 with some simple
|
||||||
@ -250,10 +145,9 @@ func (n *networkStorage) String() string {
|
|||||||
// incoming i/o.
|
// incoming i/o.
|
||||||
const maxAllowedNetworkIOError = 256 // maximum allowed network IOError.
|
const maxAllowedNetworkIOError = 256 // maximum allowed network IOError.
|
||||||
|
|
||||||
// Initializes the remote RPC connection by attempting a login attempt.
|
// Init - attempts a login to reconnect.
|
||||||
func (n *networkStorage) Init() (err error) {
|
func (n *networkStorage) Init() error {
|
||||||
// Attempt a login to reconnect.
|
err := n.rpcClient.Login()
|
||||||
err = n.rpcClient.Login()
|
|
||||||
return toStorageErr(err)
|
return toStorageErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,7 +172,7 @@ func (n *networkStorage) DiskInfo() (info disk.Info, err error) {
|
|||||||
return disk.Info{}, errFaultyRemoteDisk
|
return disk.Info{}, errFaultyRemoteDisk
|
||||||
}
|
}
|
||||||
|
|
||||||
args := GenericArgs{}
|
args := AuthRPCArgs{}
|
||||||
if err = n.rpcClient.Call("Storage.DiskInfoHandler", &args, &info); err != nil {
|
if err = n.rpcClient.Call("Storage.DiskInfoHandler", &args, &info); err != nil {
|
||||||
return disk.Info{}, toStorageErr(err)
|
return disk.Info{}, toStorageErr(err)
|
||||||
}
|
}
|
||||||
@ -299,7 +193,7 @@ func (n *networkStorage) MakeVol(volume string) (err error) {
|
|||||||
return errFaultyRemoteDisk
|
return errFaultyRemoteDisk
|
||||||
}
|
}
|
||||||
|
|
||||||
reply := GenericReply{}
|
reply := AuthRPCReply{}
|
||||||
args := GenericVolArgs{Vol: volume}
|
args := GenericVolArgs{Vol: volume}
|
||||||
if err := n.rpcClient.Call("Storage.MakeVolHandler", &args, &reply); err != nil {
|
if err := n.rpcClient.Call("Storage.MakeVolHandler", &args, &reply); err != nil {
|
||||||
return toStorageErr(err)
|
return toStorageErr(err)
|
||||||
@ -322,7 +216,7 @@ func (n *networkStorage) ListVols() (vols []VolInfo, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ListVols := ListVolsReply{}
|
ListVols := ListVolsReply{}
|
||||||
err = n.rpcClient.Call("Storage.ListVolsHandler", &GenericArgs{}, &ListVols)
|
err = n.rpcClient.Call("Storage.ListVolsHandler", &AuthRPCArgs{}, &ListVols)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, toStorageErr(err)
|
return nil, toStorageErr(err)
|
||||||
}
|
}
|
||||||
@ -364,7 +258,7 @@ func (n *networkStorage) DeleteVol(volume string) (err error) {
|
|||||||
return errFaultyRemoteDisk
|
return errFaultyRemoteDisk
|
||||||
}
|
}
|
||||||
|
|
||||||
reply := GenericReply{}
|
reply := AuthRPCReply{}
|
||||||
args := GenericVolArgs{Vol: volume}
|
args := GenericVolArgs{Vol: volume}
|
||||||
if err := n.rpcClient.Call("Storage.DeleteVolHandler", &args, &reply); err != nil {
|
if err := n.rpcClient.Call("Storage.DeleteVolHandler", &args, &reply); err != nil {
|
||||||
return toStorageErr(err)
|
return toStorageErr(err)
|
||||||
@ -386,7 +280,7 @@ func (n *networkStorage) PrepareFile(volume, path string, length int64) (err err
|
|||||||
return errFaultyRemoteDisk
|
return errFaultyRemoteDisk
|
||||||
}
|
}
|
||||||
|
|
||||||
reply := GenericReply{}
|
reply := AuthRPCReply{}
|
||||||
if err = n.rpcClient.Call("Storage.PrepareFileHandler", &PrepareFileArgs{
|
if err = n.rpcClient.Call("Storage.PrepareFileHandler", &PrepareFileArgs{
|
||||||
Vol: volume,
|
Vol: volume,
|
||||||
Path: path,
|
Path: path,
|
||||||
@ -411,7 +305,7 @@ func (n *networkStorage) AppendFile(volume, path string, buffer []byte) (err err
|
|||||||
return errFaultyRemoteDisk
|
return errFaultyRemoteDisk
|
||||||
}
|
}
|
||||||
|
|
||||||
reply := GenericReply{}
|
reply := AuthRPCReply{}
|
||||||
if err = n.rpcClient.Call("Storage.AppendFileHandler", &AppendFileArgs{
|
if err = n.rpcClient.Call("Storage.AppendFileHandler", &AppendFileArgs{
|
||||||
Vol: volume,
|
Vol: volume,
|
||||||
Path: path,
|
Path: path,
|
||||||
@ -545,7 +439,7 @@ func (n *networkStorage) DeleteFile(volume, path string) (err error) {
|
|||||||
return errFaultyRemoteDisk
|
return errFaultyRemoteDisk
|
||||||
}
|
}
|
||||||
|
|
||||||
reply := GenericReply{}
|
reply := AuthRPCReply{}
|
||||||
if err = n.rpcClient.Call("Storage.DeleteFileHandler", &DeleteFileArgs{
|
if err = n.rpcClient.Call("Storage.DeleteFileHandler", &DeleteFileArgs{
|
||||||
Vol: volume,
|
Vol: volume,
|
||||||
Path: path,
|
Path: path,
|
||||||
@ -569,7 +463,7 @@ func (n *networkStorage) RenameFile(srcVolume, srcPath, dstVolume, dstPath strin
|
|||||||
return errFaultyRemoteDisk
|
return errFaultyRemoteDisk
|
||||||
}
|
}
|
||||||
|
|
||||||
reply := GenericReply{}
|
reply := AuthRPCReply{}
|
||||||
if err = n.rpcClient.Call("Storage.RenameFileHandler", &RenameFileArgs{
|
if err = n.rpcClient.Call("Storage.RenameFileHandler", &RenameFileArgs{
|
||||||
SrcVol: srcVolume,
|
SrcVol: srcVolume,
|
||||||
SrcPath: srcPath,
|
SrcPath: srcPath,
|
||||||
|
@ -19,7 +19,7 @@ package cmd
|
|||||||
// GenericVolArgs - generic volume args.
|
// GenericVolArgs - generic volume args.
|
||||||
type GenericVolArgs struct {
|
type GenericVolArgs struct {
|
||||||
// Authentication token generated by Login.
|
// Authentication token generated by Login.
|
||||||
GenericArgs
|
AuthRPCArgs
|
||||||
|
|
||||||
// Name of the volume.
|
// Name of the volume.
|
||||||
Vol string
|
Vol string
|
||||||
@ -34,7 +34,7 @@ type ListVolsReply struct {
|
|||||||
// ReadAllArgs represents read all RPC arguments.
|
// ReadAllArgs represents read all RPC arguments.
|
||||||
type ReadAllArgs struct {
|
type ReadAllArgs struct {
|
||||||
// Authentication token generated by Login.
|
// Authentication token generated by Login.
|
||||||
GenericArgs
|
AuthRPCArgs
|
||||||
|
|
||||||
// Name of the volume.
|
// Name of the volume.
|
||||||
Vol string
|
Vol string
|
||||||
@ -46,7 +46,7 @@ type ReadAllArgs struct {
|
|||||||
// ReadFileArgs represents read file RPC arguments.
|
// ReadFileArgs represents read file RPC arguments.
|
||||||
type ReadFileArgs struct {
|
type ReadFileArgs struct {
|
||||||
// Authentication token generated by Login.
|
// Authentication token generated by Login.
|
||||||
GenericArgs
|
AuthRPCArgs
|
||||||
|
|
||||||
// Name of the volume.
|
// Name of the volume.
|
||||||
Vol string
|
Vol string
|
||||||
@ -64,7 +64,7 @@ type ReadFileArgs struct {
|
|||||||
// PrepareFileArgs represents append file RPC arguments.
|
// PrepareFileArgs represents append file RPC arguments.
|
||||||
type PrepareFileArgs struct {
|
type PrepareFileArgs struct {
|
||||||
// Authentication token generated by Login.
|
// Authentication token generated by Login.
|
||||||
GenericArgs
|
AuthRPCArgs
|
||||||
|
|
||||||
// Name of the volume.
|
// Name of the volume.
|
||||||
Vol string
|
Vol string
|
||||||
@ -79,7 +79,7 @@ type PrepareFileArgs struct {
|
|||||||
// AppendFileArgs represents append file RPC arguments.
|
// AppendFileArgs represents append file RPC arguments.
|
||||||
type AppendFileArgs struct {
|
type AppendFileArgs struct {
|
||||||
// Authentication token generated by Login.
|
// Authentication token generated by Login.
|
||||||
GenericArgs
|
AuthRPCArgs
|
||||||
|
|
||||||
// Name of the volume.
|
// Name of the volume.
|
||||||
Vol string
|
Vol string
|
||||||
@ -94,7 +94,7 @@ type AppendFileArgs struct {
|
|||||||
// StatFileArgs represents stat file RPC arguments.
|
// StatFileArgs represents stat file RPC arguments.
|
||||||
type StatFileArgs struct {
|
type StatFileArgs struct {
|
||||||
// Authentication token generated by Login.
|
// Authentication token generated by Login.
|
||||||
GenericArgs
|
AuthRPCArgs
|
||||||
|
|
||||||
// Name of the volume.
|
// Name of the volume.
|
||||||
Vol string
|
Vol string
|
||||||
@ -106,7 +106,7 @@ type StatFileArgs struct {
|
|||||||
// DeleteFileArgs represents delete file RPC arguments.
|
// DeleteFileArgs represents delete file RPC arguments.
|
||||||
type DeleteFileArgs struct {
|
type DeleteFileArgs struct {
|
||||||
// Authentication token generated by Login.
|
// Authentication token generated by Login.
|
||||||
GenericArgs
|
AuthRPCArgs
|
||||||
|
|
||||||
// Name of the volume.
|
// Name of the volume.
|
||||||
Vol string
|
Vol string
|
||||||
@ -118,7 +118,7 @@ type DeleteFileArgs struct {
|
|||||||
// ListDirArgs represents list contents RPC arguments.
|
// ListDirArgs represents list contents RPC arguments.
|
||||||
type ListDirArgs struct {
|
type ListDirArgs struct {
|
||||||
// Authentication token generated by Login.
|
// Authentication token generated by Login.
|
||||||
GenericArgs
|
AuthRPCArgs
|
||||||
|
|
||||||
// Name of the volume.
|
// Name of the volume.
|
||||||
Vol string
|
Vol string
|
||||||
@ -130,7 +130,7 @@ type ListDirArgs struct {
|
|||||||
// RenameFileArgs represents rename file RPC arguments.
|
// RenameFileArgs represents rename file RPC arguments.
|
||||||
type RenameFileArgs struct {
|
type RenameFileArgs struct {
|
||||||
// Authentication token generated by Login.
|
// Authentication token generated by Login.
|
||||||
GenericArgs
|
AuthRPCArgs
|
||||||
|
|
||||||
// Name of source volume.
|
// Name of source volume.
|
||||||
SrcVol string
|
SrcVol string
|
||||||
|
@ -29,7 +29,7 @@ import (
|
|||||||
// Storage server implements rpc primitives to facilitate exporting a
|
// Storage server implements rpc primitives to facilitate exporting a
|
||||||
// disk over a network.
|
// disk over a network.
|
||||||
type storageServer struct {
|
type storageServer struct {
|
||||||
loginServer
|
AuthRPCServer
|
||||||
storage StorageAPI
|
storage StorageAPI
|
||||||
path string
|
path string
|
||||||
timestamp time.Time
|
timestamp time.Time
|
||||||
@ -38,10 +38,11 @@ type storageServer struct {
|
|||||||
/// Storage operations handlers.
|
/// Storage operations handlers.
|
||||||
|
|
||||||
// DiskInfoHandler - disk info handler is rpc wrapper for DiskInfo operation.
|
// DiskInfoHandler - disk info handler is rpc wrapper for DiskInfo operation.
|
||||||
func (s *storageServer) DiskInfoHandler(args *GenericArgs, reply *disk.Info) error {
|
func (s *storageServer) DiskInfoHandler(args *AuthRPCArgs, reply *disk.Info) error {
|
||||||
if !isAuthTokenValid(args.Token) {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return errInvalidToken
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := s.storage.DiskInfo()
|
info, err := s.storage.DiskInfo()
|
||||||
*reply = info
|
*reply = info
|
||||||
return err
|
return err
|
||||||
@ -50,18 +51,20 @@ func (s *storageServer) DiskInfoHandler(args *GenericArgs, reply *disk.Info) err
|
|||||||
/// Volume operations handlers.
|
/// Volume operations handlers.
|
||||||
|
|
||||||
// MakeVolHandler - make vol handler is rpc wrapper for MakeVol operation.
|
// MakeVolHandler - make vol handler is rpc wrapper for MakeVol operation.
|
||||||
func (s *storageServer) MakeVolHandler(args *GenericVolArgs, reply *GenericReply) error {
|
func (s *storageServer) MakeVolHandler(args *GenericVolArgs, reply *AuthRPCReply) error {
|
||||||
if !isAuthTokenValid(args.Token) {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return errInvalidToken
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.storage.MakeVol(args.Vol)
|
return s.storage.MakeVol(args.Vol)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListVolsHandler - list vols handler is rpc wrapper for ListVols operation.
|
// ListVolsHandler - list vols handler is rpc wrapper for ListVols operation.
|
||||||
func (s *storageServer) ListVolsHandler(args *GenericArgs, reply *ListVolsReply) error {
|
func (s *storageServer) ListVolsHandler(args *AuthRPCArgs, reply *ListVolsReply) error {
|
||||||
if !isAuthTokenValid(args.Token) {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return errInvalidToken
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
vols, err := s.storage.ListVols()
|
vols, err := s.storage.ListVols()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -72,9 +75,10 @@ func (s *storageServer) ListVolsHandler(args *GenericArgs, reply *ListVolsReply)
|
|||||||
|
|
||||||
// StatVolHandler - stat vol handler is a rpc wrapper for StatVol operation.
|
// StatVolHandler - stat vol handler is a rpc wrapper for StatVol operation.
|
||||||
func (s *storageServer) StatVolHandler(args *GenericVolArgs, reply *VolInfo) error {
|
func (s *storageServer) StatVolHandler(args *GenericVolArgs, reply *VolInfo) error {
|
||||||
if !isAuthTokenValid(args.Token) {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return errInvalidToken
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
volInfo, err := s.storage.StatVol(args.Vol)
|
volInfo, err := s.storage.StatVol(args.Vol)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -85,10 +89,11 @@ func (s *storageServer) StatVolHandler(args *GenericVolArgs, reply *VolInfo) err
|
|||||||
|
|
||||||
// DeleteVolHandler - delete vol handler is a rpc wrapper for
|
// DeleteVolHandler - delete vol handler is a rpc wrapper for
|
||||||
// DeleteVol operation.
|
// DeleteVol operation.
|
||||||
func (s *storageServer) DeleteVolHandler(args *GenericVolArgs, reply *GenericReply) error {
|
func (s *storageServer) DeleteVolHandler(args *GenericVolArgs, reply *AuthRPCReply) error {
|
||||||
if !isAuthTokenValid(args.Token) {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return errInvalidToken
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.storage.DeleteVol(args.Vol)
|
return s.storage.DeleteVol(args.Vol)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,9 +101,10 @@ func (s *storageServer) DeleteVolHandler(args *GenericVolArgs, reply *GenericRep
|
|||||||
|
|
||||||
// StatFileHandler - stat file handler is rpc wrapper to stat file.
|
// StatFileHandler - stat file handler is rpc wrapper to stat file.
|
||||||
func (s *storageServer) StatFileHandler(args *StatFileArgs, reply *FileInfo) error {
|
func (s *storageServer) StatFileHandler(args *StatFileArgs, reply *FileInfo) error {
|
||||||
if !isAuthTokenValid(args.Token) {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return errInvalidToken
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fileInfo, err := s.storage.StatFile(args.Vol, args.Path)
|
fileInfo, err := s.storage.StatFile(args.Vol, args.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -109,9 +115,10 @@ func (s *storageServer) StatFileHandler(args *StatFileArgs, reply *FileInfo) err
|
|||||||
|
|
||||||
// ListDirHandler - list directory handler is rpc wrapper to list dir.
|
// ListDirHandler - list directory handler is rpc wrapper to list dir.
|
||||||
func (s *storageServer) ListDirHandler(args *ListDirArgs, reply *[]string) error {
|
func (s *storageServer) ListDirHandler(args *ListDirArgs, reply *[]string) error {
|
||||||
if !isAuthTokenValid(args.Token) {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return errInvalidToken
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
entries, err := s.storage.ListDir(args.Vol, args.Path)
|
entries, err := s.storage.ListDir(args.Vol, args.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -122,9 +129,10 @@ func (s *storageServer) ListDirHandler(args *ListDirArgs, reply *[]string) error
|
|||||||
|
|
||||||
// ReadAllHandler - read all handler is rpc wrapper to read all storage API.
|
// ReadAllHandler - read all handler is rpc wrapper to read all storage API.
|
||||||
func (s *storageServer) ReadAllHandler(args *ReadFileArgs, reply *[]byte) error {
|
func (s *storageServer) ReadAllHandler(args *ReadFileArgs, reply *[]byte) error {
|
||||||
if !isAuthTokenValid(args.Token) {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return errInvalidToken
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := s.storage.ReadAll(args.Vol, args.Path)
|
buf, err := s.storage.ReadAll(args.Vol, args.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -135,8 +143,8 @@ func (s *storageServer) ReadAllHandler(args *ReadFileArgs, reply *[]byte) error
|
|||||||
|
|
||||||
// ReadFileHandler - read file handler is rpc wrapper to read file.
|
// ReadFileHandler - read file handler is rpc wrapper to read file.
|
||||||
func (s *storageServer) ReadFileHandler(args *ReadFileArgs, reply *[]byte) (err error) {
|
func (s *storageServer) ReadFileHandler(args *ReadFileArgs, reply *[]byte) (err error) {
|
||||||
if !isAuthTokenValid(args.Token) {
|
if err = args.IsAuthenticated(); err != nil {
|
||||||
return errInvalidToken
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var n int64
|
var n int64
|
||||||
@ -153,34 +161,38 @@ func (s *storageServer) ReadFileHandler(args *ReadFileArgs, reply *[]byte) (err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PrepareFileHandler - prepare file handler is rpc wrapper to prepare file.
|
// PrepareFileHandler - prepare file handler is rpc wrapper to prepare file.
|
||||||
func (s *storageServer) PrepareFileHandler(args *PrepareFileArgs, reply *GenericReply) error {
|
func (s *storageServer) PrepareFileHandler(args *PrepareFileArgs, reply *AuthRPCReply) error {
|
||||||
if !isAuthTokenValid(args.Token) {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return errInvalidToken
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.storage.PrepareFile(args.Vol, args.Path, args.Size)
|
return s.storage.PrepareFile(args.Vol, args.Path, args.Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppendFileHandler - append file handler is rpc wrapper to append file.
|
// AppendFileHandler - append file handler is rpc wrapper to append file.
|
||||||
func (s *storageServer) AppendFileHandler(args *AppendFileArgs, reply *GenericReply) error {
|
func (s *storageServer) AppendFileHandler(args *AppendFileArgs, reply *AuthRPCReply) error {
|
||||||
if !isAuthTokenValid(args.Token) {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return errInvalidToken
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.storage.AppendFile(args.Vol, args.Path, args.Buffer)
|
return s.storage.AppendFile(args.Vol, args.Path, args.Buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteFileHandler - delete file handler is rpc wrapper to delete file.
|
// DeleteFileHandler - delete file handler is rpc wrapper to delete file.
|
||||||
func (s *storageServer) DeleteFileHandler(args *DeleteFileArgs, reply *GenericReply) error {
|
func (s *storageServer) DeleteFileHandler(args *DeleteFileArgs, reply *AuthRPCReply) error {
|
||||||
if !isAuthTokenValid(args.Token) {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return errInvalidToken
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.storage.DeleteFile(args.Vol, args.Path)
|
return s.storage.DeleteFile(args.Vol, args.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenameFileHandler - rename file handler is rpc wrapper to rename file.
|
// RenameFileHandler - rename file handler is rpc wrapper to rename file.
|
||||||
func (s *storageServer) RenameFileHandler(args *RenameFileArgs, reply *GenericReply) error {
|
func (s *storageServer) RenameFileHandler(args *RenameFileArgs, reply *AuthRPCReply) error {
|
||||||
if !isAuthTokenValid(args.Token) {
|
if err := args.IsAuthenticated(); err != nil {
|
||||||
return errInvalidToken
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.storage.RenameFile(args.SrcVol, args.SrcPath, args.DstVol, args.DstPath)
|
return s.storage.RenameFile(args.SrcVol, args.SrcPath, args.DstVol, args.DstPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,108 +87,113 @@ func TestStorageRPCInvalidToken(t *testing.T) {
|
|||||||
defer removeAll(st.configDir)
|
defer removeAll(st.configDir)
|
||||||
|
|
||||||
storageRPC := st.stServer
|
storageRPC := st.stServer
|
||||||
timestamp := time.Now().UTC()
|
|
||||||
ga := GenericArgs{
|
|
||||||
Token: st.token,
|
|
||||||
Timestamp: timestamp,
|
|
||||||
}
|
|
||||||
// Construct an invalid token.
|
|
||||||
badga := ga
|
|
||||||
badga.Token = "invalidToken"
|
|
||||||
|
|
||||||
// Following test cases are meant to exercise the invalid
|
// Following test cases are meant to exercise the invalid
|
||||||
// token code path of the storage RPC methods.
|
// token code path of the storage RPC methods.
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
gva := GenericVolArgs{
|
badAuthRPCArgs := AuthRPCArgs{AuthToken: "invalidToken"}
|
||||||
GenericArgs: badga,
|
badGenericVolArgs := GenericVolArgs{
|
||||||
|
AuthRPCArgs: badAuthRPCArgs,
|
||||||
Vol: "myvol",
|
Vol: "myvol",
|
||||||
}
|
}
|
||||||
// 1. DiskInfoHandler
|
// 1. DiskInfoHandler
|
||||||
diskInfoReply := &disk.Info{}
|
diskInfoReply := &disk.Info{}
|
||||||
err = storageRPC.DiskInfoHandler(&badga, diskInfoReply)
|
badAuthRPCArgs.RequestTime = time.Now().UTC()
|
||||||
|
err = storageRPC.DiskInfoHandler(&badAuthRPCArgs, diskInfoReply)
|
||||||
errorIfInvalidToken(t, err)
|
errorIfInvalidToken(t, err)
|
||||||
|
|
||||||
// 2. MakeVolHandler
|
// 2. MakeVolHandler
|
||||||
makeVolArgs := &gva
|
makeVolArgs := &badGenericVolArgs
|
||||||
makeVolReply := &GenericReply{}
|
makeVolArgs.AuthRPCArgs.RequestTime = time.Now().UTC()
|
||||||
|
makeVolReply := &AuthRPCReply{}
|
||||||
err = storageRPC.MakeVolHandler(makeVolArgs, makeVolReply)
|
err = storageRPC.MakeVolHandler(makeVolArgs, makeVolReply)
|
||||||
errorIfInvalidToken(t, err)
|
errorIfInvalidToken(t, err)
|
||||||
|
|
||||||
// 3. ListVolsHandler
|
// 3. ListVolsHandler
|
||||||
listVolReply := &ListVolsReply{}
|
listVolReply := &ListVolsReply{}
|
||||||
err = storageRPC.ListVolsHandler(&badga, listVolReply)
|
badAuthRPCArgs.RequestTime = time.Now().UTC()
|
||||||
|
err = storageRPC.ListVolsHandler(&badAuthRPCArgs, listVolReply)
|
||||||
errorIfInvalidToken(t, err)
|
errorIfInvalidToken(t, err)
|
||||||
|
|
||||||
// 4. StatVolHandler
|
// 4. StatVolHandler
|
||||||
statVolReply := &VolInfo{}
|
statVolReply := &VolInfo{}
|
||||||
statVolArgs := &gva
|
statVolArgs := &badGenericVolArgs
|
||||||
|
statVolArgs.AuthRPCArgs.RequestTime = time.Now().UTC()
|
||||||
err = storageRPC.StatVolHandler(statVolArgs, statVolReply)
|
err = storageRPC.StatVolHandler(statVolArgs, statVolReply)
|
||||||
errorIfInvalidToken(t, err)
|
errorIfInvalidToken(t, err)
|
||||||
|
|
||||||
// 5. DeleteVolHandler
|
// 5. DeleteVolHandler
|
||||||
deleteVolArgs := &gva
|
deleteVolArgs := &badGenericVolArgs
|
||||||
deleteVolReply := &GenericReply{}
|
deleteVolArgs.AuthRPCArgs.RequestTime = time.Now().UTC()
|
||||||
|
deleteVolReply := &AuthRPCReply{}
|
||||||
err = storageRPC.DeleteVolHandler(deleteVolArgs, deleteVolReply)
|
err = storageRPC.DeleteVolHandler(deleteVolArgs, deleteVolReply)
|
||||||
errorIfInvalidToken(t, err)
|
errorIfInvalidToken(t, err)
|
||||||
|
|
||||||
// 6. StatFileHandler
|
// 6. StatFileHandler
|
||||||
statFileArgs := &StatFileArgs{
|
statFileArgs := &StatFileArgs{
|
||||||
GenericArgs: badga,
|
AuthRPCArgs: badAuthRPCArgs,
|
||||||
}
|
}
|
||||||
|
statFileArgs.AuthRPCArgs.RequestTime = time.Now().UTC()
|
||||||
statReply := &FileInfo{}
|
statReply := &FileInfo{}
|
||||||
err = storageRPC.StatFileHandler(statFileArgs, statReply)
|
err = storageRPC.StatFileHandler(statFileArgs, statReply)
|
||||||
errorIfInvalidToken(t, err)
|
errorIfInvalidToken(t, err)
|
||||||
|
|
||||||
// 7. ListDirHandler
|
// 7. ListDirHandler
|
||||||
listDirArgs := &ListDirArgs{
|
listDirArgs := &ListDirArgs{
|
||||||
GenericArgs: badga,
|
AuthRPCArgs: badAuthRPCArgs,
|
||||||
}
|
}
|
||||||
|
listDirArgs.AuthRPCArgs.RequestTime = time.Now().UTC()
|
||||||
listDirReply := &[]string{}
|
listDirReply := &[]string{}
|
||||||
err = storageRPC.ListDirHandler(listDirArgs, listDirReply)
|
err = storageRPC.ListDirHandler(listDirArgs, listDirReply)
|
||||||
errorIfInvalidToken(t, err)
|
errorIfInvalidToken(t, err)
|
||||||
|
|
||||||
// 8. ReadAllHandler
|
// 8. ReadAllHandler
|
||||||
readFileArgs := &ReadFileArgs{
|
readFileArgs := &ReadFileArgs{
|
||||||
GenericArgs: badga,
|
AuthRPCArgs: badAuthRPCArgs,
|
||||||
}
|
}
|
||||||
|
readFileArgs.AuthRPCArgs.RequestTime = time.Now().UTC()
|
||||||
readFileReply := &[]byte{}
|
readFileReply := &[]byte{}
|
||||||
err = storageRPC.ReadAllHandler(readFileArgs, readFileReply)
|
err = storageRPC.ReadAllHandler(readFileArgs, readFileReply)
|
||||||
errorIfInvalidToken(t, err)
|
errorIfInvalidToken(t, err)
|
||||||
|
|
||||||
// 9. ReadFileHandler
|
// 9. ReadFileHandler
|
||||||
|
readFileArgs.AuthRPCArgs.RequestTime = time.Now().UTC()
|
||||||
err = storageRPC.ReadFileHandler(readFileArgs, readFileReply)
|
err = storageRPC.ReadFileHandler(readFileArgs, readFileReply)
|
||||||
errorIfInvalidToken(t, err)
|
errorIfInvalidToken(t, err)
|
||||||
|
|
||||||
// 10. PrepareFileHandler
|
// 10. PrepareFileHandler
|
||||||
prepFileArgs := &PrepareFileArgs{
|
prepFileArgs := &PrepareFileArgs{
|
||||||
GenericArgs: badga,
|
AuthRPCArgs: badAuthRPCArgs,
|
||||||
}
|
}
|
||||||
prepFileReply := &GenericReply{}
|
prepFileArgs.AuthRPCArgs.RequestTime = time.Now().UTC()
|
||||||
|
prepFileReply := &AuthRPCReply{}
|
||||||
err = storageRPC.PrepareFileHandler(prepFileArgs, prepFileReply)
|
err = storageRPC.PrepareFileHandler(prepFileArgs, prepFileReply)
|
||||||
errorIfInvalidToken(t, err)
|
errorIfInvalidToken(t, err)
|
||||||
|
|
||||||
// 11. AppendFileHandler
|
// 11. AppendFileHandler
|
||||||
appendArgs := &AppendFileArgs{
|
appendArgs := &AppendFileArgs{
|
||||||
GenericArgs: badga,
|
AuthRPCArgs: badAuthRPCArgs,
|
||||||
}
|
}
|
||||||
appendReply := &GenericReply{}
|
appendArgs.AuthRPCArgs.RequestTime = time.Now().UTC()
|
||||||
|
appendReply := &AuthRPCReply{}
|
||||||
err = storageRPC.AppendFileHandler(appendArgs, appendReply)
|
err = storageRPC.AppendFileHandler(appendArgs, appendReply)
|
||||||
errorIfInvalidToken(t, err)
|
errorIfInvalidToken(t, err)
|
||||||
|
|
||||||
// 12. DeleteFileHandler
|
// 12. DeleteFileHandler
|
||||||
delFileArgs := &DeleteFileArgs{
|
delFileArgs := &DeleteFileArgs{
|
||||||
GenericArgs: badga,
|
AuthRPCArgs: badAuthRPCArgs,
|
||||||
}
|
}
|
||||||
delFileRely := &GenericReply{}
|
delFileArgs.AuthRPCArgs.RequestTime = time.Now().UTC()
|
||||||
|
delFileRely := &AuthRPCReply{}
|
||||||
err = storageRPC.DeleteFileHandler(delFileArgs, delFileRely)
|
err = storageRPC.DeleteFileHandler(delFileArgs, delFileRely)
|
||||||
errorIfInvalidToken(t, err)
|
errorIfInvalidToken(t, err)
|
||||||
|
|
||||||
// 13. RenameFileHandler
|
// 13. RenameFileHandler
|
||||||
renameArgs := &RenameFileArgs{
|
renameArgs := &RenameFileArgs{
|
||||||
GenericArgs: badga,
|
AuthRPCArgs: badAuthRPCArgs,
|
||||||
}
|
}
|
||||||
renameReply := &GenericReply{}
|
renameArgs.AuthRPCArgs.RequestTime = time.Now().UTC()
|
||||||
|
renameReply := &AuthRPCReply{}
|
||||||
err = storageRPC.RenameFileHandler(renameArgs, renameReply)
|
err = storageRPC.RenameFileHandler(renameArgs, renameReply)
|
||||||
errorIfInvalidToken(t, err)
|
errorIfInvalidToken(t, err)
|
||||||
}
|
}
|
||||||
|
@ -56,9 +56,11 @@ import (
|
|||||||
|
|
||||||
// Tests should initNSLock only once.
|
// Tests should initNSLock only once.
|
||||||
func init() {
|
func init() {
|
||||||
|
// Set as non-distributed.
|
||||||
|
globalIsDistXL = false
|
||||||
|
|
||||||
// Initialize name space lock.
|
// Initialize name space lock.
|
||||||
isDist := false
|
initNSLock(globalIsDistXL)
|
||||||
initNSLock(isDist)
|
|
||||||
|
|
||||||
// Disable printing console messages during tests.
|
// Disable printing console messages during tests.
|
||||||
color.Output = ioutil.Discard
|
color.Output = ioutil.Discard
|
||||||
@ -426,9 +428,6 @@ func StartTestPeersRPCServer(t TestErrHandler, instanceType string) TestServer {
|
|||||||
// Run TestServer.
|
// Run TestServer.
|
||||||
testRPCServer.Server = httptest.NewServer(mux)
|
testRPCServer.Server = httptest.NewServer(mux)
|
||||||
|
|
||||||
// Set as non-distributed.
|
|
||||||
globalIsDistXL = false
|
|
||||||
|
|
||||||
// initialize remainder of serverCmdConfig
|
// initialize remainder of serverCmdConfig
|
||||||
testRPCServer.SrvCmdCfg = srvCfg
|
testRPCServer.SrvCmdCfg = srvCfg
|
||||||
|
|
||||||
|
@ -232,6 +232,12 @@ func TestLocalAddress(t *testing.T) {
|
|||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentIsDistXL := globalIsDistXL
|
||||||
|
defer func() {
|
||||||
|
globalIsDistXL = currentIsDistXL
|
||||||
|
}()
|
||||||
|
|
||||||
// need to set this to avoid stale values from other tests.
|
// need to set this to avoid stale values from other tests.
|
||||||
globalMinioPort = "9000"
|
globalMinioPort = "9000"
|
||||||
globalMinioHost = ""
|
globalMinioHost = ""
|
||||||
|
@ -94,7 +94,7 @@ type StorageInfoRep struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StorageInfo - web call to gather storage usage statistics.
|
// StorageInfo - web call to gather storage usage statistics.
|
||||||
func (web *webAPIHandlers) StorageInfo(r *http.Request, args *GenericArgs, reply *StorageInfoRep) error {
|
func (web *webAPIHandlers) StorageInfo(r *http.Request, args *AuthRPCArgs, reply *StorageInfoRep) error {
|
||||||
objectAPI := web.ObjectAPI()
|
objectAPI := web.ObjectAPI()
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return toJSONError(errServerNotInitialized)
|
return toJSONError(errServerNotInitialized)
|
||||||
|
@ -100,7 +100,7 @@ func getWebRPCToken(apiRouter http.Handler, accessKey, secretKey string) (token
|
|||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
request := LoginArgs{Username: accessKey, Password: secretKey}
|
request := LoginArgs{Username: accessKey, Password: secretKey}
|
||||||
reply := &LoginRep{}
|
reply := &LoginRep{}
|
||||||
req, err := newTestWebRPCRequest("Web.Login", "", request)
|
req, err := newTestWebRPCRequest("Web"+loginMethodName, "", request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -193,7 +193,7 @@ func testStorageInfoWebHandler(obj ObjectLayer, instanceType string, t TestErrHa
|
|||||||
|
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
storageInfoRequest := GenericArgs{}
|
storageInfoRequest := AuthRPCArgs{}
|
||||||
storageInfoReply := &StorageInfoRep{}
|
storageInfoReply := &StorageInfoRep{}
|
||||||
req, err := newTestWebRPCRequest("Web.StorageInfo", authorization, storageInfoRequest)
|
req, err := newTestWebRPCRequest("Web.StorageInfo", authorization, storageInfoRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -239,7 +239,7 @@ func testServerInfoWebHandler(obj ObjectLayer, instanceType string, t TestErrHan
|
|||||||
|
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
serverInfoRequest := GenericArgs{}
|
serverInfoRequest := AuthRPCArgs{}
|
||||||
serverInfoReply := &ServerInfoRep{}
|
serverInfoReply := &ServerInfoRep{}
|
||||||
req, err := newTestWebRPCRequest("Web.ServerInfo", authorization, serverInfoRequest)
|
req, err := newTestWebRPCRequest("Web.ServerInfo", authorization, serverInfoRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1204,7 +1204,7 @@ func TestWebCheckAuthorization(t *testing.T) {
|
|||||||
"PresignedGet",
|
"PresignedGet",
|
||||||
}
|
}
|
||||||
for _, rpcCall := range webRPCs {
|
for _, rpcCall := range webRPCs {
|
||||||
args := &GenericArgs{}
|
args := &AuthRPCArgs{}
|
||||||
reply := &WebGenericRep{}
|
reply := &WebGenericRep{}
|
||||||
req, nerr := newTestWebRPCRequest("Web."+rpcCall, "Bearer fooauthorization", args)
|
req, nerr := newTestWebRPCRequest("Web."+rpcCall, "Bearer fooauthorization", args)
|
||||||
if nerr != nil {
|
if nerr != nil {
|
||||||
@ -1288,7 +1288,7 @@ func TestWebObjectLayerNotReady(t *testing.T) {
|
|||||||
webRPCs := []string{"StorageInfo", "MakeBucket", "ListBuckets", "ListObjects", "RemoveObject",
|
webRPCs := []string{"StorageInfo", "MakeBucket", "ListBuckets", "ListObjects", "RemoveObject",
|
||||||
"GetBucketPolicy", "SetBucketPolicy", "ListAllBucketPolicies"}
|
"GetBucketPolicy", "SetBucketPolicy", "ListAllBucketPolicies"}
|
||||||
for _, rpcCall := range webRPCs {
|
for _, rpcCall := range webRPCs {
|
||||||
args := &GenericArgs{}
|
args := &AuthRPCArgs{}
|
||||||
reply := &WebGenericRep{}
|
reply := &WebGenericRep{}
|
||||||
req, nerr := newTestWebRPCRequest("Web."+rpcCall, authorization, args)
|
req, nerr := newTestWebRPCRequest("Web."+rpcCall, authorization, args)
|
||||||
if nerr != nil {
|
if nerr != nil {
|
||||||
@ -1392,7 +1392,7 @@ func TestWebObjectLayerFaultyDisks(t *testing.T) {
|
|||||||
"GetBucketPolicy", "SetBucketPolicy"}
|
"GetBucketPolicy", "SetBucketPolicy"}
|
||||||
|
|
||||||
for _, rpcCall := range webRPCs {
|
for _, rpcCall := range webRPCs {
|
||||||
args := &GenericArgs{}
|
args := &AuthRPCArgs{}
|
||||||
reply := &WebGenericRep{}
|
reply := &WebGenericRep{}
|
||||||
req, nerr := newTestWebRPCRequest("Web."+rpcCall, authorization, args)
|
req, nerr := newTestWebRPCRequest("Web."+rpcCall, authorization, args)
|
||||||
if nerr != nil {
|
if nerr != nil {
|
||||||
@ -1409,7 +1409,7 @@ func TestWebObjectLayerFaultyDisks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test Web.StorageInfo
|
// Test Web.StorageInfo
|
||||||
storageInfoRequest := GenericArgs{}
|
storageInfoRequest := AuthRPCArgs{}
|
||||||
storageInfoReply := &StorageInfoRep{}
|
storageInfoReply := &StorageInfoRep{}
|
||||||
req, err := newTestWebRPCRequest("Web.StorageInfo", authorization, storageInfoRequest)
|
req, err := newTestWebRPCRequest("Web.StorageInfo", authorization, storageInfoRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
2
vendor/github.com/minio/dsync/README.md
generated
vendored
2
vendor/github.com/minio/dsync/README.md
generated
vendored
@ -193,7 +193,7 @@ The basic steps in the lock process are as follows:
|
|||||||
### Unlock process
|
### Unlock process
|
||||||
|
|
||||||
The unlock process is really simple:
|
The unlock process is really simple:
|
||||||
- boardcast unlock message to all nodes that granted lock
|
- broadcast unlock message to all nodes that granted lock
|
||||||
- if a destination is not available, retry with gradually longer back-off window to still deliver
|
- if a destination is not available, retry with gradually longer back-off window to still deliver
|
||||||
- ignore the 'result' (cover for cases where destination node has gone down and came back up)
|
- ignore the 'result' (cover for cases where destination node has gone down and came back up)
|
||||||
|
|
||||||
|
121
vendor/github.com/minio/dsync/drwmutex.go
generated
vendored
121
vendor/github.com/minio/dsync/drwmutex.go
generated
vendored
@ -19,7 +19,7 @@ package dsync
|
|||||||
import (
|
import (
|
||||||
cryptorand "crypto/rand"
|
cryptorand "crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
golog "log"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
@ -36,6 +36,12 @@ func init() {
|
|||||||
dsyncLog = os.Getenv("DSYNC_LOG") == "1"
|
dsyncLog = os.Getenv("DSYNC_LOG") == "1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func log(msg ...interface{}) {
|
||||||
|
if dsyncLog {
|
||||||
|
golog.Println(msg...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// DRWMutexAcquireTimeout - tolerance limit to wait for lock acquisition before.
|
// DRWMutexAcquireTimeout - tolerance limit to wait for lock acquisition before.
|
||||||
const DRWMutexAcquireTimeout = 25 * time.Millisecond // 25ms.
|
const DRWMutexAcquireTimeout = 25 * time.Millisecond // 25ms.
|
||||||
|
|
||||||
@ -60,23 +66,6 @@ func isLocked(uid string) bool {
|
|||||||
return len(uid) > 0
|
return len(uid) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
type LockArgs struct {
|
|
||||||
Token string
|
|
||||||
Timestamp time.Time
|
|
||||||
Name string
|
|
||||||
Node string
|
|
||||||
RPCPath string
|
|
||||||
UID string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LockArgs) SetToken(token string) {
|
|
||||||
l.Token = token
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LockArgs) SetTimestamp(tstamp time.Time) {
|
|
||||||
l.Timestamp = tstamp
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDRWMutex(name string) *DRWMutex {
|
func NewDRWMutex(name string) *DRWMutex {
|
||||||
return &DRWMutex{
|
return &DRWMutex{
|
||||||
Name: name,
|
Name: name,
|
||||||
@ -152,7 +141,7 @@ func (dm *DRWMutex) lockBlocking(isReadLock bool) {
|
|||||||
|
|
||||||
// lock tries to acquire the distributed lock, returning true or false
|
// lock tries to acquire the distributed lock, returning true or false
|
||||||
//
|
//
|
||||||
func lock(clnts []RPC, locks *[]string, lockName string, isReadLock bool) bool {
|
func lock(clnts []NetLocker, locks *[]string, lockName string, isReadLock bool) bool {
|
||||||
|
|
||||||
// Create buffered channel of size equal to total number of nodes.
|
// Create buffered channel of size equal to total number of nodes.
|
||||||
ch := make(chan Granted, dnodeCount)
|
ch := make(chan Granted, dnodeCount)
|
||||||
@ -160,25 +149,29 @@ func lock(clnts []RPC, locks *[]string, lockName string, isReadLock bool) bool {
|
|||||||
for index, c := range clnts {
|
for index, c := range clnts {
|
||||||
|
|
||||||
// broadcast lock request to all nodes
|
// broadcast lock request to all nodes
|
||||||
go func(index int, isReadLock bool, c RPC) {
|
go func(index int, isReadLock bool, c NetLocker) {
|
||||||
// All client methods issuing RPCs are thread-safe and goroutine-safe,
|
// All client methods issuing RPCs are thread-safe and goroutine-safe,
|
||||||
// i.e. it is safe to call them from multiple concurrently running go routines.
|
// i.e. it is safe to call them from multiple concurrently running go routines.
|
||||||
var locked bool
|
|
||||||
bytesUid := [16]byte{}
|
bytesUid := [16]byte{}
|
||||||
cryptorand.Read(bytesUid[:])
|
cryptorand.Read(bytesUid[:])
|
||||||
uid := fmt.Sprintf("%X", bytesUid[:])
|
uid := fmt.Sprintf("%X", bytesUid[:])
|
||||||
args := LockArgs{Name: lockName, Node: clnts[ownNode].Node(), RPCPath: clnts[ownNode].RPCPath(), UID: uid}
|
|
||||||
|
args := LockArgs{
|
||||||
|
UID: uid,
|
||||||
|
Resource: lockName,
|
||||||
|
ServerAddr: clnts[ownNode].ServerAddr(),
|
||||||
|
ServiceEndpoint: clnts[ownNode].ServiceEndpoint(),
|
||||||
|
}
|
||||||
|
|
||||||
|
var locked bool
|
||||||
|
var err error
|
||||||
if isReadLock {
|
if isReadLock {
|
||||||
if err := c.Call("Dsync.RLock", &args, &locked); err != nil {
|
if locked, err = c.RLock(args); err != nil {
|
||||||
if dsyncLog {
|
log("Unable to call RLock", err)
|
||||||
log.Println("Unable to call Dsync.RLock", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := c.Call("Dsync.Lock", &args, &locked); err != nil {
|
if locked, err = c.Lock(args); err != nil {
|
||||||
if dsyncLog {
|
log("Unable to call Lock", err)
|
||||||
log.Println("Unable to call Dsync.Lock", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,7 +277,7 @@ func quorumMet(locks *[]string, isReadLock bool) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// releaseAll releases all locks that are marked as locked
|
// releaseAll releases all locks that are marked as locked
|
||||||
func releaseAll(clnts []RPC, locks *[]string, lockName string, isReadLock bool) {
|
func releaseAll(clnts []NetLocker, locks *[]string, lockName string, isReadLock bool) {
|
||||||
for lock := 0; lock < dnodeCount; lock++ {
|
for lock := 0; lock < dnodeCount; lock++ {
|
||||||
if isLocked((*locks)[lock]) {
|
if isLocked((*locks)[lock]) {
|
||||||
sendRelease(clnts[lock], lockName, (*locks)[lock], isReadLock)
|
sendRelease(clnts[lock], lockName, (*locks)[lock], isReadLock)
|
||||||
@ -385,7 +378,7 @@ func (dm *DRWMutex) ForceUnlock() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sendRelease sends a release message to a node that previously granted a lock
|
// sendRelease sends a release message to a node that previously granted a lock
|
||||||
func sendRelease(c RPC, name, uid string, isReadLock bool) {
|
func sendRelease(c NetLocker, name, uid string, isReadLock bool) {
|
||||||
|
|
||||||
backOffArray := []time.Duration{
|
backOffArray := []time.Duration{
|
||||||
30 * time.Second, // 30secs.
|
30 * time.Second, // 30secs.
|
||||||
@ -396,55 +389,47 @@ func sendRelease(c RPC, name, uid string, isReadLock bool) {
|
|||||||
1 * time.Hour, // 1hr.
|
1 * time.Hour, // 1hr.
|
||||||
}
|
}
|
||||||
|
|
||||||
go func(c RPC, name string) {
|
go func(c NetLocker, name string) {
|
||||||
|
|
||||||
for _, backOff := range backOffArray {
|
for _, backOff := range backOffArray {
|
||||||
|
|
||||||
// All client methods issuing RPCs are thread-safe and goroutine-safe,
|
// All client methods issuing RPCs are thread-safe and goroutine-safe,
|
||||||
// i.e. it is safe to call them from multiple concurrently running goroutines.
|
// i.e. it is safe to call them from multiple concurrently running goroutines.
|
||||||
var unlocked bool
|
args := LockArgs{
|
||||||
args := LockArgs{Name: name, UID: uid} // Just send name & uid (and leave out node and rpcPath; unimportant for unlocks)
|
UID: uid,
|
||||||
|
Resource: name,
|
||||||
|
ServerAddr: clnts[ownNode].ServerAddr(),
|
||||||
|
ServiceEndpoint: clnts[ownNode].ServiceEndpoint(),
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
if len(uid) == 0 {
|
if len(uid) == 0 {
|
||||||
if err := c.Call("Dsync.ForceUnlock", &args, &unlocked); err == nil {
|
if _, err = c.ForceUnlock(args); err != nil {
|
||||||
// ForceUnlock delivered, exit out
|
log("Unable to call ForceUnlock", err)
|
||||||
return
|
|
||||||
} else if err != nil {
|
|
||||||
if dsyncLog {
|
|
||||||
log.Println("Unable to call Dsync.ForceUnlock", err)
|
|
||||||
}
|
|
||||||
if nErr, ok := err.(net.Error); ok && nErr.Timeout() {
|
|
||||||
// ForceUnlock possibly failed with server timestamp mismatch, server may have restarted.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if isReadLock {
|
} else if isReadLock {
|
||||||
if err := c.Call("Dsync.RUnlock", &args, &unlocked); err == nil {
|
if _, err = c.RUnlock(args); err != nil {
|
||||||
// RUnlock delivered, exit out
|
log("Unable to call RUnlock", err)
|
||||||
return
|
|
||||||
} else if err != nil {
|
|
||||||
if dsyncLog {
|
|
||||||
log.Println("Unable to call Dsync.RUnlock", err)
|
|
||||||
}
|
|
||||||
if nErr, ok := err.(net.Error); ok && nErr.Timeout() {
|
|
||||||
// RUnlock possibly failed with server timestamp mismatch, server may have restarted.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := c.Call("Dsync.Unlock", &args, &unlocked); err == nil {
|
if _, err = c.Unlock(args); err != nil {
|
||||||
// Unlock delivered, exit out
|
log("Unable to call Unlock", err)
|
||||||
return
|
|
||||||
} else if err != nil {
|
|
||||||
if dsyncLog {
|
|
||||||
log.Println("Unable to call Dsync.Unlock", err)
|
|
||||||
}
|
|
||||||
if nErr, ok := err.(net.Error); ok && nErr.Timeout() {
|
|
||||||
// Unlock possibly failed with server timestamp mismatch, server may have restarted.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// Ignore if err is net.Error and it is occurred due to timeout.
|
||||||
|
// The cause could have been server timestamp mismatch or server may have restarted.
|
||||||
|
// FIXME: This is minio specific behaviour and we would need a way to make it generically.
|
||||||
|
if nErr, ok := err.(net.Error); ok && nErr.Timeout() {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Wait..
|
// Wait..
|
||||||
time.Sleep(backOff)
|
time.Sleep(backOff)
|
||||||
}
|
}
|
||||||
|
30
vendor/github.com/minio/dsync/dsync.go
generated
vendored
30
vendor/github.com/minio/dsync/dsync.go
generated
vendored
@ -18,16 +18,11 @@ package dsync
|
|||||||
|
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
const RpcPath = "/dsync"
|
|
||||||
const DebugPath = "/debug"
|
|
||||||
|
|
||||||
const DefaultPath = "/rpc/dsync"
|
|
||||||
|
|
||||||
// Number of nodes participating in the distributed locking.
|
// Number of nodes participating in the distributed locking.
|
||||||
var dnodeCount int
|
var dnodeCount int
|
||||||
|
|
||||||
// List of rpc client objects, one per lock server.
|
// List of rpc client objects, one per lock server.
|
||||||
var clnts []RPC
|
var clnts []NetLocker
|
||||||
|
|
||||||
// Index into rpc client array for server running on localhost
|
// Index into rpc client array for server running on localhost
|
||||||
var ownNode int
|
var ownNode int
|
||||||
@ -38,20 +33,21 @@ var dquorum int
|
|||||||
// Simple quorum for read operations, set to dNodeCount/2
|
// Simple quorum for read operations, set to dNodeCount/2
|
||||||
var dquorumReads int
|
var dquorumReads int
|
||||||
|
|
||||||
// SetNodesWithPath - initializes package-level global state variables such as clnts.
|
// Init - initializes package-level global state variables such as clnts.
|
||||||
// N B - This function should be called only once inside any program that uses
|
// N B - This function should be called only once inside any program
|
||||||
// dsync.
|
// that uses dsync.
|
||||||
func SetNodesWithClients(rpcClnts []RPC, rpcOwnNode int) (err error) {
|
func Init(rpcClnts []NetLocker, rpcOwnNode int) (err error) {
|
||||||
|
|
||||||
// Validate if number of nodes is within allowable range.
|
// Validate if number of nodes is within allowable range.
|
||||||
if dnodeCount != 0 {
|
if dnodeCount != 0 {
|
||||||
return errors.New("Cannot reinitialize dsync package")
|
return errors.New("Cannot reinitialize dsync package")
|
||||||
} else if len(rpcClnts) < 4 {
|
}
|
||||||
return errors.New("Dsync not designed for less than 4 nodes")
|
if len(rpcClnts) < 4 {
|
||||||
|
return errors.New("Dsync is not designed for less than 4 nodes")
|
||||||
} else if len(rpcClnts) > 16 {
|
} else if len(rpcClnts) > 16 {
|
||||||
return errors.New("Dsync not designed for more than 16 nodes")
|
return errors.New("Dsync is not designed for more than 16 nodes")
|
||||||
} else if len(rpcClnts)&1 == 1 {
|
} else if len(rpcClnts)%2 != 0 {
|
||||||
return errors.New("Dsync not designed for an uneven number of nodes")
|
return errors.New("Dsync is not designed for an uneven number of nodes")
|
||||||
}
|
}
|
||||||
|
|
||||||
if rpcOwnNode > len(rpcClnts) {
|
if rpcOwnNode > len(rpcClnts) {
|
||||||
@ -61,8 +57,8 @@ func SetNodesWithClients(rpcClnts []RPC, rpcOwnNode int) (err error) {
|
|||||||
dnodeCount = len(rpcClnts)
|
dnodeCount = len(rpcClnts)
|
||||||
dquorum = dnodeCount/2 + 1
|
dquorum = dnodeCount/2 + 1
|
||||||
dquorumReads = dnodeCount / 2
|
dquorumReads = dnodeCount / 2
|
||||||
// Initialize node name and rpc path for each RPCClient object.
|
// Initialize node name and rpc path for each NetLocker object.
|
||||||
clnts = make([]RPC, dnodeCount)
|
clnts = make([]NetLocker, dnodeCount)
|
||||||
copy(clnts, rpcClnts)
|
copy(clnts, rpcClnts)
|
||||||
|
|
||||||
ownNode = rpcOwnNode
|
ownNode = rpcOwnNode
|
||||||
|
56
vendor/github.com/minio/dsync/rpc-client-interface.go
generated
vendored
56
vendor/github.com/minio/dsync/rpc-client-interface.go
generated
vendored
@ -16,15 +16,51 @@
|
|||||||
|
|
||||||
package dsync
|
package dsync
|
||||||
|
|
||||||
import "time"
|
// LockArgs is minimal required values for any dsync compatible lock operation.
|
||||||
|
type LockArgs struct {
|
||||||
|
// Unique ID of lock/unlock request.
|
||||||
|
UID string
|
||||||
|
|
||||||
// RPC - is dsync compatible client interface.
|
// Resource contains a entity to be locked/unlocked.
|
||||||
type RPC interface {
|
Resource string
|
||||||
Call(serviceMethod string, args interface {
|
|
||||||
SetToken(token string)
|
// ServerAddr contains the address of the server who requested lock/unlock of the above resource.
|
||||||
SetTimestamp(tstamp time.Time)
|
ServerAddr string
|
||||||
}, reply interface{}) error
|
|
||||||
Node() string
|
// ServiceEndpoint contains the network path of above server to do lock/unlock.
|
||||||
RPCPath() string
|
ServiceEndpoint string
|
||||||
Close() error
|
}
|
||||||
|
|
||||||
|
// NetLocker is dsync compatible locker interface.
|
||||||
|
type NetLocker interface {
|
||||||
|
// Do read lock for given LockArgs. It should return
|
||||||
|
// * a boolean to indicate success/failure of the operation
|
||||||
|
// * an error on failure of lock request operation.
|
||||||
|
RLock(args LockArgs) (bool, error)
|
||||||
|
|
||||||
|
// Do write lock for given LockArgs. It should return
|
||||||
|
// * a boolean to indicate success/failure of the operation
|
||||||
|
// * an error on failure of lock request operation.
|
||||||
|
Lock(args LockArgs) (bool, error)
|
||||||
|
|
||||||
|
// Do read unlock for given LockArgs. It should return
|
||||||
|
// * a boolean to indicate success/failure of the operation
|
||||||
|
// * an error on failure of unlock request operation.
|
||||||
|
RUnlock(args LockArgs) (bool, error)
|
||||||
|
|
||||||
|
// Do write unlock for given LockArgs. It should return
|
||||||
|
// * a boolean to indicate success/failure of the operation
|
||||||
|
// * an error on failure of unlock request operation.
|
||||||
|
Unlock(args LockArgs) (bool, error)
|
||||||
|
|
||||||
|
// Unlock (read/write) forcefully for given LockArgs. It should return
|
||||||
|
// * a boolean to indicate success/failure of the operation
|
||||||
|
// * an error on failure of unlock request operation.
|
||||||
|
ForceUnlock(args LockArgs) (bool, error)
|
||||||
|
|
||||||
|
// Return this lock server address.
|
||||||
|
ServerAddr() string
|
||||||
|
|
||||||
|
// Return this lock server service endpoint on which the server runs.
|
||||||
|
ServiceEndpoint() string
|
||||||
}
|
}
|
||||||
|
6
vendor/vendor.json
vendored
6
vendor/vendor.json
vendored
@ -148,10 +148,10 @@
|
|||||||
"revisionTime": "2015-11-18T20:00:48-08:00"
|
"revisionTime": "2015-11-18T20:00:48-08:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "ddMyebkzU3xB7K8dAhM1S+Mflmo=",
|
"checksumSHA1": "NBGyq2+iTtJvJ+ElG4FzHLe1WSY=",
|
||||||
"path": "github.com/minio/dsync",
|
"path": "github.com/minio/dsync",
|
||||||
"revision": "dd0da3743e6668b03559c2905cc661bc0fceeae3",
|
"revision": "9cafd4d729eb71b31ef7851a8c8f6ceb855d0915",
|
||||||
"revisionTime": "2016-11-28T22:07:34Z"
|
"revisionTime": "2016-12-23T07:07:24Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "github.com/minio/go-homedir",
|
"path": "github.com/minio/go-homedir",
|
||||||
|
Loading…
Reference in New Issue
Block a user