| 
									
										
										
										
											2021-04-19 03:41:13 +08:00
										 |  |  | // Copyright (c) 2015-2021 MinIO, Inc.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This file is part of MinIO Object Storage stack
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify
 | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by
 | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or
 | 
					
						
							|  |  |  | // (at your option) any later version.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful
 | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					
						
							|  |  |  | // GNU Affero General Public License for more details.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License
 | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | package cmd | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2021-12-10 09:52:53 +08:00
										 |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2020-09-05 00:45:06 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2023-12-02 04:01:14 +08:00
										 |  |  | 	"errors" | 
					
						
							|  |  |  | 	"math/rand" | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	"reflect" | 
					
						
							| 
									
										
										
										
											2021-12-10 00:38:46 +08:00
										 |  |  | 	"runtime" | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	"testing" | 
					
						
							| 
									
										
										
										
											2023-12-02 04:01:14 +08:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
											  
											
												perf: websocket grid connectivity for all internode communication (#18461)
This PR adds a WebSocket grid feature that allows servers to communicate via 
a single two-way connection.
There are two request types:
* Single requests, which are `[]byte => ([]byte, error)`. This is for efficient small
  roundtrips with small payloads.
* Streaming requests which are `[]byte, chan []byte => chan []byte (and error)`,
  which allows for different combinations of full two-way streams with an initial payload.
Only a single stream is created between two machines - and there is, as such, no
server/client relation since both sides can initiate and handle requests. Which server
initiates the request is decided deterministically on the server names.
Requests are made through a mux client and server, which handles message
passing, congestion, cancelation, timeouts, etc.
If a connection is lost, all requests are canceled, and the calling server will try
to reconnect. Registered handlers can operate directly on byte 
slices or use a higher-level generics abstraction.
There is no versioning of handlers/clients, and incompatible changes should
be handled by adding new handlers.
The request path can be changed to a new one for any protocol changes.
First, all servers create a "Manager." The manager must know its address 
as well as all remote addresses. This will manage all connections.
To get a connection to any remote, ask the manager to provide it given
the remote address using.
```
func (m *Manager) Connection(host string) *Connection
```
All serverside handlers must also be registered on the manager. This will
make sure that all incoming requests are served. The number of in-flight 
requests and responses must also be given for streaming requests.
The "Connection" returned manages the mux-clients. Requests issued
to the connection will be sent to the remote.
* `func (c *Connection) Request(ctx context.Context, h HandlerID, req []byte) ([]byte, error)`
   performs a single request and returns the result. Any deadline provided on the request is
   forwarded to the server, and canceling the context will make the function return at once.
* `func (c *Connection) NewStream(ctx context.Context, h HandlerID, payload []byte) (st *Stream, err error)`
   will initiate a remote call and send the initial payload.
```Go
// A Stream is a two-way stream.
// All responses *must* be read by the caller.
// If the call is canceled through the context,
//The appropriate error will be returned.
type Stream struct {
	// Responses from the remote server.
	// Channel will be closed after an error or when the remote closes.
	// All responses *must* be read by the caller until either an error is returned or the channel is closed.
	// Canceling the context will cause the context cancellation error to be returned.
	Responses <-chan Response
	// Requests sent to the server.
	// If the handler is defined with 0 incoming capacity this will be nil.
	// Channel *must* be closed to signal the end of the stream.
	// If the request context is canceled, the stream will no longer process requests.
	Requests chan<- []byte
}
type Response struct {
	Msg []byte
	Err error
}
```
There are generic versions of the server/client handlers that allow the use of type
safe implementations for data types that support msgpack marshal/unmarshal.
											
										 
											2023-11-21 09:09:35 +08:00
										 |  |  | 	"github.com/minio/minio/internal/grid" | 
					
						
							| 
									
										
										
										
											2024-05-25 07:05:23 +08:00
										 |  |  | 	xnet "github.com/minio/pkg/v3/net" | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-05 08:44:06 +08:00
										 |  |  | // Storage REST server, storageRESTReceiver and StorageRESTClient are
 | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | // inter-dependent, below test functions are sufficient to test all of them.
 | 
					
						
							|  |  |  | func testStorageAPIDiskInfo(t *testing.T, storage StorageAPI) { | 
					
						
							|  |  |  | 	testCases := []struct { | 
					
						
							|  |  |  | 		expectErr bool | 
					
						
							|  |  |  | 	}{ | 
					
						
							| 
									
										
										
										
											2020-07-14 00:51:07 +08:00
										 |  |  | 		{true}, | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i, testCase := range testCases { | 
					
						
							| 
									
										
										
										
											2024-01-26 04:45:46 +08:00
										 |  |  | 		_, err := storage.DiskInfo(context.Background(), DiskInfoOptions{Metrics: true}) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 		expectErr := (err != nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if expectErr != testCase.expectErr { | 
					
						
							|  |  |  | 			t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-07-14 00:51:07 +08:00
										 |  |  | 		if err != errUnformattedDisk { | 
					
						
							|  |  |  | 			t.Fatalf("case %v: error: expected: %v, got: %v", i+1, errUnformattedDisk, err) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-04 04:26:57 +08:00
										 |  |  | func testStorageAPIStatInfoFile(t *testing.T, storage StorageAPI) { | 
					
						
							| 
									
										
										
										
											2024-01-18 12:41:23 +08:00
										 |  |  | 	err := storage.AppendFile(context.Background(), "foo", pathJoin("myobject", xlStorageFormatFile), []byte("foo")) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("unexpected error %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	testCases := []struct { | 
					
						
							|  |  |  | 		volumeName string | 
					
						
							|  |  |  | 		objectName string | 
					
						
							|  |  |  | 		expectErr  bool | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{"foo", "myobject", false}, | 
					
						
							|  |  |  | 		// file not found error.
 | 
					
						
							|  |  |  | 		{"foo", "yourobject", true}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i, testCase := range testCases { | 
					
						
							| 
									
										
										
										
											2021-10-02 02:50:00 +08:00
										 |  |  | 		_, err := storage.StatInfoFile(context.Background(), testCase.volumeName, testCase.objectName+"/"+xlStorageFormatFile, false) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 		expectErr := (err != nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if expectErr != testCase.expectErr { | 
					
						
							| 
									
										
										
										
											2021-10-02 02:50:00 +08:00
										 |  |  | 			t.Fatalf("case %v: error: expected: %v, got: %v, err: %v", i+1, expectErr, testCase.expectErr, err) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func testStorageAPIListDir(t *testing.T, storage StorageAPI) { | 
					
						
							| 
									
										
										
										
											2024-01-18 12:41:23 +08:00
										 |  |  | 	err := storage.AppendFile(context.Background(), "foo", "path/to/myobject", []byte("foo")) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("unexpected error %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	testCases := []struct { | 
					
						
							|  |  |  | 		volumeName     string | 
					
						
							|  |  |  | 		prefix         string | 
					
						
							|  |  |  | 		expectedResult []string | 
					
						
							|  |  |  | 		expectErr      bool | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{"foo", "path", []string{"to/"}, false}, | 
					
						
							|  |  |  | 		// prefix not found error.
 | 
					
						
							|  |  |  | 		{"foo", "nodir", nil, true}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i, testCase := range testCases { | 
					
						
							| 
									
										
										
										
											2024-01-31 04:43:25 +08:00
										 |  |  | 		result, err := storage.ListDir(context.Background(), "", testCase.volumeName, testCase.prefix, -1) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 		expectErr := (err != nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if expectErr != testCase.expectErr { | 
					
						
							|  |  |  | 			t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if !testCase.expectErr { | 
					
						
							|  |  |  | 			if !reflect.DeepEqual(result, testCase.expectedResult) { | 
					
						
							|  |  |  | 				t.Fatalf("case %v: result: expected: %v, got: %v", i+1, testCase.expectedResult, result) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func testStorageAPIReadAll(t *testing.T, storage StorageAPI) { | 
					
						
							| 
									
										
										
										
											2024-01-18 12:41:23 +08:00
										 |  |  | 	err := storage.AppendFile(context.Background(), "foo", "myobject", []byte("foo")) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("unexpected error %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	testCases := []struct { | 
					
						
							|  |  |  | 		volumeName     string | 
					
						
							|  |  |  | 		objectName     string | 
					
						
							|  |  |  | 		expectedResult []byte | 
					
						
							|  |  |  | 		expectErr      bool | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{"foo", "myobject", []byte("foo"), false}, | 
					
						
							|  |  |  | 		// file not found error.
 | 
					
						
							|  |  |  | 		{"foo", "yourobject", nil, true}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i, testCase := range testCases { | 
					
						
							| 
									
										
										
										
											2020-09-05 00:45:06 +08:00
										 |  |  | 		result, err := storage.ReadAll(context.Background(), testCase.volumeName, testCase.objectName) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 		expectErr := (err != nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if expectErr != testCase.expectErr { | 
					
						
							|  |  |  | 			t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if !testCase.expectErr { | 
					
						
							|  |  |  | 			if !reflect.DeepEqual(result, testCase.expectedResult) { | 
					
						
							|  |  |  | 				t.Fatalf("case %v: result: expected: %v, got: %v", i+1, string(testCase.expectedResult), string(result)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func testStorageAPIReadFile(t *testing.T, storage StorageAPI) { | 
					
						
							| 
									
										
										
										
											2024-01-18 12:41:23 +08:00
										 |  |  | 	err := storage.AppendFile(context.Background(), "foo", "myobject", []byte("foo")) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("unexpected error %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	testCases := []struct { | 
					
						
							|  |  |  | 		volumeName     string | 
					
						
							|  |  |  | 		objectName     string | 
					
						
							|  |  |  | 		offset         int64 | 
					
						
							|  |  |  | 		expectedResult []byte | 
					
						
							|  |  |  | 		expectErr      bool | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{"foo", "myobject", 0, []byte("foo"), false}, | 
					
						
							|  |  |  | 		{"foo", "myobject", 1, []byte("oo"), false}, | 
					
						
							|  |  |  | 		// file not found error.
 | 
					
						
							|  |  |  | 		{"foo", "yourobject", 0, nil, true}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | 	result := make([]byte, 100) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	for i, testCase := range testCases { | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | 		result = result[testCase.offset:3] | 
					
						
							| 
									
										
										
										
											2020-09-05 00:45:06 +08:00
										 |  |  | 		_, err := storage.ReadFile(context.Background(), testCase.volumeName, testCase.objectName, testCase.offset, result, nil) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 		expectErr := (err != nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if expectErr != testCase.expectErr { | 
					
						
							|  |  |  | 			t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if !testCase.expectErr { | 
					
						
							|  |  |  | 			if !reflect.DeepEqual(result, testCase.expectedResult) { | 
					
						
							|  |  |  | 				t.Fatalf("case %v: result: expected: %v, got: %v", i+1, string(testCase.expectedResult), string(result)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func testStorageAPIAppendFile(t *testing.T, storage StorageAPI) { | 
					
						
							| 
									
										
										
										
											2021-12-10 09:52:53 +08:00
										 |  |  | 	testData := []byte("foo") | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	testCases := []struct { | 
					
						
							| 
									
										
										
										
											2021-12-10 00:38:46 +08:00
										 |  |  | 		volumeName      string | 
					
						
							|  |  |  | 		objectName      string | 
					
						
							|  |  |  | 		data            []byte | 
					
						
							|  |  |  | 		expectErr       bool | 
					
						
							|  |  |  | 		ignoreIfWindows bool | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	}{ | 
					
						
							| 
									
										
										
										
											2021-12-10 09:52:53 +08:00
										 |  |  | 		{"foo", "myobject", testData, false, false}, | 
					
						
							|  |  |  | 		{"foo", "myobject-0byte", []byte{}, false, false}, | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 		// volume not found error.
 | 
					
						
							| 
									
										
										
										
											2024-01-18 12:41:23 +08:00
										 |  |  | 		{"foo-bar", "myobject", testData, true, false}, | 
					
						
							| 
									
										
										
										
											2021-12-10 09:52:53 +08:00
										 |  |  | 		// Test some weird characters over the wire.
 | 
					
						
							|  |  |  | 		{"foo", "newline\n", testData, false, true}, | 
					
						
							|  |  |  | 		{"foo", "newline\t", testData, false, true}, | 
					
						
							|  |  |  | 		{"foo", "newline \n", testData, false, true}, | 
					
						
							|  |  |  | 		{"foo", "newline$$$\n", testData, false, true}, | 
					
						
							|  |  |  | 		{"foo", "newline%%%\n", testData, false, true}, | 
					
						
							|  |  |  | 		{"foo", "newline \t % $ & * ^ # @ \n", testData, false, true}, | 
					
						
							|  |  |  | 		{"foo", "\n\tnewline \t % $ & * ^ # @ \n", testData, false, true}, | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i, testCase := range testCases { | 
					
						
							| 
									
										
										
										
											2021-12-10 00:38:46 +08:00
										 |  |  | 		if testCase.ignoreIfWindows && runtime.GOOS == "windows" { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-09-05 00:45:06 +08:00
										 |  |  | 		err := storage.AppendFile(context.Background(), testCase.volumeName, testCase.objectName, testCase.data) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 		expectErr := (err != nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if expectErr != testCase.expectErr { | 
					
						
							|  |  |  | 			t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-12-10 09:52:53 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		if !testCase.expectErr { | 
					
						
							|  |  |  | 			data, err := storage.ReadAll(context.Background(), testCase.volumeName, testCase.objectName) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if !bytes.Equal(data, testCase.data) { | 
					
						
							|  |  |  | 				t.Fatalf("case %v: expected %v, got %v", i+1, testCase.data, data) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func testStorageAPIDeleteFile(t *testing.T, storage StorageAPI) { | 
					
						
							| 
									
										
										
										
											2024-01-18 12:41:23 +08:00
										 |  |  | 	err := storage.AppendFile(context.Background(), "foo", "myobject", []byte("foo")) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("unexpected error %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	testCases := []struct { | 
					
						
							|  |  |  | 		volumeName string | 
					
						
							|  |  |  | 		objectName string | 
					
						
							|  |  |  | 		expectErr  bool | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{"foo", "myobject", false}, | 
					
						
							| 
									
										
										
										
											2021-06-08 00:35:08 +08:00
										 |  |  | 		// file not found not returned
 | 
					
						
							|  |  |  | 		{"foo", "myobject", false}, | 
					
						
							|  |  |  | 		// file not found not returned
 | 
					
						
							|  |  |  | 		{"foo", "yourobject", false}, | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i, testCase := range testCases { | 
					
						
							| 
									
										
										
										
											2022-07-12 00:15:54 +08:00
										 |  |  | 		err := storage.Delete(context.Background(), testCase.volumeName, testCase.objectName, DeleteOptions{ | 
					
						
							|  |  |  | 			Recursive: false, | 
					
						
							| 
									
										
										
										
											2023-11-29 14:35:16 +08:00
										 |  |  | 			Immediate: false, | 
					
						
							| 
									
										
										
										
											2022-07-12 00:15:54 +08:00
										 |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 		expectErr := (err != nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if expectErr != testCase.expectErr { | 
					
						
							|  |  |  | 			t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func testStorageAPIRenameFile(t *testing.T, storage StorageAPI) { | 
					
						
							| 
									
										
										
										
											2024-01-18 12:41:23 +08:00
										 |  |  | 	err := storage.AppendFile(context.Background(), "foo", "myobject", []byte("foo")) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("unexpected error %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-05 00:45:06 +08:00
										 |  |  | 	err = storage.AppendFile(context.Background(), "foo", "otherobject", []byte("foo")) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("unexpected error %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	testCases := []struct { | 
					
						
							|  |  |  | 		volumeName     string | 
					
						
							|  |  |  | 		objectName     string | 
					
						
							|  |  |  | 		destVolumeName string | 
					
						
							|  |  |  | 		destObjectName string | 
					
						
							|  |  |  | 		expectErr      bool | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{"foo", "myobject", "foo", "yourobject", false}, | 
					
						
							|  |  |  | 		{"foo", "yourobject", "bar", "myobject", false}, | 
					
						
							|  |  |  | 		// overwrite.
 | 
					
						
							|  |  |  | 		{"foo", "otherobject", "bar", "myobject", false}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i, testCase := range testCases { | 
					
						
							| 
									
										
										
										
											2020-09-05 00:45:06 +08:00
										 |  |  | 		err := storage.RenameFile(context.Background(), testCase.volumeName, testCase.objectName, testCase.destVolumeName, testCase.destObjectName) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 		expectErr := (err != nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if expectErr != testCase.expectErr { | 
					
						
							|  |  |  | 			t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												perf: websocket grid connectivity for all internode communication (#18461)
This PR adds a WebSocket grid feature that allows servers to communicate via 
a single two-way connection.
There are two request types:
* Single requests, which are `[]byte => ([]byte, error)`. This is for efficient small
  roundtrips with small payloads.
* Streaming requests which are `[]byte, chan []byte => chan []byte (and error)`,
  which allows for different combinations of full two-way streams with an initial payload.
Only a single stream is created between two machines - and there is, as such, no
server/client relation since both sides can initiate and handle requests. Which server
initiates the request is decided deterministically on the server names.
Requests are made through a mux client and server, which handles message
passing, congestion, cancelation, timeouts, etc.
If a connection is lost, all requests are canceled, and the calling server will try
to reconnect. Registered handlers can operate directly on byte 
slices or use a higher-level generics abstraction.
There is no versioning of handlers/clients, and incompatible changes should
be handled by adding new handlers.
The request path can be changed to a new one for any protocol changes.
First, all servers create a "Manager." The manager must know its address 
as well as all remote addresses. This will manage all connections.
To get a connection to any remote, ask the manager to provide it given
the remote address using.
```
func (m *Manager) Connection(host string) *Connection
```
All serverside handlers must also be registered on the manager. This will
make sure that all incoming requests are served. The number of in-flight 
requests and responses must also be given for streaming requests.
The "Connection" returned manages the mux-clients. Requests issued
to the connection will be sent to the remote.
* `func (c *Connection) Request(ctx context.Context, h HandlerID, req []byte) ([]byte, error)`
   performs a single request and returns the result. Any deadline provided on the request is
   forwarded to the server, and canceling the context will make the function return at once.
* `func (c *Connection) NewStream(ctx context.Context, h HandlerID, payload []byte) (st *Stream, err error)`
   will initiate a remote call and send the initial payload.
```Go
// A Stream is a two-way stream.
// All responses *must* be read by the caller.
// If the call is canceled through the context,
//The appropriate error will be returned.
type Stream struct {
	// Responses from the remote server.
	// Channel will be closed after an error or when the remote closes.
	// All responses *must* be read by the caller until either an error is returned or the channel is closed.
	// Canceling the context will cause the context cancellation error to be returned.
	Responses <-chan Response
	// Requests sent to the server.
	// If the handler is defined with 0 incoming capacity this will be nil.
	// Channel *must* be closed to signal the end of the stream.
	// If the request context is canceled, the stream will no longer process requests.
	Requests chan<- []byte
}
type Response struct {
	Msg []byte
	Err error
}
```
There are generic versions of the server/client handlers that allow the use of type
safe implementations for data types that support msgpack marshal/unmarshal.
											
										 
											2023-11-21 09:09:35 +08:00
										 |  |  | func newStorageRESTHTTPServerClient(t testing.TB) *storageRESTClient { | 
					
						
							|  |  |  | 	// Grid with 2 hosts
 | 
					
						
							|  |  |  | 	tg, err := grid.SetupTestGrid(2) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("SetupTestGrid: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	t.Cleanup(tg.Cleanup) | 
					
						
							| 
									
										
										
										
											2019-11-27 03:42:10 +08:00
										 |  |  | 	prevHost, prevPort := globalMinioHost, globalMinioPort | 
					
						
							|  |  |  | 	defer func() { | 
					
						
							|  |  |  | 		globalMinioHost, globalMinioPort = prevHost, prevPort | 
					
						
							|  |  |  | 	}() | 
					
						
							| 
									
										
											  
											
												perf: websocket grid connectivity for all internode communication (#18461)
This PR adds a WebSocket grid feature that allows servers to communicate via 
a single two-way connection.
There are two request types:
* Single requests, which are `[]byte => ([]byte, error)`. This is for efficient small
  roundtrips with small payloads.
* Streaming requests which are `[]byte, chan []byte => chan []byte (and error)`,
  which allows for different combinations of full two-way streams with an initial payload.
Only a single stream is created between two machines - and there is, as such, no
server/client relation since both sides can initiate and handle requests. Which server
initiates the request is decided deterministically on the server names.
Requests are made through a mux client and server, which handles message
passing, congestion, cancelation, timeouts, etc.
If a connection is lost, all requests are canceled, and the calling server will try
to reconnect. Registered handlers can operate directly on byte 
slices or use a higher-level generics abstraction.
There is no versioning of handlers/clients, and incompatible changes should
be handled by adding new handlers.
The request path can be changed to a new one for any protocol changes.
First, all servers create a "Manager." The manager must know its address 
as well as all remote addresses. This will manage all connections.
To get a connection to any remote, ask the manager to provide it given
the remote address using.
```
func (m *Manager) Connection(host string) *Connection
```
All serverside handlers must also be registered on the manager. This will
make sure that all incoming requests are served. The number of in-flight 
requests and responses must also be given for streaming requests.
The "Connection" returned manages the mux-clients. Requests issued
to the connection will be sent to the remote.
* `func (c *Connection) Request(ctx context.Context, h HandlerID, req []byte) ([]byte, error)`
   performs a single request and returns the result. Any deadline provided on the request is
   forwarded to the server, and canceling the context will make the function return at once.
* `func (c *Connection) NewStream(ctx context.Context, h HandlerID, payload []byte) (st *Stream, err error)`
   will initiate a remote call and send the initial payload.
```Go
// A Stream is a two-way stream.
// All responses *must* be read by the caller.
// If the call is canceled through the context,
//The appropriate error will be returned.
type Stream struct {
	// Responses from the remote server.
	// Channel will be closed after an error or when the remote closes.
	// All responses *must* be read by the caller until either an error is returned or the channel is closed.
	// Canceling the context will cause the context cancellation error to be returned.
	Responses <-chan Response
	// Requests sent to the server.
	// If the handler is defined with 0 incoming capacity this will be nil.
	// Channel *must* be closed to signal the end of the stream.
	// If the request context is canceled, the stream will no longer process requests.
	Requests chan<- []byte
}
type Response struct {
	Msg []byte
	Err error
}
```
There are generic versions of the server/client handlers that allow the use of type
safe implementations for data types that support msgpack marshal/unmarshal.
											
										 
											2023-11-21 09:09:35 +08:00
										 |  |  | 	// tg[0] = local, tg[1] = remote
 | 
					
						
							| 
									
										
										
										
											2019-11-27 03:42:10 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
											  
											
												perf: websocket grid connectivity for all internode communication (#18461)
This PR adds a WebSocket grid feature that allows servers to communicate via 
a single two-way connection.
There are two request types:
* Single requests, which are `[]byte => ([]byte, error)`. This is for efficient small
  roundtrips with small payloads.
* Streaming requests which are `[]byte, chan []byte => chan []byte (and error)`,
  which allows for different combinations of full two-way streams with an initial payload.
Only a single stream is created between two machines - and there is, as such, no
server/client relation since both sides can initiate and handle requests. Which server
initiates the request is decided deterministically on the server names.
Requests are made through a mux client and server, which handles message
passing, congestion, cancelation, timeouts, etc.
If a connection is lost, all requests are canceled, and the calling server will try
to reconnect. Registered handlers can operate directly on byte 
slices or use a higher-level generics abstraction.
There is no versioning of handlers/clients, and incompatible changes should
be handled by adding new handlers.
The request path can be changed to a new one for any protocol changes.
First, all servers create a "Manager." The manager must know its address 
as well as all remote addresses. This will manage all connections.
To get a connection to any remote, ask the manager to provide it given
the remote address using.
```
func (m *Manager) Connection(host string) *Connection
```
All serverside handlers must also be registered on the manager. This will
make sure that all incoming requests are served. The number of in-flight 
requests and responses must also be given for streaming requests.
The "Connection" returned manages the mux-clients. Requests issued
to the connection will be sent to the remote.
* `func (c *Connection) Request(ctx context.Context, h HandlerID, req []byte) ([]byte, error)`
   performs a single request and returns the result. Any deadline provided on the request is
   forwarded to the server, and canceling the context will make the function return at once.
* `func (c *Connection) NewStream(ctx context.Context, h HandlerID, payload []byte) (st *Stream, err error)`
   will initiate a remote call and send the initial payload.
```Go
// A Stream is a two-way stream.
// All responses *must* be read by the caller.
// If the call is canceled through the context,
//The appropriate error will be returned.
type Stream struct {
	// Responses from the remote server.
	// Channel will be closed after an error or when the remote closes.
	// All responses *must* be read by the caller until either an error is returned or the channel is closed.
	// Canceling the context will cause the context cancellation error to be returned.
	Responses <-chan Response
	// Requests sent to the server.
	// If the handler is defined with 0 incoming capacity this will be nil.
	// Channel *must* be closed to signal the end of the stream.
	// If the request context is canceled, the stream will no longer process requests.
	Requests chan<- []byte
}
type Response struct {
	Msg []byte
	Err error
}
```
There are generic versions of the server/client handlers that allow the use of type
safe implementations for data types that support msgpack marshal/unmarshal.
											
										 
											2023-11-21 09:09:35 +08:00
										 |  |  | 	// Remote URL
 | 
					
						
							|  |  |  | 	url, err := xnet.ParseHTTPURL(tg.Servers[1].URL) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("unexpected error %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-07-26 03:37:26 +08:00
										 |  |  | 	url.Path = t.TempDir() | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-27 03:42:10 +08:00
										 |  |  | 	globalMinioHost, globalMinioPort = mustSplitHostPort(url.Host) | 
					
						
							| 
									
										
										
										
											2024-07-22 15:04:48 +08:00
										 |  |  | 	globalNodeAuthToken, _ = authenticateNode(globalActiveCred.AccessKey, globalActiveCred.SecretKey) | 
					
						
							| 
									
										
										
										
											2019-11-27 03:42:10 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-05 08:44:06 +08:00
										 |  |  | 	endpoint, err := NewEndpoint(url.String()) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2018-10-05 08:44:06 +08:00
										 |  |  | 		t.Fatalf("NewEndpoint failed %v", endpoint) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-27 03:42:10 +08:00
										 |  |  | 	if err = endpoint.UpdateIsLocal(); err != nil { | 
					
						
							| 
									
										
										
										
											2019-04-20 01:26:44 +08:00
										 |  |  | 		t.Fatalf("UpdateIsLocal failed %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-14 11:27:55 +08:00
										 |  |  | 	endpoint.PoolIdx = 0 | 
					
						
							|  |  |  | 	endpoint.SetIdx = 0 | 
					
						
							|  |  |  | 	endpoint.DiskIdx = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	poolEps := []PoolEndpoints{{ | 
					
						
							| 
									
										
											  
											
												perf: websocket grid connectivity for all internode communication (#18461)
This PR adds a WebSocket grid feature that allows servers to communicate via 
a single two-way connection.
There are two request types:
* Single requests, which are `[]byte => ([]byte, error)`. This is for efficient small
  roundtrips with small payloads.
* Streaming requests which are `[]byte, chan []byte => chan []byte (and error)`,
  which allows for different combinations of full two-way streams with an initial payload.
Only a single stream is created between two machines - and there is, as such, no
server/client relation since both sides can initiate and handle requests. Which server
initiates the request is decided deterministically on the server names.
Requests are made through a mux client and server, which handles message
passing, congestion, cancelation, timeouts, etc.
If a connection is lost, all requests are canceled, and the calling server will try
to reconnect. Registered handlers can operate directly on byte 
slices or use a higher-level generics abstraction.
There is no versioning of handlers/clients, and incompatible changes should
be handled by adding new handlers.
The request path can be changed to a new one for any protocol changes.
First, all servers create a "Manager." The manager must know its address 
as well as all remote addresses. This will manage all connections.
To get a connection to any remote, ask the manager to provide it given
the remote address using.
```
func (m *Manager) Connection(host string) *Connection
```
All serverside handlers must also be registered on the manager. This will
make sure that all incoming requests are served. The number of in-flight 
requests and responses must also be given for streaming requests.
The "Connection" returned manages the mux-clients. Requests issued
to the connection will be sent to the remote.
* `func (c *Connection) Request(ctx context.Context, h HandlerID, req []byte) ([]byte, error)`
   performs a single request and returns the result. Any deadline provided on the request is
   forwarded to the server, and canceling the context will make the function return at once.
* `func (c *Connection) NewStream(ctx context.Context, h HandlerID, payload []byte) (st *Stream, err error)`
   will initiate a remote call and send the initial payload.
```Go
// A Stream is a two-way stream.
// All responses *must* be read by the caller.
// If the call is canceled through the context,
//The appropriate error will be returned.
type Stream struct {
	// Responses from the remote server.
	// Channel will be closed after an error or when the remote closes.
	// All responses *must* be read by the caller until either an error is returned or the channel is closed.
	// Canceling the context will cause the context cancellation error to be returned.
	Responses <-chan Response
	// Requests sent to the server.
	// If the handler is defined with 0 incoming capacity this will be nil.
	// Channel *must* be closed to signal the end of the stream.
	// If the request context is canceled, the stream will no longer process requests.
	Requests chan<- []byte
}
type Response struct {
	Msg []byte
	Err error
}
```
There are generic versions of the server/client handlers that allow the use of type
safe implementations for data types that support msgpack marshal/unmarshal.
											
										 
											2023-11-21 09:09:35 +08:00
										 |  |  | 		Endpoints: Endpoints{endpoint}, | 
					
						
							| 
									
										
										
										
											2023-12-14 11:27:55 +08:00
										 |  |  | 	}} | 
					
						
							|  |  |  | 	poolEps[0].SetCount = 1 | 
					
						
							|  |  |  | 	poolEps[0].DrivesPerSet = 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Register handlers on newly created servers
 | 
					
						
							|  |  |  | 	registerStorageRESTHandlers(tg.Mux[0], poolEps, tg.Managers[0]) | 
					
						
							|  |  |  | 	registerStorageRESTHandlers(tg.Mux[1], poolEps, tg.Managers[1]) | 
					
						
							| 
									
										
										
										
											2019-11-27 03:42:10 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-18 12:41:23 +08:00
										 |  |  | 	storage := globalLocalSetDrives[0][0][0] | 
					
						
							|  |  |  | 	if err = storage.MakeVol(context.Background(), "foo"); err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("unexpected error %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err = storage.MakeVol(context.Background(), "bar"); err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("unexpected error %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												perf: websocket grid connectivity for all internode communication (#18461)
This PR adds a WebSocket grid feature that allows servers to communicate via 
a single two-way connection.
There are two request types:
* Single requests, which are `[]byte => ([]byte, error)`. This is for efficient small
  roundtrips with small payloads.
* Streaming requests which are `[]byte, chan []byte => chan []byte (and error)`,
  which allows for different combinations of full two-way streams with an initial payload.
Only a single stream is created between two machines - and there is, as such, no
server/client relation since both sides can initiate and handle requests. Which server
initiates the request is decided deterministically on the server names.
Requests are made through a mux client and server, which handles message
passing, congestion, cancelation, timeouts, etc.
If a connection is lost, all requests are canceled, and the calling server will try
to reconnect. Registered handlers can operate directly on byte 
slices or use a higher-level generics abstraction.
There is no versioning of handlers/clients, and incompatible changes should
be handled by adding new handlers.
The request path can be changed to a new one for any protocol changes.
First, all servers create a "Manager." The manager must know its address 
as well as all remote addresses. This will manage all connections.
To get a connection to any remote, ask the manager to provide it given
the remote address using.
```
func (m *Manager) Connection(host string) *Connection
```
All serverside handlers must also be registered on the manager. This will
make sure that all incoming requests are served. The number of in-flight 
requests and responses must also be given for streaming requests.
The "Connection" returned manages the mux-clients. Requests issued
to the connection will be sent to the remote.
* `func (c *Connection) Request(ctx context.Context, h HandlerID, req []byte) ([]byte, error)`
   performs a single request and returns the result. Any deadline provided on the request is
   forwarded to the server, and canceling the context will make the function return at once.
* `func (c *Connection) NewStream(ctx context.Context, h HandlerID, payload []byte) (st *Stream, err error)`
   will initiate a remote call and send the initial payload.
```Go
// A Stream is a two-way stream.
// All responses *must* be read by the caller.
// If the call is canceled through the context,
//The appropriate error will be returned.
type Stream struct {
	// Responses from the remote server.
	// Channel will be closed after an error or when the remote closes.
	// All responses *must* be read by the caller until either an error is returned or the channel is closed.
	// Canceling the context will cause the context cancellation error to be returned.
	Responses <-chan Response
	// Requests sent to the server.
	// If the handler is defined with 0 incoming capacity this will be nil.
	// Channel *must* be closed to signal the end of the stream.
	// If the request context is canceled, the stream will no longer process requests.
	Requests chan<- []byte
}
type Response struct {
	Msg []byte
	Err error
}
```
There are generic versions of the server/client handlers that allow the use of type
safe implementations for data types that support msgpack marshal/unmarshal.
											
										 
											2023-11-21 09:09:35 +08:00
										 |  |  | 	restClient, err := newStorageRESTClient(endpoint, false, tg.Managers[0]) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-02 04:01:14 +08:00
										 |  |  | 	for { | 
					
						
							| 
									
										
										
										
											2024-01-26 04:45:46 +08:00
										 |  |  | 		_, err := restClient.DiskInfo(context.Background(), DiskInfoOptions{}) | 
					
						
							| 
									
										
										
										
											2023-12-02 04:01:14 +08:00
										 |  |  | 		if err == nil || errors.Is(err, errUnformattedDisk) { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		time.Sleep(time.Duration(rand.Float64() * float64(100*time.Millisecond))) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-26 03:37:26 +08:00
										 |  |  | 	return restClient | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-05 08:44:06 +08:00
										 |  |  | func TestStorageRESTClientDiskInfo(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-07-26 03:37:26 +08:00
										 |  |  | 	restClient := newStorageRESTHTTPServerClient(t) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-05 08:44:06 +08:00
										 |  |  | 	testStorageAPIDiskInfo(t, restClient) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-04 04:26:57 +08:00
										 |  |  | func TestStorageRESTClientStatInfoFile(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-07-26 03:37:26 +08:00
										 |  |  | 	restClient := newStorageRESTHTTPServerClient(t) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-04 04:26:57 +08:00
										 |  |  | 	testStorageAPIStatInfoFile(t, restClient) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-05 08:44:06 +08:00
										 |  |  | func TestStorageRESTClientListDir(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-07-26 03:37:26 +08:00
										 |  |  | 	restClient := newStorageRESTHTTPServerClient(t) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-05 08:44:06 +08:00
										 |  |  | 	testStorageAPIListDir(t, restClient) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-05 08:44:06 +08:00
										 |  |  | func TestStorageRESTClientReadAll(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-07-26 03:37:26 +08:00
										 |  |  | 	restClient := newStorageRESTHTTPServerClient(t) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-05 08:44:06 +08:00
										 |  |  | 	testStorageAPIReadAll(t, restClient) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-05 08:44:06 +08:00
										 |  |  | func TestStorageRESTClientReadFile(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-07-26 03:37:26 +08:00
										 |  |  | 	restClient := newStorageRESTHTTPServerClient(t) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-05 08:44:06 +08:00
										 |  |  | 	testStorageAPIReadFile(t, restClient) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-05 08:44:06 +08:00
										 |  |  | func TestStorageRESTClientAppendFile(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-07-26 03:37:26 +08:00
										 |  |  | 	restClient := newStorageRESTHTTPServerClient(t) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-05 08:44:06 +08:00
										 |  |  | 	testStorageAPIAppendFile(t, restClient) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-05 08:44:06 +08:00
										 |  |  | func TestStorageRESTClientDeleteFile(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-07-26 03:37:26 +08:00
										 |  |  | 	restClient := newStorageRESTHTTPServerClient(t) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-05 08:44:06 +08:00
										 |  |  | 	testStorageAPIDeleteFile(t, restClient) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-05 08:44:06 +08:00
										 |  |  | func TestStorageRESTClientRenameFile(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-07-26 03:37:26 +08:00
										 |  |  | 	restClient := newStorageRESTHTTPServerClient(t) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-05 08:44:06 +08:00
										 |  |  | 	testStorageAPIRenameFile(t, restClient) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | } |