2014-10-21 22:02:20 +00:00
|
|
|
package testsuites
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2017-08-11 22:31:16 +00:00
|
|
|
"context"
|
2023-08-27 10:06:16 +00:00
|
|
|
crand "crypto/rand"
|
2017-05-15 15:34:14 +00:00
|
|
|
"crypto/sha256"
|
2014-12-05 19:46:41 +00:00
|
|
|
"io"
|
2014-10-21 22:02:20 +00:00
|
|
|
"math/rand"
|
2015-01-07 16:31:38 +00:00
|
|
|
"net/http"
|
2023-10-24 19:49:47 +00:00
|
|
|
"net/http/httptest"
|
2014-11-20 22:50:51 +00:00
|
|
|
"os"
|
2014-10-21 22:02:20 +00:00
|
|
|
"path"
|
|
|
|
"sort"
|
2014-12-03 04:43:31 +00:00
|
|
|
"sync"
|
2014-10-21 22:02:20 +00:00
|
|
|
"testing"
|
2014-12-04 00:37:46 +00:00
|
|
|
"time"
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2020-08-24 11:18:39 +00:00
|
|
|
storagedriver "github.com/distribution/distribution/v3/registry/storage/driver"
|
2023-12-07 18:00:27 +00:00
|
|
|
"github.com/stretchr/testify/suite"
|
2014-10-21 22:02:20 +00:00
|
|
|
)
|
|
|
|
|
2023-11-28 06:50:48 +00:00
|
|
|
// randomBytes pre-allocates all of the memory sizes needed for the test. If
|
|
|
|
// anything panics while accessing randomBytes, just make this number bigger.
|
|
|
|
var randomBytes = make([]byte, 128<<20)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
_, _ = crand.Read(randomBytes) // always returns len(randomBytes) and nil error
|
|
|
|
}
|
|
|
|
|
2014-11-20 22:50:51 +00:00
|
|
|
// SkipCheck is a function used to determine if a test suite should be skipped.
|
|
|
|
// If a SkipCheck returns a non-empty skip reason, the suite is skipped with
|
|
|
|
// the given reason.
|
2014-10-27 20:24:07 +00:00
|
|
|
type SkipCheck func() (reason string)
|
|
|
|
|
2014-11-20 22:50:51 +00:00
|
|
|
// NeverSkip is a default SkipCheck which never skips the suite.
|
2014-10-29 19:14:19 +00:00
|
|
|
var NeverSkip SkipCheck = func() string { return "" }
|
2014-10-27 20:24:07 +00:00
|
|
|
|
2014-11-20 22:50:51 +00:00
|
|
|
// DriverConstructor is a function which returns a new
|
|
|
|
// storagedriver.StorageDriver.
|
2014-10-21 22:02:20 +00:00
|
|
|
type DriverConstructor func() (storagedriver.StorageDriver, error)
|
2014-10-29 19:14:19 +00:00
|
|
|
|
2014-11-20 22:50:51 +00:00
|
|
|
// DriverTeardown is a function which cleans up a suite's
|
|
|
|
// storagedriver.StorageDriver.
|
2014-10-21 22:02:20 +00:00
|
|
|
type DriverTeardown func() error
|
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
// DriverSuite is a [suite.Suite] test suite designed to test a
|
2015-06-29 23:39:45 +00:00
|
|
|
// storagedriver.StorageDriver. The intended way to create a DriverSuite is
|
2023-12-07 18:00:27 +00:00
|
|
|
// with [NewDriverSuite].
|
2014-10-21 22:02:20 +00:00
|
|
|
type DriverSuite struct {
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Suite
|
2014-10-21 22:02:20 +00:00
|
|
|
Constructor DriverConstructor
|
|
|
|
Teardown DriverTeardown
|
2014-10-27 20:24:07 +00:00
|
|
|
SkipCheck
|
2014-10-21 22:02:20 +00:00
|
|
|
storagedriver.StorageDriver
|
2015-04-27 22:58:58 +00:00
|
|
|
ctx context.Context
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
func NewDriverSuite(driverConstructor DriverConstructor, skipCheck SkipCheck) *DriverSuite {
|
|
|
|
return &DriverSuite{
|
|
|
|
Constructor: driverConstructor,
|
|
|
|
SkipCheck: skipCheck,
|
|
|
|
ctx: context.Background(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetupSuite implements [suite.SetupAllSuite] interface.
|
|
|
|
func (suite *DriverSuite) SetupSuite() {
|
2014-10-27 20:24:07 +00:00
|
|
|
if reason := suite.SkipCheck(); reason != "" {
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.T().Skip(reason)
|
2014-10-27 20:24:07 +00:00
|
|
|
}
|
2014-10-21 22:02:20 +00:00
|
|
|
d, err := suite.Constructor()
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
suite.StorageDriver = d
|
|
|
|
}
|
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
// TearDownSuite implements [suite.TearDownAllSuite].
|
|
|
|
func (suite *DriverSuite) TearDownSuite() {
|
2014-10-21 22:02:20 +00:00
|
|
|
if suite.Teardown != nil {
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(suite.Teardown())
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
// TearDownTest implements [suite.TearDownTestSuite].
|
2014-12-11 22:11:47 +00:00
|
|
|
// This causes the suite to abort if any files are left around in the storage
|
|
|
|
// driver.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TearDownTest() {
|
2015-04-27 22:58:58 +00:00
|
|
|
files, _ := suite.StorageDriver.List(suite.ctx, "/")
|
2014-12-11 22:11:47 +00:00
|
|
|
if len(files) > 0 {
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.T().Fatalf("Storage driver did not clean up properly. Offending files: %#v", files)
|
2014-12-11 22:11:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-10 21:11:17 +00:00
|
|
|
// TestRootExists ensures that all storage drivers have a root path by default.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestRootExists() {
|
2015-08-10 21:11:17 +00:00
|
|
|
_, err := suite.StorageDriver.List(suite.ctx, "/")
|
|
|
|
if err != nil {
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.T().Fatalf(`the root path "/" should always exist: %v`, err)
|
2015-08-10 21:11:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-11 22:54:04 +00:00
|
|
|
// TestValidPaths checks that various valid file paths are accepted by the
|
|
|
|
// storage driver.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestValidPaths() {
|
2014-12-11 22:54:04 +00:00
|
|
|
contents := randomContents(64)
|
2015-02-02 21:17:33 +00:00
|
|
|
validFiles := []string{
|
|
|
|
"/a",
|
|
|
|
"/2",
|
|
|
|
"/aa",
|
|
|
|
"/a.a",
|
|
|
|
"/0-9/abcdefg",
|
|
|
|
"/abcdefg/z.75",
|
|
|
|
"/abc/1.2.3.4.5-6_zyx/123.z/4",
|
|
|
|
"/docker/docker-registry",
|
|
|
|
"/123.abc",
|
|
|
|
"/abc./abc",
|
|
|
|
"/.abc",
|
|
|
|
"/a--b",
|
|
|
|
"/a-.b",
|
2015-04-07 21:14:45 +00:00
|
|
|
"/_.abc",
|
|
|
|
"/Docker/docker-registry",
|
2022-11-02 21:05:45 +00:00
|
|
|
"/Abc/Cba",
|
|
|
|
}
|
2014-12-11 22:54:04 +00:00
|
|
|
|
|
|
|
for _, filename := range validFiles {
|
2015-04-27 22:58:58 +00:00
|
|
|
err := suite.StorageDriver.PutContent(suite.ctx, filename, contents)
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(firstPart(filename))
|
|
|
|
suite.Require().NoError(err)
|
2014-12-11 22:54:04 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
received, err := suite.StorageDriver.GetContent(suite.ctx, filename)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(contents, received)
|
2014-12-11 22:54:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) deletePath(path string) {
|
2016-01-19 14:09:32 +00:00
|
|
|
for tries := 2; tries > 0; tries-- {
|
|
|
|
err := suite.StorageDriver.Delete(suite.ctx, path)
|
|
|
|
if _, ok := err.(storagedriver.PathNotFoundError); ok {
|
|
|
|
err = nil
|
|
|
|
}
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2018-08-06 21:34:15 +00:00
|
|
|
paths, _ := suite.StorageDriver.List(suite.ctx, path)
|
2016-01-19 14:09:32 +00:00
|
|
|
if len(paths) == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
time.Sleep(time.Second * 2)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-11 22:54:04 +00:00
|
|
|
// TestInvalidPaths checks that various invalid file paths are rejected by the
|
|
|
|
// storage driver.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestInvalidPaths() {
|
2014-12-11 22:54:04 +00:00
|
|
|
contents := randomContents(64)
|
2015-02-02 21:17:33 +00:00
|
|
|
invalidFiles := []string{
|
|
|
|
"",
|
|
|
|
"/",
|
|
|
|
"abc",
|
|
|
|
"123.abc",
|
|
|
|
"//bcd",
|
2022-11-02 21:05:45 +00:00
|
|
|
"/abc_123/",
|
|
|
|
}
|
2014-12-11 22:54:04 +00:00
|
|
|
|
|
|
|
for _, filename := range invalidFiles {
|
2015-04-27 22:58:58 +00:00
|
|
|
err := suite.StorageDriver.PutContent(suite.ctx, filename, contents)
|
2016-02-11 00:26:29 +00:00
|
|
|
// only delete if file was successfully written
|
2016-01-19 14:09:32 +00:00
|
|
|
if err == nil {
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(firstPart(filename))
|
2016-01-19 14:09:32 +00:00
|
|
|
}
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err)
|
|
|
|
suite.Require().IsType(err, storagedriver.InvalidPathError{})
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
2014-12-11 22:54:04 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
_, err = suite.StorageDriver.GetContent(suite.ctx, filename)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err)
|
|
|
|
suite.Require().IsType(err, storagedriver.InvalidPathError{})
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
2014-12-11 22:54:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-20 22:50:51 +00:00
|
|
|
// TestWriteRead1 tests a simple write-read workflow.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestWriteRead1() {
|
2014-12-09 02:22:08 +00:00
|
|
|
filename := randomPath(32)
|
2014-10-21 22:02:20 +00:00
|
|
|
contents := []byte("a")
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.writeReadCompare(filename, contents)
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-11-20 22:50:51 +00:00
|
|
|
// TestWriteRead2 tests a simple write-read workflow with unicode data.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestWriteRead2() {
|
2014-12-09 02:22:08 +00:00
|
|
|
filename := randomPath(32)
|
2014-10-21 22:02:20 +00:00
|
|
|
contents := []byte("\xc3\x9f")
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.writeReadCompare(filename, contents)
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-11-20 22:50:51 +00:00
|
|
|
// TestWriteRead3 tests a simple write-read workflow with a small string.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestWriteRead3() {
|
2014-12-09 02:22:08 +00:00
|
|
|
filename := randomPath(32)
|
|
|
|
contents := randomContents(32)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.writeReadCompare(filename, contents)
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-11-20 22:50:51 +00:00
|
|
|
// TestWriteRead4 tests a simple write-read workflow with 1MB of data.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestWriteRead4() {
|
2014-12-09 02:22:08 +00:00
|
|
|
filename := randomPath(32)
|
|
|
|
contents := randomContents(1024 * 1024)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.writeReadCompare(filename, contents)
|
2014-12-09 02:22:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TestWriteReadNonUTF8 tests that non-utf8 data may be written to the storage
|
|
|
|
// driver safely.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestWriteReadNonUTF8() {
|
2014-12-09 02:22:08 +00:00
|
|
|
filename := randomPath(32)
|
|
|
|
contents := []byte{0x80, 0x80, 0x80, 0x80}
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.writeReadCompare(filename, contents)
|
2014-12-09 02:22:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TestTruncate tests that putting smaller contents than an original file does
|
|
|
|
// remove the excess contents.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestTruncate() {
|
2014-12-09 02:22:08 +00:00
|
|
|
filename := randomPath(32)
|
|
|
|
contents := randomContents(1024 * 1024)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.writeReadCompare(filename, contents)
|
2014-12-09 02:22:08 +00:00
|
|
|
|
|
|
|
contents = randomContents(1024)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.writeReadCompare(filename, contents)
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-11-20 22:50:51 +00:00
|
|
|
// TestReadNonexistent tests reading content from an empty path.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestReadNonexistent() {
|
2014-12-09 02:22:08 +00:00
|
|
|
filename := randomPath(32)
|
2015-04-27 22:58:58 +00:00
|
|
|
_, err := suite.StorageDriver.GetContent(suite.ctx, filename)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err)
|
|
|
|
suite.Require().IsType(err, storagedriver.PathNotFoundError{})
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-11-20 22:50:51 +00:00
|
|
|
// TestWriteReadStreams1 tests a simple write-read streaming workflow.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestWriteReadStreams1() {
|
2014-12-09 02:22:08 +00:00
|
|
|
filename := randomPath(32)
|
2014-10-21 22:02:20 +00:00
|
|
|
contents := []byte("a")
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.writeReadCompareStreams(filename, contents)
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// TestWriteReadStreams2 tests a simple write-read streaming workflow with
|
2014-11-20 22:50:51 +00:00
|
|
|
// unicode data.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestWriteReadStreams2() {
|
2014-12-09 02:22:08 +00:00
|
|
|
filename := randomPath(32)
|
2014-10-21 22:02:20 +00:00
|
|
|
contents := []byte("\xc3\x9f")
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.writeReadCompareStreams(filename, contents)
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// TestWriteReadStreams3 tests a simple write-read streaming workflow with a
|
2014-11-20 22:50:51 +00:00
|
|
|
// small amount of data.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestWriteReadStreams3() {
|
2014-12-09 02:22:08 +00:00
|
|
|
filename := randomPath(32)
|
|
|
|
contents := randomContents(32)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.writeReadCompareStreams(filename, contents)
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// TestWriteReadStreams4 tests a simple write-read streaming workflow with 1MB
|
2014-11-20 22:50:51 +00:00
|
|
|
// of data.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestWriteReadStreams4() {
|
2014-12-09 02:22:08 +00:00
|
|
|
filename := randomPath(32)
|
|
|
|
contents := randomContents(1024 * 1024)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.writeReadCompareStreams(filename, contents)
|
2014-12-09 02:22:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TestWriteReadStreamsNonUTF8 tests that non-utf8 data may be written to the
|
|
|
|
// storage driver safely.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestWriteReadStreamsNonUTF8() {
|
2014-12-09 02:22:08 +00:00
|
|
|
filename := randomPath(32)
|
|
|
|
contents := []byte{0x80, 0x80, 0x80, 0x80}
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.writeReadCompareStreams(filename, contents)
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-12-09 02:22:08 +00:00
|
|
|
// TestWriteReadLargeStreams tests that a 5GB file may be written to the storage
|
|
|
|
// driver safely.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestWriteReadLargeStreams() {
|
2014-12-09 02:22:08 +00:00
|
|
|
if testing.Short() {
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.T().Skip("Skipping test in short mode")
|
2014-12-09 02:22:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
filename := randomPath(32)
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(firstPart(filename))
|
2014-12-09 02:22:08 +00:00
|
|
|
|
2017-05-15 15:34:14 +00:00
|
|
|
checksum := sha256.New()
|
2015-01-18 01:08:04 +00:00
|
|
|
var fileSize int64 = 5 * 1024 * 1024 * 1024
|
|
|
|
|
|
|
|
contents := newRandReader(fileSize)
|
2016-02-08 22:29:21 +00:00
|
|
|
|
|
|
|
writer, err := suite.StorageDriver.Writer(suite.ctx, filename, false)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-02-08 22:29:21 +00:00
|
|
|
written, err := io.Copy(writer, io.TeeReader(contents, checksum))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(fileSize, written)
|
2014-12-09 02:22:08 +00:00
|
|
|
|
2023-10-18 09:34:10 +00:00
|
|
|
err = writer.Commit(context.Background())
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-02-08 22:29:21 +00:00
|
|
|
err = writer.Close()
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-02-08 22:29:21 +00:00
|
|
|
|
|
|
|
reader, err := suite.StorageDriver.Reader(suite.ctx, filename, 0)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2015-07-17 06:55:31 +00:00
|
|
|
defer reader.Close()
|
2014-12-09 02:22:08 +00:00
|
|
|
|
2017-05-15 15:34:14 +00:00
|
|
|
writtenChecksum := sha256.New()
|
2023-11-18 06:50:40 +00:00
|
|
|
if _, err := io.Copy(writtenChecksum, reader); err != nil {
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2023-11-18 06:50:40 +00:00
|
|
|
}
|
2014-12-09 02:22:08 +00:00
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Equal(checksum.Sum(nil), writtenChecksum.Sum(nil))
|
2014-12-09 02:22:08 +00:00
|
|
|
}
|
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
// TestReaderWithOffset tests that the appropriate data is streamed when
|
2014-12-04 00:37:46 +00:00
|
|
|
// reading with a given offset.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestReaderWithOffset() {
|
2014-12-09 02:22:08 +00:00
|
|
|
filename := randomPath(32)
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(firstPart(filename))
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2014-12-04 00:37:46 +00:00
|
|
|
chunkSize := int64(32)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2014-12-09 02:22:08 +00:00
|
|
|
contentsChunk1 := randomContents(chunkSize)
|
|
|
|
contentsChunk2 := randomContents(chunkSize)
|
|
|
|
contentsChunk3 := randomContents(chunkSize)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err := suite.StorageDriver.PutContent(suite.ctx, filename, append(append(contentsChunk1, contentsChunk2...), contentsChunk3...))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
reader, err := suite.StorageDriver.Reader(suite.ctx, filename, 0)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-12-04 00:37:46 +00:00
|
|
|
defer reader.Close()
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2022-11-02 21:55:22 +00:00
|
|
|
readContents, err := io.ReadAll(reader)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-12-04 00:37:46 +00:00
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Equal(append(append(contentsChunk1, contentsChunk2...), contentsChunk3...), readContents)
|
2014-12-04 00:37:46 +00:00
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
reader, err = suite.StorageDriver.Reader(suite.ctx, filename, chunkSize)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-12-04 00:37:46 +00:00
|
|
|
defer reader.Close()
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2022-11-02 21:55:22 +00:00
|
|
|
readContents, err = io.ReadAll(reader)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Equal(append(contentsChunk2, contentsChunk3...), readContents)
|
2014-12-04 00:37:46 +00:00
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
reader, err = suite.StorageDriver.Reader(suite.ctx, filename, chunkSize*2)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-12-04 00:37:46 +00:00
|
|
|
defer reader.Close()
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2022-11-02 21:55:22 +00:00
|
|
|
readContents, err = io.ReadAll(reader)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(contentsChunk3, readContents)
|
2014-12-05 19:46:41 +00:00
|
|
|
|
2019-04-05 19:20:20 +00:00
|
|
|
// Ensure we get invalid offset for negative offsets.
|
2016-02-08 22:29:21 +00:00
|
|
|
reader, err = suite.StorageDriver.Reader(suite.ctx, filename, -1)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().IsType(err, storagedriver.InvalidOffsetError{})
|
|
|
|
suite.Require().Equal(int64(-1), err.(storagedriver.InvalidOffsetError).Offset)
|
|
|
|
suite.Require().Equal(filename, err.(storagedriver.InvalidOffsetError).Path)
|
|
|
|
suite.Require().Nil(reader)
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
2014-12-05 19:46:41 +00:00
|
|
|
|
|
|
|
// Read past the end of the content and make sure we get a reader that
|
|
|
|
// returns 0 bytes and io.EOF
|
2016-02-08 22:29:21 +00:00
|
|
|
reader, err = suite.StorageDriver.Reader(suite.ctx, filename, chunkSize*3)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-12-05 19:46:41 +00:00
|
|
|
defer reader.Close()
|
|
|
|
|
|
|
|
buf := make([]byte, chunkSize)
|
|
|
|
n, err := reader.Read(buf)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().ErrorIs(err, io.EOF)
|
|
|
|
suite.Require().Equal(0, n)
|
2014-12-05 19:46:41 +00:00
|
|
|
|
|
|
|
// Check the N-1 boundary condition, ensuring we get 1 byte then io.EOF.
|
2016-02-08 22:29:21 +00:00
|
|
|
reader, err = suite.StorageDriver.Reader(suite.ctx, filename, chunkSize*3-1)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-12-05 19:46:41 +00:00
|
|
|
defer reader.Close()
|
|
|
|
|
|
|
|
n, err = reader.Read(buf)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Equal(1, n)
|
2014-12-05 19:46:41 +00:00
|
|
|
|
|
|
|
// We don't care whether the io.EOF comes on the this read or the first
|
|
|
|
// zero read, but the only error acceptable here is io.EOF.
|
|
|
|
if err != nil {
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().ErrorIs(err, io.EOF)
|
2014-12-05 19:46:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Any more reads should result in zero bytes and io.EOF
|
|
|
|
n, err = reader.Read(buf)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Equal(0, n)
|
|
|
|
suite.Require().ErrorIs(err, io.EOF)
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2015-01-26 21:38:51 +00:00
|
|
|
// TestContinueStreamAppendLarge tests that a stream write can be appended to without
|
|
|
|
// corrupting the data with a large chunk size.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestContinueStreamAppendLarge() {
|
2023-05-30 12:52:39 +00:00
|
|
|
chunkSize := int64(10 * 1024 * 1024)
|
|
|
|
if suite.Name() == "azure" {
|
|
|
|
chunkSize = int64(4 * 1024 * 1024)
|
|
|
|
}
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.testContinueStreamAppend(chunkSize)
|
2015-01-26 21:38:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TestContinueStreamAppendSmall is the same as TestContinueStreamAppendLarge, but only
|
|
|
|
// with a tiny chunk size in order to test corner cases for some cloud storage drivers.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestContinueStreamAppendSmall() {
|
|
|
|
suite.testContinueStreamAppend(int64(32))
|
2015-01-26 21:38:51 +00:00
|
|
|
}
|
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) testContinueStreamAppend(chunkSize int64) {
|
2014-12-09 02:22:08 +00:00
|
|
|
filename := randomPath(32)
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(firstPart(filename))
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2014-12-09 02:22:08 +00:00
|
|
|
contentsChunk1 := randomContents(chunkSize)
|
|
|
|
contentsChunk2 := randomContents(chunkSize)
|
|
|
|
contentsChunk3 := randomContents(chunkSize)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2014-12-04 00:37:46 +00:00
|
|
|
fullContents := append(append(contentsChunk1, contentsChunk2...), contentsChunk3...)
|
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
writer, err := suite.StorageDriver.Writer(suite.ctx, filename, false)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-02-08 22:29:21 +00:00
|
|
|
nn, err := io.Copy(writer, bytes.NewReader(contentsChunk1))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(int64(len(contentsChunk1)), nn)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
err = writer.Close()
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2016-02-12 17:49:37 +00:00
|
|
|
curSize := writer.Size()
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Equal(int64(len(contentsChunk1)), curSize)
|
2016-02-12 17:49:37 +00:00
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
writer, err = suite.StorageDriver.Writer(suite.ctx, filename, true)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(curSize, writer.Size())
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
nn, err = io.Copy(writer, bytes.NewReader(contentsChunk2))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(int64(len(contentsChunk2)), nn)
|
2015-01-26 21:38:51 +00:00
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
err = writer.Close()
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2016-02-12 17:49:37 +00:00
|
|
|
curSize = writer.Size()
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Equal(2*chunkSize, curSize)
|
2016-02-12 17:49:37 +00:00
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
writer, err = suite.StorageDriver.Writer(suite.ctx, filename, true)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(curSize, writer.Size())
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
nn, err = io.Copy(writer, bytes.NewReader(fullContents[curSize:]))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(int64(len(fullContents[curSize:])), nn)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2023-10-18 09:34:10 +00:00
|
|
|
err = writer.Commit(context.Background())
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-02-08 22:29:21 +00:00
|
|
|
err = writer.Close()
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
received, err := suite.StorageDriver.GetContent(suite.ctx, filename)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(fullContents, received)
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// TestReadNonexistentStream tests that reading a stream for a nonexistent path
|
2014-11-20 22:50:51 +00:00
|
|
|
// fails.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestReadNonexistentStream() {
|
2014-12-09 02:22:08 +00:00
|
|
|
filename := randomPath(32)
|
2014-12-10 18:51:07 +00:00
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
_, err := suite.StorageDriver.Reader(suite.ctx, filename, 0)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err)
|
|
|
|
suite.Require().IsType(err, storagedriver.PathNotFoundError{})
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
2014-12-10 18:51:07 +00:00
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
_, err = suite.StorageDriver.Reader(suite.ctx, filename, 64)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err)
|
|
|
|
suite.Require().IsType(err, storagedriver.PathNotFoundError{})
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2023-09-22 08:57:31 +00:00
|
|
|
// TestWriteZeroByteStreamThenAppend tests if zero byte file handling works for append to a Stream
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestWriteZeroByteStreamThenAppend() {
|
2023-09-22 08:57:31 +00:00
|
|
|
filename := randomPath(32)
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(firstPart(filename))
|
2023-09-22 08:57:31 +00:00
|
|
|
chunkSize := int64(32)
|
|
|
|
contentsChunk1 := randomContents(chunkSize)
|
|
|
|
|
|
|
|
// Open a Writer
|
|
|
|
writer, err := suite.StorageDriver.Writer(suite.ctx, filename, false)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2023-09-22 08:57:31 +00:00
|
|
|
|
|
|
|
// Close the Writer
|
2023-10-18 09:34:10 +00:00
|
|
|
err = writer.Commit(context.Background())
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2023-09-22 08:57:31 +00:00
|
|
|
err = writer.Close()
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2023-09-22 08:57:31 +00:00
|
|
|
curSize := writer.Size()
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Equal(int64(0), curSize)
|
2023-09-22 08:57:31 +00:00
|
|
|
|
|
|
|
// Open a Reader
|
|
|
|
reader, err := suite.StorageDriver.Reader(suite.ctx, filename, 0)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2023-09-22 08:57:31 +00:00
|
|
|
defer reader.Close()
|
|
|
|
|
|
|
|
// Check the file is empty
|
|
|
|
buf := make([]byte, chunkSize)
|
|
|
|
n, err := reader.Read(buf)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().ErrorIs(err, io.EOF)
|
|
|
|
suite.Require().Equal(0, n)
|
2023-09-22 08:57:31 +00:00
|
|
|
|
|
|
|
// Open a Writer for Append
|
|
|
|
awriter, err := suite.StorageDriver.Writer(suite.ctx, filename, true)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2023-09-22 08:57:31 +00:00
|
|
|
|
|
|
|
// Write small bytes to AppendWriter
|
|
|
|
nn, err := io.Copy(awriter, bytes.NewReader(contentsChunk1))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(int64(len(contentsChunk1)), nn)
|
2023-09-22 08:57:31 +00:00
|
|
|
|
|
|
|
// Close the AppendWriter
|
2023-10-18 09:34:10 +00:00
|
|
|
err = awriter.Commit(context.Background())
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2023-09-22 08:57:31 +00:00
|
|
|
err = awriter.Close()
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2023-09-22 08:57:31 +00:00
|
|
|
appendSize := awriter.Size()
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Equal(int64(len(contentsChunk1)), appendSize)
|
2023-09-22 08:57:31 +00:00
|
|
|
|
|
|
|
// Open a Reader
|
|
|
|
reader, err = suite.StorageDriver.Reader(suite.ctx, filename, 0)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2023-09-22 08:57:31 +00:00
|
|
|
defer reader.Close()
|
|
|
|
|
|
|
|
// Read small bytes from Reader
|
|
|
|
readContents, err := io.ReadAll(reader)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(contentsChunk1, readContents)
|
2023-09-22 08:57:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TestWriteZeroByteContentThenAppend tests if zero byte file handling works for append to PutContent
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestWriteZeroByteContentThenAppend() {
|
2023-09-22 08:57:31 +00:00
|
|
|
filename := randomPath(32)
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(firstPart(filename))
|
2023-09-22 08:57:31 +00:00
|
|
|
chunkSize := int64(32)
|
|
|
|
contentsChunk1 := randomContents(chunkSize)
|
|
|
|
|
|
|
|
err := suite.StorageDriver.PutContent(suite.ctx, filename, nil)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2023-09-22 08:57:31 +00:00
|
|
|
|
|
|
|
// Open a Reader
|
|
|
|
reader, err := suite.StorageDriver.Reader(suite.ctx, filename, 0)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2023-09-22 08:57:31 +00:00
|
|
|
defer reader.Close()
|
|
|
|
|
|
|
|
// Check the file is empty
|
|
|
|
buf := make([]byte, chunkSize)
|
|
|
|
n, err := reader.Read(buf)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().ErrorIs(err, io.EOF)
|
|
|
|
suite.Require().Equal(0, n)
|
2023-09-22 08:57:31 +00:00
|
|
|
|
|
|
|
// Open a Writer for Append
|
|
|
|
awriter, err := suite.StorageDriver.Writer(suite.ctx, filename, true)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2023-09-22 08:57:31 +00:00
|
|
|
|
|
|
|
// Write small bytes to AppendWriter
|
|
|
|
nn, err := io.Copy(awriter, bytes.NewReader(contentsChunk1))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(int64(len(contentsChunk1)), nn)
|
2023-09-22 08:57:31 +00:00
|
|
|
|
|
|
|
// Close the AppendWriter
|
2023-10-18 09:34:10 +00:00
|
|
|
err = awriter.Commit(context.Background())
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2023-09-22 08:57:31 +00:00
|
|
|
err = awriter.Close()
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2023-09-22 08:57:31 +00:00
|
|
|
appendSize := awriter.Size()
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Equal(int64(len(contentsChunk1)), appendSize)
|
2023-09-22 08:57:31 +00:00
|
|
|
|
|
|
|
// Open a Reader
|
|
|
|
reader, err = suite.StorageDriver.Reader(suite.ctx, filename, 0)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2023-09-22 08:57:31 +00:00
|
|
|
defer reader.Close()
|
|
|
|
|
|
|
|
// Read small bytes from Reader
|
|
|
|
readContents, err := io.ReadAll(reader)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(contentsChunk1, readContents)
|
2023-09-22 08:57:31 +00:00
|
|
|
}
|
|
|
|
|
2014-11-20 22:50:51 +00:00
|
|
|
// TestList checks the returned list of keys after populating a directory tree.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestList() {
|
2014-12-09 02:22:08 +00:00
|
|
|
rootDirectory := "/" + randomFilename(int64(8+rand.Intn(8)))
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(rootDirectory)
|
2014-10-24 23:36:17 +00:00
|
|
|
|
2015-11-13 21:47:07 +00:00
|
|
|
doesnotexist := path.Join(rootDirectory, "nonexistent")
|
|
|
|
_, err := suite.StorageDriver.List(suite.ctx, doesnotexist)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Equal(err, storagedriver.PathNotFoundError{
|
2015-11-13 21:47:07 +00:00
|
|
|
Path: doesnotexist,
|
|
|
|
DriverName: suite.StorageDriver.Name(),
|
|
|
|
})
|
|
|
|
|
2014-12-09 02:22:08 +00:00
|
|
|
parentDirectory := rootDirectory + "/" + randomFilename(int64(8+rand.Intn(8)))
|
2014-10-21 22:02:20 +00:00
|
|
|
childFiles := make([]string, 50)
|
|
|
|
for i := 0; i < len(childFiles); i++ {
|
2014-12-09 02:22:08 +00:00
|
|
|
childFile := parentDirectory + "/" + randomFilename(int64(8+rand.Intn(8)))
|
2014-10-21 22:02:20 +00:00
|
|
|
childFiles[i] = childFile
|
2015-04-27 22:58:58 +00:00
|
|
|
err := suite.StorageDriver.PutContent(suite.ctx, childFile, randomContents(32))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
sort.Strings(childFiles)
|
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
keys, err := suite.StorageDriver.List(suite.ctx, "/")
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal([]string{rootDirectory}, keys)
|
2014-11-20 22:11:49 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
keys, err = suite.StorageDriver.List(suite.ctx, rootDirectory)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal([]string{parentDirectory}, keys)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
keys, err = suite.StorageDriver.List(suite.ctx, parentDirectory)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
|
|
|
sort.Strings(keys)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Equal(childFiles, keys)
|
2014-12-05 19:46:41 +00:00
|
|
|
|
|
|
|
// A few checks to add here (check out #819 for more discussion on this):
|
|
|
|
// 1. Ensure that all paths are absolute.
|
|
|
|
// 2. Ensure that listings only include direct children.
|
|
|
|
// 3. Ensure that we only respond to directory listings that end with a slash (maybe?).
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// TestMove checks that a moved object no longer exists at the source path and
|
2014-11-20 22:50:51 +00:00
|
|
|
// does exist at the destination.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestMove() {
|
2014-12-09 02:22:08 +00:00
|
|
|
contents := randomContents(32)
|
|
|
|
sourcePath := randomPath(32)
|
|
|
|
destPath := randomPath(32)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(firstPart(sourcePath))
|
|
|
|
defer suite.deletePath(firstPart(destPath))
|
2014-10-24 23:36:17 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err := suite.StorageDriver.PutContent(suite.ctx, sourcePath, contents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err = suite.StorageDriver.Move(suite.ctx, sourcePath, destPath)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
received, err := suite.StorageDriver.GetContent(suite.ctx, destPath)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(contents, received)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
_, err = suite.StorageDriver.GetContent(suite.ctx, sourcePath)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err)
|
|
|
|
suite.Require().IsType(err, storagedriver.PathNotFoundError{})
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-12-10 18:53:51 +00:00
|
|
|
// TestMoveOverwrite checks that a moved object no longer exists at the source
|
|
|
|
// path and overwrites the contents at the destination.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestMoveOverwrite() {
|
2014-12-10 18:53:51 +00:00
|
|
|
sourcePath := randomPath(32)
|
|
|
|
destPath := randomPath(32)
|
|
|
|
sourceContents := randomContents(32)
|
|
|
|
destContents := randomContents(64)
|
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(firstPart(sourcePath))
|
|
|
|
defer suite.deletePath(firstPart(destPath))
|
2014-12-10 18:53:51 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err := suite.StorageDriver.PutContent(suite.ctx, sourcePath, sourceContents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-12-10 18:53:51 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err = suite.StorageDriver.PutContent(suite.ctx, destPath, destContents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-12-10 18:53:51 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err = suite.StorageDriver.Move(suite.ctx, sourcePath, destPath)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-12-10 18:53:51 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
received, err := suite.StorageDriver.GetContent(suite.ctx, destPath)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(sourceContents, received)
|
2014-12-10 18:53:51 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
_, err = suite.StorageDriver.GetContent(suite.ctx, sourcePath)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err)
|
|
|
|
suite.Require().IsType(err, storagedriver.PathNotFoundError{})
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
2014-12-10 18:53:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TestMoveNonexistent checks that moving a nonexistent key fails and does not
|
|
|
|
// delete the data at the destination path.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestMoveNonexistent() {
|
2014-12-10 18:53:51 +00:00
|
|
|
contents := randomContents(32)
|
2014-12-09 02:22:08 +00:00
|
|
|
sourcePath := randomPath(32)
|
|
|
|
destPath := randomPath(32)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(firstPart(destPath))
|
2014-12-10 18:53:51 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err := suite.StorageDriver.PutContent(suite.ctx, destPath, contents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-12-10 18:53:51 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err = suite.StorageDriver.Move(suite.ctx, sourcePath, destPath)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err)
|
|
|
|
suite.Require().IsType(err, storagedriver.PathNotFoundError{})
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
2014-12-10 18:53:51 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
received, err := suite.StorageDriver.GetContent(suite.ctx, destPath)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(contents, received)
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2015-04-02 01:45:13 +00:00
|
|
|
// TestMoveInvalid provides various checks for invalid moves.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestMoveInvalid() {
|
2015-04-02 01:45:13 +00:00
|
|
|
contents := randomContents(32)
|
|
|
|
|
|
|
|
// Create a regular file.
|
2015-04-27 22:58:58 +00:00
|
|
|
err := suite.StorageDriver.PutContent(suite.ctx, "/notadir", contents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
defer suite.deletePath("/notadir")
|
2015-04-02 01:45:13 +00:00
|
|
|
|
|
|
|
// Now try to move a non-existent file under it.
|
2015-04-27 22:58:58 +00:00
|
|
|
err = suite.StorageDriver.Move(suite.ctx, "/notadir/foo", "/notadir/bar")
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err) // non-nil error
|
2015-04-02 01:45:13 +00:00
|
|
|
}
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// TestDelete checks that the delete operation removes data from the storage
|
|
|
|
// driver
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestDelete() {
|
2014-12-09 02:22:08 +00:00
|
|
|
filename := randomPath(32)
|
|
|
|
contents := randomContents(32)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(firstPart(filename))
|
2014-10-24 23:36:17 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err := suite.StorageDriver.PutContent(suite.ctx, filename, contents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err = suite.StorageDriver.Delete(suite.ctx, filename)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
_, err = suite.StorageDriver.GetContent(suite.ctx, filename)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err)
|
|
|
|
suite.Require().IsType(err, storagedriver.PathNotFoundError{})
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2023-10-24 19:49:47 +00:00
|
|
|
// TestRedirectURL checks that the RedirectURL method functions properly,
|
|
|
|
// but only if it is implemented
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestRedirectURL() {
|
2015-01-07 16:31:38 +00:00
|
|
|
filename := randomPath(32)
|
|
|
|
contents := randomContents(32)
|
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(firstPart(filename))
|
2015-01-07 16:31:38 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err := suite.StorageDriver.PutContent(suite.ctx, filename, contents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2015-01-07 16:31:38 +00:00
|
|
|
|
2023-10-24 19:49:47 +00:00
|
|
|
url, err := suite.StorageDriver.RedirectURL(httptest.NewRequest(http.MethodGet, filename, nil), filename)
|
|
|
|
if url == "" && err == nil {
|
2015-01-07 16:31:38 +00:00
|
|
|
return
|
|
|
|
}
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2015-01-07 16:31:38 +00:00
|
|
|
|
|
|
|
response, err := http.Get(url)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2015-01-07 16:31:38 +00:00
|
|
|
defer response.Body.Close()
|
|
|
|
|
2022-11-02 21:55:22 +00:00
|
|
|
read, err := io.ReadAll(response.Body)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(contents, read)
|
2015-01-14 19:31:11 +00:00
|
|
|
|
2023-10-24 19:49:47 +00:00
|
|
|
url, err = suite.StorageDriver.RedirectURL(httptest.NewRequest(http.MethodHead, filename, nil), filename)
|
|
|
|
if url == "" && err == nil {
|
2015-01-14 19:31:11 +00:00
|
|
|
return
|
|
|
|
}
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2015-01-14 19:31:11 +00:00
|
|
|
|
2023-08-19 08:16:02 +00:00
|
|
|
response, err = http.Head(url)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2023-08-19 08:16:02 +00:00
|
|
|
defer response.Body.Close()
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Equal(200, response.StatusCode)
|
|
|
|
suite.Require().Equal(int64(32), response.ContentLength)
|
2015-01-07 16:31:38 +00:00
|
|
|
}
|
|
|
|
|
2014-11-20 22:50:51 +00:00
|
|
|
// TestDeleteNonexistent checks that removing a nonexistent key fails.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestDeleteNonexistent() {
|
2014-12-09 02:22:08 +00:00
|
|
|
filename := randomPath(32)
|
2015-04-27 22:58:58 +00:00
|
|
|
err := suite.StorageDriver.Delete(suite.ctx, filename)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err)
|
|
|
|
suite.Require().IsType(err, storagedriver.PathNotFoundError{})
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-11-20 22:50:51 +00:00
|
|
|
// TestDeleteFolder checks that deleting a folder removes all child elements.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestDeleteFolder() {
|
2014-12-09 02:22:08 +00:00
|
|
|
dirname := randomPath(32)
|
|
|
|
filename1 := randomPath(32)
|
|
|
|
filename2 := randomPath(32)
|
|
|
|
filename3 := randomPath(32)
|
|
|
|
contents := randomContents(32)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(firstPart(dirname))
|
2014-10-24 23:36:17 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err := suite.StorageDriver.PutContent(suite.ctx, path.Join(dirname, filename1), contents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err = suite.StorageDriver.PutContent(suite.ctx, path.Join(dirname, filename2), contents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err = suite.StorageDriver.PutContent(suite.ctx, path.Join(dirname, filename3), contents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-12-09 02:22:08 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err = suite.StorageDriver.Delete(suite.ctx, path.Join(dirname, filename1))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-12-09 02:22:08 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
_, err = suite.StorageDriver.GetContent(suite.ctx, path.Join(dirname, filename1))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err)
|
|
|
|
suite.Require().IsType(err, storagedriver.PathNotFoundError{})
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
2014-12-09 02:22:08 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
_, err = suite.StorageDriver.GetContent(suite.ctx, path.Join(dirname, filename2))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-12-09 02:22:08 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
_, err = suite.StorageDriver.GetContent(suite.ctx, path.Join(dirname, filename3))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-12-09 02:22:08 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err = suite.StorageDriver.Delete(suite.ctx, dirname)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
_, err = suite.StorageDriver.GetContent(suite.ctx, path.Join(dirname, filename1))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err)
|
|
|
|
suite.Require().IsType(err, storagedriver.PathNotFoundError{})
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
_, err = suite.StorageDriver.GetContent(suite.ctx, path.Join(dirname, filename2))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err)
|
|
|
|
suite.Require().IsType(err, storagedriver.PathNotFoundError{})
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
2014-12-09 02:22:08 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
_, err = suite.StorageDriver.GetContent(suite.ctx, path.Join(dirname, filename3))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err)
|
|
|
|
suite.Require().IsType(err, storagedriver.PathNotFoundError{})
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2016-10-05 23:39:38 +00:00
|
|
|
// TestDeleteOnlyDeletesSubpaths checks that deleting path A does not
|
|
|
|
// delete path B when A is a prefix of B but B is not a subpath of A (so that
|
|
|
|
// deleting "/a" does not delete "/ab"). This matters for services like S3 that
|
|
|
|
// do not implement directories.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestDeleteOnlyDeletesSubpaths() {
|
2016-10-05 23:39:38 +00:00
|
|
|
dirname := randomPath(32)
|
|
|
|
filename := randomPath(32)
|
|
|
|
contents := randomContents(32)
|
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(firstPart(dirname))
|
2016-10-05 23:39:38 +00:00
|
|
|
|
|
|
|
err := suite.StorageDriver.PutContent(suite.ctx, path.Join(dirname, filename), contents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-10-05 23:39:38 +00:00
|
|
|
|
|
|
|
err = suite.StorageDriver.PutContent(suite.ctx, path.Join(dirname, filename+"suffix"), contents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-10-05 23:39:38 +00:00
|
|
|
|
|
|
|
err = suite.StorageDriver.PutContent(suite.ctx, path.Join(dirname, dirname, filename), contents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-10-05 23:39:38 +00:00
|
|
|
|
|
|
|
err = suite.StorageDriver.PutContent(suite.ctx, path.Join(dirname, dirname+"suffix", filename), contents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-10-05 23:39:38 +00:00
|
|
|
|
|
|
|
err = suite.StorageDriver.Delete(suite.ctx, path.Join(dirname, filename))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-10-05 23:39:38 +00:00
|
|
|
|
|
|
|
_, err = suite.StorageDriver.GetContent(suite.ctx, path.Join(dirname, filename))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err)
|
|
|
|
suite.Require().IsType(err, storagedriver.PathNotFoundError{})
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
2016-10-05 23:39:38 +00:00
|
|
|
|
|
|
|
_, err = suite.StorageDriver.GetContent(suite.ctx, path.Join(dirname, filename+"suffix"))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-10-05 23:39:38 +00:00
|
|
|
|
|
|
|
err = suite.StorageDriver.Delete(suite.ctx, path.Join(dirname, dirname))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-10-05 23:39:38 +00:00
|
|
|
|
|
|
|
_, err = suite.StorageDriver.GetContent(suite.ctx, path.Join(dirname, dirname, filename))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err)
|
|
|
|
suite.Require().IsType(err, storagedriver.PathNotFoundError{})
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
2016-10-05 23:39:38 +00:00
|
|
|
|
|
|
|
_, err = suite.StorageDriver.GetContent(suite.ctx, path.Join(dirname, dirname+"suffix", filename))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-10-05 23:39:38 +00:00
|
|
|
}
|
|
|
|
|
2014-12-06 03:20:42 +00:00
|
|
|
// TestStatCall runs verifies the implementation of the storagedriver's Stat call.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestStatCall() {
|
2014-12-09 02:22:08 +00:00
|
|
|
content := randomContents(4096)
|
|
|
|
dirPath := randomPath(32)
|
|
|
|
fileName := randomFilename(32)
|
2014-12-04 00:37:46 +00:00
|
|
|
filePath := path.Join(dirPath, fileName)
|
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(firstPart(dirPath))
|
2014-12-09 02:22:08 +00:00
|
|
|
|
2014-12-04 00:37:46 +00:00
|
|
|
// Call on non-existent file/dir, check error.
|
2015-04-27 22:58:58 +00:00
|
|
|
fi, err := suite.StorageDriver.Stat(suite.ctx, dirPath)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err)
|
|
|
|
suite.Require().IsType(err, storagedriver.PathNotFoundError{})
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
|
|
|
suite.Require().Nil(fi)
|
2014-12-10 18:55:33 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
fi, err = suite.StorageDriver.Stat(suite.ctx, filePath)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Error(err)
|
|
|
|
suite.Require().IsType(err, storagedriver.PathNotFoundError{})
|
|
|
|
suite.Require().Contains(err.Error(), suite.Name())
|
|
|
|
suite.Require().Nil(fi)
|
2014-12-04 00:37:46 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err = suite.StorageDriver.PutContent(suite.ctx, filePath, content)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-12-04 00:37:46 +00:00
|
|
|
|
|
|
|
// Call on regular file, check results
|
2015-04-27 22:58:58 +00:00
|
|
|
fi, err = suite.StorageDriver.Stat(suite.ctx, filePath)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().NotNil(fi)
|
|
|
|
suite.Require().Equal(filePath, fi.Path())
|
|
|
|
suite.Require().Equal(int64(len(content)), fi.Size())
|
|
|
|
suite.Require().False(fi.IsDir())
|
2015-01-18 07:19:04 +00:00
|
|
|
createdTime := fi.ModTime()
|
2014-12-04 00:37:46 +00:00
|
|
|
|
2015-01-18 07:19:04 +00:00
|
|
|
// Sleep and modify the file
|
|
|
|
time.Sleep(time.Second * 10)
|
|
|
|
content = randomContents(4096)
|
2015-04-27 22:58:58 +00:00
|
|
|
err = suite.StorageDriver.PutContent(suite.ctx, filePath, content)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2015-04-27 22:58:58 +00:00
|
|
|
fi, err = suite.StorageDriver.Stat(suite.ctx, filePath)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().NotNil(fi)
|
2015-01-18 07:19:04 +00:00
|
|
|
time.Sleep(time.Second * 5) // allow changes to propagate (eventual consistency)
|
|
|
|
|
|
|
|
// Check if the modification time is after the creation time.
|
|
|
|
// In case of cloud storage services, storage frontend nodes might have
|
|
|
|
// time drift between them, however that should be solved with sleeping
|
|
|
|
// before update.
|
|
|
|
modTime := fi.ModTime()
|
|
|
|
if !modTime.After(createdTime) {
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.T().Errorf("modtime (%s) is before the creation time (%s)", modTime, createdTime)
|
2014-12-04 00:37:46 +00:00
|
|
|
}
|
|
|
|
|
2015-01-18 07:19:04 +00:00
|
|
|
// Call on directory (do not check ModTime as dirs don't need to support it)
|
2015-04-27 22:58:58 +00:00
|
|
|
fi, err = suite.StorageDriver.Stat(suite.ctx, dirPath)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().NotNil(fi)
|
|
|
|
suite.Require().Equal(dirPath, fi.Path())
|
|
|
|
suite.Require().Equal(int64(0), fi.Size())
|
|
|
|
suite.Require().True(fi.IsDir())
|
2023-05-30 07:20:55 +00:00
|
|
|
|
|
|
|
// The storage healthcheck performs this exact call to Stat.
|
|
|
|
// PathNotFoundErrors are not considered health check failures.
|
|
|
|
_, err = suite.StorageDriver.Stat(suite.ctx, "/")
|
|
|
|
// Some drivers will return a not found here, while others will not
|
|
|
|
// return an error at all. If we get an error, ensure it's a not found.
|
|
|
|
if err != nil {
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().IsType(err, storagedriver.PathNotFoundError{})
|
2023-05-30 07:20:55 +00:00
|
|
|
}
|
2014-12-04 00:37:46 +00:00
|
|
|
}
|
|
|
|
|
2015-01-18 07:42:10 +00:00
|
|
|
// TestPutContentMultipleTimes checks that if storage driver can overwrite the content
|
|
|
|
// in the subsequent puts. Validates that PutContent does not have to work
|
2016-02-08 22:29:21 +00:00
|
|
|
// with an offset like Writer does and overwrites the file entirely
|
2015-01-18 07:42:10 +00:00
|
|
|
// rather than writing the data to the [0,len(data)) of the file.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestPutContentMultipleTimes() {
|
2015-01-18 07:42:10 +00:00
|
|
|
filename := randomPath(32)
|
|
|
|
contents := randomContents(4096)
|
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(firstPart(filename))
|
2015-04-27 22:58:58 +00:00
|
|
|
err := suite.StorageDriver.PutContent(suite.ctx, filename, contents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2015-01-18 07:42:10 +00:00
|
|
|
|
|
|
|
contents = randomContents(2048) // upload a different, smaller file
|
2015-04-27 22:58:58 +00:00
|
|
|
err = suite.StorageDriver.PutContent(suite.ctx, filename, contents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2015-01-18 07:42:10 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
readContents, err := suite.StorageDriver.GetContent(suite.ctx, filename)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(contents, readContents)
|
2015-01-18 07:42:10 +00:00
|
|
|
}
|
|
|
|
|
2014-12-10 18:57:47 +00:00
|
|
|
// TestConcurrentStreamReads checks that multiple clients can safely read from
|
|
|
|
// the same file simultaneously with various offsets.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestConcurrentStreamReads() {
|
2014-12-10 18:57:47 +00:00
|
|
|
var filesize int64 = 128 * 1024 * 1024
|
|
|
|
|
|
|
|
if testing.Short() {
|
|
|
|
filesize = 10 * 1024 * 1024
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.T().Log("Reducing file size to 10MB for short mode")
|
2014-12-10 18:57:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
filename := randomPath(32)
|
|
|
|
contents := randomContents(filesize)
|
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(firstPart(filename))
|
2014-12-10 18:57:47 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err := suite.StorageDriver.PutContent(suite.ctx, filename, contents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-12-10 18:57:47 +00:00
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
|
|
|
readContents := func() {
|
|
|
|
defer wg.Done()
|
|
|
|
offset := rand.Int63n(int64(len(contents)))
|
2016-02-08 22:29:21 +00:00
|
|
|
reader, err := suite.StorageDriver.Reader(suite.ctx, filename, offset)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-12-10 18:57:47 +00:00
|
|
|
|
2022-11-02 21:55:22 +00:00
|
|
|
readContents, err := io.ReadAll(reader)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(contents[offset:], readContents)
|
2014-12-10 18:57:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
wg.Add(10)
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
go readContents()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
}
|
|
|
|
|
2014-11-20 22:50:51 +00:00
|
|
|
// TestConcurrentFileStreams checks that multiple *os.File objects can be passed
|
2016-02-08 22:29:21 +00:00
|
|
|
// in to Writer concurrently without hanging.
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) TestConcurrentFileStreams() {
|
2014-12-11 00:20:14 +00:00
|
|
|
numStreams := 32
|
|
|
|
|
|
|
|
if testing.Short() {
|
|
|
|
numStreams = 8
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.T().Log("Reducing number of streams to 8 for short mode")
|
2014-12-11 00:20:14 +00:00
|
|
|
}
|
|
|
|
|
2014-12-03 04:43:31 +00:00
|
|
|
var wg sync.WaitGroup
|
2014-11-20 22:50:51 +00:00
|
|
|
|
2014-12-03 03:01:00 +00:00
|
|
|
testStream := func(size int64) {
|
2014-12-03 04:43:31 +00:00
|
|
|
defer wg.Done()
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.testFileStreams(size)
|
2014-11-20 22:50:51 +00:00
|
|
|
}
|
|
|
|
|
2014-12-11 00:20:14 +00:00
|
|
|
wg.Add(numStreams)
|
|
|
|
for i := numStreams; i > 0; i-- {
|
|
|
|
go testStream(int64(numStreams) * 1024 * 1024)
|
|
|
|
}
|
2014-11-20 22:50:51 +00:00
|
|
|
|
2014-12-03 04:43:31 +00:00
|
|
|
wg.Wait()
|
2014-11-20 22:50:51 +00:00
|
|
|
}
|
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
// TODO (brianbland): evaluate the relevancy of this test
|
2014-12-21 15:46:52 +00:00
|
|
|
// TestEventualConsistency checks that if stat says that a file is a certain size, then
|
|
|
|
// you can freely read from the file (this is the only guarantee that the driver needs to provide)
|
2023-12-07 18:00:27 +00:00
|
|
|
// func (suite *DriverSuite) TestEventualConsistency() {
|
2016-02-08 22:29:21 +00:00
|
|
|
// if testing.Short() {
|
|
|
|
// c.Skip("Skipping test in short mode")
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// filename := randomPath(32)
|
|
|
|
// defer suite.deletePath(c, firstPart(filename))
|
|
|
|
//
|
|
|
|
// var offset int64
|
|
|
|
// var misswrites int
|
|
|
|
// var chunkSize int64 = 32
|
|
|
|
//
|
|
|
|
// for i := 0; i < 1024; i++ {
|
|
|
|
// contents := randomContents(chunkSize)
|
|
|
|
// read, err := suite.StorageDriver.Writer(suite.ctx, filename, offset, bytes.NewReader(contents))
|
2023-12-07 18:00:27 +00:00
|
|
|
// suite.Require().NoError( err)
|
2016-02-08 22:29:21 +00:00
|
|
|
//
|
|
|
|
// fi, err := suite.StorageDriver.Stat(suite.ctx, filename)
|
2023-12-07 18:00:27 +00:00
|
|
|
// suite.Require().NoError( err)
|
2016-02-08 22:29:21 +00:00
|
|
|
//
|
|
|
|
// // We are most concerned with being able to read data as soon as Stat declares
|
|
|
|
// // it is uploaded. This is the strongest guarantee that some drivers (that guarantee
|
|
|
|
// // at best eventual consistency) absolutely need to provide.
|
|
|
|
// if fi.Size() == offset+chunkSize {
|
|
|
|
// reader, err := suite.StorageDriver.Reader(suite.ctx, filename, offset)
|
2023-12-07 18:00:27 +00:00
|
|
|
// suite.Require().NoError( err)
|
2016-02-08 22:29:21 +00:00
|
|
|
//
|
2022-11-02 21:55:22 +00:00
|
|
|
// readContents, err := io.ReadAll(reader)
|
2023-12-07 18:00:27 +00:00
|
|
|
// suite.Require().NoError( err)
|
2016-02-08 22:29:21 +00:00
|
|
|
//
|
|
|
|
// c.Assert(readContents, check.DeepEquals, contents)
|
|
|
|
//
|
|
|
|
// reader.Close()
|
|
|
|
// offset += read
|
|
|
|
// } else {
|
|
|
|
// misswrites++
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// if misswrites > 0 {
|
|
|
|
// c.Log("There were " + string(misswrites) + " occurrences of a write not being instantly available.")
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// c.Assert(misswrites, check.Not(check.Equals), 1024)
|
|
|
|
// }
|
2014-12-21 15:46:52 +00:00
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) testFileStreams(size int64) {
|
2022-11-02 21:55:22 +00:00
|
|
|
tf, err := os.CreateTemp("", "tf")
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-11-20 22:50:51 +00:00
|
|
|
defer os.Remove(tf.Name())
|
2014-12-11 00:20:14 +00:00
|
|
|
defer tf.Close()
|
2014-11-20 22:50:51 +00:00
|
|
|
|
2014-12-10 18:57:47 +00:00
|
|
|
filename := randomPath(32)
|
2023-12-07 18:00:27 +00:00
|
|
|
defer suite.deletePath(firstPart(filename))
|
2014-11-20 22:50:51 +00:00
|
|
|
|
2014-12-09 02:22:08 +00:00
|
|
|
contents := randomContents(size)
|
2014-11-20 22:50:51 +00:00
|
|
|
|
|
|
|
_, err = tf.Write(contents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-11-20 22:50:51 +00:00
|
|
|
|
2023-11-18 06:50:40 +00:00
|
|
|
err = tf.Sync()
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2023-11-18 06:50:40 +00:00
|
|
|
_, err = tf.Seek(0, io.SeekStart)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-11-20 22:50:51 +00:00
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
writer, err := suite.StorageDriver.Writer(suite.ctx, filename, false)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-02-08 22:29:21 +00:00
|
|
|
nn, err := io.Copy(writer, tf)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(size, nn)
|
2014-11-20 22:50:51 +00:00
|
|
|
|
2023-10-18 09:34:10 +00:00
|
|
|
err = writer.Commit(context.Background())
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-02-08 22:29:21 +00:00
|
|
|
err = writer.Close()
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-02-08 22:29:21 +00:00
|
|
|
|
|
|
|
reader, err := suite.StorageDriver.Reader(suite.ctx, filename, 0)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-11-20 22:50:51 +00:00
|
|
|
defer reader.Close()
|
|
|
|
|
2022-11-02 21:55:22 +00:00
|
|
|
readContents, err := io.ReadAll(reader)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-11-20 22:50:51 +00:00
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Equal(contents, readContents)
|
2014-11-20 22:50:51 +00:00
|
|
|
}
|
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) writeReadCompare(filename string, contents []byte) {
|
|
|
|
defer suite.deletePath(firstPart(filename))
|
2014-10-24 23:36:17 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
err := suite.StorageDriver.PutContent(suite.ctx, filename, contents)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2015-04-27 22:58:58 +00:00
|
|
|
readContents, err := suite.StorageDriver.GetContent(suite.ctx, filename)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Equal(contents, readContents)
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
func (suite *DriverSuite) writeReadCompareStreams(filename string, contents []byte) {
|
|
|
|
defer suite.deletePath(firstPart(filename))
|
2014-10-24 23:36:17 +00:00
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
writer, err := suite.StorageDriver.Writer(suite.ctx, filename, false)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-02-08 22:29:21 +00:00
|
|
|
nn, err := io.Copy(writer, bytes.NewReader(contents))
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(int64(len(contents)), nn)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2023-10-18 09:34:10 +00:00
|
|
|
err = writer.Commit(context.Background())
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-02-08 22:29:21 +00:00
|
|
|
err = writer.Close()
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2016-02-08 22:29:21 +00:00
|
|
|
|
|
|
|
reader, err := suite.StorageDriver.Reader(suite.ctx, filename, 0)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
defer reader.Close()
|
|
|
|
|
2022-11-02 21:55:22 +00:00
|
|
|
readContents, err := io.ReadAll(reader)
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().NoError(err)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2023-12-07 18:00:27 +00:00
|
|
|
suite.Require().Equal(contents, readContents)
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2022-11-02 21:05:45 +00:00
|
|
|
var (
|
|
|
|
filenameChars = []byte("abcdefghijklmnopqrstuvwxyz0123456789")
|
|
|
|
separatorChars = []byte("._-")
|
|
|
|
)
|
2014-12-09 02:22:08 +00:00
|
|
|
|
|
|
|
func randomPath(length int64) string {
|
2014-12-11 22:11:47 +00:00
|
|
|
path := "/"
|
2014-12-09 02:22:08 +00:00
|
|
|
for int64(len(path)) < length {
|
2015-01-07 01:16:43 +00:00
|
|
|
chunkLength := rand.Int63n(length-int64(len(path))) + 1
|
2014-12-09 02:22:08 +00:00
|
|
|
chunk := randomFilename(chunkLength)
|
|
|
|
path += chunk
|
2015-01-07 01:16:43 +00:00
|
|
|
remaining := length - int64(len(path))
|
|
|
|
if remaining == 1 {
|
2014-12-09 02:22:08 +00:00
|
|
|
path += randomFilename(1)
|
2015-01-07 01:16:43 +00:00
|
|
|
} else if remaining > 1 {
|
2014-12-09 02:22:08 +00:00
|
|
|
path += "/"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return path
|
|
|
|
}
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2014-12-09 02:22:08 +00:00
|
|
|
func randomFilename(length int64) string {
|
2014-10-21 22:02:20 +00:00
|
|
|
b := make([]byte, length)
|
2014-12-11 22:11:47 +00:00
|
|
|
wasSeparator := true
|
2014-10-21 22:02:20 +00:00
|
|
|
for i := range b {
|
2014-12-11 22:11:47 +00:00
|
|
|
if !wasSeparator && i < len(b)-1 && rand.Intn(4) == 0 {
|
|
|
|
b[i] = separatorChars[rand.Intn(len(separatorChars))]
|
|
|
|
wasSeparator = true
|
|
|
|
} else {
|
|
|
|
b[i] = filenameChars[rand.Intn(len(filenameChars))]
|
|
|
|
wasSeparator = false
|
|
|
|
}
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
return string(b)
|
|
|
|
}
|
2014-12-09 02:22:08 +00:00
|
|
|
|
2015-12-08 02:54:22 +00:00
|
|
|
func randomContents(length int64) []byte {
|
|
|
|
return randomBytes[:length]
|
2014-12-09 02:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-01-18 01:08:04 +00:00
|
|
|
type randReader struct {
|
|
|
|
r int64
|
|
|
|
m sync.Mutex
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rr *randReader) Read(p []byte) (n int, err error) {
|
|
|
|
rr.m.Lock()
|
|
|
|
defer rr.m.Unlock()
|
2015-12-08 02:54:22 +00:00
|
|
|
|
2016-02-04 08:14:35 +00:00
|
|
|
toread := int64(len(p))
|
|
|
|
if toread > rr.r {
|
|
|
|
toread = rr.r
|
|
|
|
}
|
|
|
|
n = copy(p, randomContents(toread))
|
2015-12-08 02:54:22 +00:00
|
|
|
rr.r -= int64(n)
|
|
|
|
|
|
|
|
if rr.r <= 0 {
|
2015-01-18 01:08:04 +00:00
|
|
|
err = io.EOF
|
|
|
|
}
|
2015-12-08 02:54:22 +00:00
|
|
|
|
2015-01-18 01:08:04 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func newRandReader(n int64) *randReader {
|
|
|
|
return &randReader{r: n}
|
|
|
|
}
|
|
|
|
|
2014-12-09 02:22:08 +00:00
|
|
|
func firstPart(filePath string) string {
|
2014-12-16 20:01:27 +00:00
|
|
|
if filePath == "" {
|
|
|
|
return "/"
|
|
|
|
}
|
2014-12-09 02:22:08 +00:00
|
|
|
for {
|
|
|
|
if filePath[len(filePath)-1] == '/' {
|
|
|
|
filePath = filePath[:len(filePath)-1]
|
|
|
|
}
|
|
|
|
|
|
|
|
dir, file := path.Split(filePath)
|
|
|
|
if dir == "" && file == "" {
|
|
|
|
return "/"
|
|
|
|
}
|
2014-12-11 22:11:47 +00:00
|
|
|
if dir == "/" || dir == "" {
|
|
|
|
return "/" + file
|
2014-12-09 02:22:08 +00:00
|
|
|
}
|
|
|
|
if file == "" {
|
|
|
|
return dir
|
|
|
|
}
|
|
|
|
filePath = dir
|
|
|
|
}
|
|
|
|
}
|