From cce4956131f8083ffaa5f032fd8ea8747a269117 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Mon, 11 May 2015 18:11:47 +0200 Subject: [PATCH 01/32] Add Openstack Swift storage driver Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 519 ++++++++++++++++++++++++ docs/storage/driver/swift/swift_test.go | 141 +++++++ 2 files changed, 660 insertions(+) create mode 100644 docs/storage/driver/swift/swift.go create mode 100644 docs/storage/driver/swift/swift_test.go diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go new file mode 100644 index 000000000..4a812e9e0 --- /dev/null +++ b/docs/storage/driver/swift/swift.go @@ -0,0 +1,519 @@ +// Package swift provides a storagedriver.StorageDriver implementation to +// store blobs in Openstack Swift object storage. +// +// This package leverages the ncw/swift client library for interfacing with +// Swift. +// +// Because Swift is a key, value store the Stat call does not support last modification +// time for directories (directories are an abstraction for key, value stores) +package swift + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + gopath "path" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/lebauce/swift" + + "github.com/docker/distribution/context" + storagedriver "github.com/docker/distribution/registry/storage/driver" + "github.com/docker/distribution/registry/storage/driver/base" + "github.com/docker/distribution/registry/storage/driver/factory" +) + +const driverName = "swift" + +const defaultChunkSize = 5 * 1024 * 1024 + +//DriverParameters A struct that encapsulates all of the driver parameters after all values have been set +type DriverParameters struct { + Username string + Password string + AuthURL string + Tenant string + Region string + Container string + Prefix string + ChunkSize int64 +} + +type swiftInfo map[string]interface{} + +func init() { + factory.Register(driverName, &swiftDriverFactory{}) +} + +// swiftDriverFactory implements the factory.StorageDriverFactory interface +type swiftDriverFactory struct{} + +func (factory *swiftDriverFactory) Create(parameters map[string]interface{}) (storagedriver.StorageDriver, error) { + return FromParameters(parameters) +} + +type driver struct { + Conn swift.Connection + Container string + Prefix string + BulkDeleteSupport bool + ChunkSize int64 +} + +type baseEmbed struct { + base.Base +} + +// Driver is a storagedriver.StorageDriver implementation backed by Amazon Swift +// Objects are stored at absolute keys in the provided bucket. +type Driver struct { + baseEmbed +} + +// FromParameters constructs a new Driver with a given parameters map +// Required parameters: +// - username +// - password +// - authurl +// - container +func FromParameters(parameters map[string]interface{}) (*Driver, error) { + username, ok := parameters["username"] + if !ok || fmt.Sprint(username) == "" { + return nil, fmt.Errorf("No username parameter provided") + } + password, ok := parameters["password"] + if !ok || fmt.Sprint(password) == "" { + return nil, fmt.Errorf("No password parameter provided") + } + authURL, ok := parameters["authurl"] + if !ok || fmt.Sprint(authURL) == "" { + return nil, fmt.Errorf("No container parameter provided") + } + container, ok := parameters["container"] + if !ok || fmt.Sprint(container) == "" { + return nil, fmt.Errorf("No container parameter provided") + } + tenant, ok := parameters["tenant"] + if !ok { + tenant = "" + } + region, ok := parameters["region"] + if !ok { + region = "" + } + rootDirectory, ok := parameters["rootdirectory"] + if !ok { + rootDirectory = "" + } + chunkSize := int64(defaultChunkSize) + chunkSizeParam, ok := parameters["chunksize"] + if ok { + chunkSize, ok = chunkSizeParam.(int64) + if !ok { + return nil, fmt.Errorf("The chunksize parameter should be a number") + } + } + + params := DriverParameters{ + fmt.Sprint(username), + fmt.Sprint(password), + fmt.Sprint(authURL), + fmt.Sprint(tenant), + fmt.Sprint(region), + fmt.Sprint(container), + fmt.Sprint(rootDirectory), + chunkSize, + } + + return New(params) +} + +// New constructs a new Driver with the given Openstack Swift credentials and container name +func New(params DriverParameters) (*Driver, error) { + ct := swift.Connection{ + UserName: params.Username, + ApiKey: params.Password, + AuthUrl: params.AuthURL, + Region: params.Region, + UserAgent: "distribution", + Tenant: params.Tenant, + ConnectTimeout: 60 * time.Second, + Timeout: 15 * 60 * time.Second, + } + err := ct.Authenticate() + if err != nil { + return nil, fmt.Errorf("Swift authentication failed: %s", err) + } + + if err := ct.ContainerCreate(params.Container, nil); err != nil { + return nil, fmt.Errorf("Failed to create container %s (%s)", params.Container, err) + } + + if err := ct.ContainerCreate(params.Container + "_segments", nil); err != nil { + return nil, fmt.Errorf("Failed to create container %s (%s)", params.Container + "_segments", err) + } + + d := &driver{ + Conn: ct, + Container: params.Container, + Prefix: params.Prefix, + BulkDeleteSupport: detectBulkDelete(params.AuthURL), + ChunkSize: params.ChunkSize, + } + + return &Driver{ + baseEmbed: baseEmbed{ + Base: base.Base{ + StorageDriver: d, + }, + }, + }, nil +} + +// Implement the storagedriver.StorageDriver interface + +func (d *driver) Name() string { + return driverName +} + +// GetContent retrieves the content stored at "path" as a []byte. +func (d *driver) GetContent(ctx context.Context, path string) ([]byte, error) { + content, err := d.Conn.ObjectGetBytes(d.Container, d.swiftPath(path)) + if err != nil { + return nil, parseError(path, err) + } + return content, nil +} + +// PutContent stores the []byte content at a location designated by "path". +func (d *driver) PutContent(ctx context.Context, path string, contents []byte) error { + if dir, err := d.createParentFolder(path); err != nil { + return parseError(dir, err) + } + err := d.Conn.ObjectPutBytes(d.Container, d.swiftPath(path), + contents, d.getContentType()) + return parseError(path, err) +} + +// ReadStream retrieves an io.ReadCloser for the content stored at "path" with a +// given byte offset. +func (d *driver) ReadStream(ctx context.Context, path string, offset int64) (io.ReadCloser, error) { + headers := make(swift.Headers) + headers["Range"] = "bytes=" + strconv.FormatInt(offset, 10) + "-" + + file, _, err := d.Conn.ObjectOpen(d.Container, d.swiftPath(path), false, headers) + + if err != nil { + if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == 416 { + return ioutil.NopCloser(bytes.NewReader(nil)), nil + } + + return nil, parseError(path, err) + } + + return file, nil +} + +// WriteStream stores the contents of the provided io.Reader at a +// location designated by the given path. The driver will know it has +// received the full contents when the reader returns io.EOF. The number +// of successfully READ bytes will be returned, even if an error is +// returned. May be used to resume writing a stream by providing a nonzero +// offset. Offsets past the current size will write from the position +// beyond the end of the file. +func (d *driver) WriteStream(ctx context.Context, path string, offset int64, reader io.Reader) (int64, error) { + var ( + segments []swift.Object + paddingReader io.Reader + ) + + partNumber := int64(1) + bytesRead := int64(0) + currentLength := int64(0) + zeroBuf := make([]byte, d.ChunkSize) + segmentsContainer := d.Container + "_segments" + cursor := int64(0) + + getSegment := func() string { + return d.swiftPath(path) + "/" + fmt.Sprintf("%016d", partNumber) + } + + max := func(a int64, b int64) int64 { + if a > b { + return a + } + return b + } + + info, _, err := d.Conn.Object(d.Container, d.swiftPath(path)) + if err != nil { + if swiftErr, ok := err.(*swift.Error); ok { + if swiftErr.StatusCode == 404 { + // Create a object manifest + if dir, err := d.createParentFolder(path); err != nil { + return bytesRead, parseError(dir, err) + } + headers := make(swift.Headers) + headers["X-Object-Manifest"] = segmentsContainer + "/" + d.swiftPath(path) + manifest, err := d.Conn.ObjectCreate(d.Container, d.swiftPath(path), false, "", + d.getContentType(), headers) + manifest.Close() + if err != nil { + return bytesRead, parseError(path, err) + } + } else { + return bytesRead, parseError(path, err) + } + } else { + return bytesRead, parseError(path, err) + } + } else { + // The manifest already exists. Get all the segments + currentLength = info.Bytes + headers := make(swift.Headers) + headers["Content-Type"] = "application/json" + opts := &swift.ObjectsOpts{Prefix: d.swiftPath(path), Headers: headers} + segments, err = d.Conn.Objects(d.Container + "_segments", opts) + if err != nil { + return bytesRead, parseError(path, err) + } + } + + // First, we skip the existing segments that are not modified by this call + for i := range segments { + if offset < cursor + segments[i].Bytes { + break + } + cursor += segments[i].Bytes + partNumber++ + } + + // We reached the end of the file but we haven't reached 'offset' yet + // Therefore we add blocks of zeros + if offset >= currentLength { + for offset - currentLength >= d.ChunkSize { + // Insert a block a zero + d.Conn.ObjectPut(segmentsContainer, getSegment(), + bytes.NewReader(zeroBuf), false, "", + d.getContentType(), nil) + currentLength += d.ChunkSize + partNumber++ + } + + cursor = currentLength + paddingReader = bytes.NewReader(zeroBuf) + } else { + // Offset is inside the current segment : we need to read the + // data from the beginning of the segment to offset + paddingReader, _, err = d.Conn.ObjectOpen(segmentsContainer, getSegment(), false, nil) + if err != nil { + return bytesRead, parseError(getSegment(), err) + } + } + + multi := io.MultiReader( + io.LimitReader(paddingReader, offset - cursor), + io.LimitReader(reader, d.ChunkSize - (offset - cursor)), + ) + + for { + currentSegment, err := d.Conn.ObjectCreate(segmentsContainer, getSegment(), false, "", d.getContentType(), nil) + if err != nil { + return bytesRead, parseError(path, err) + } + + n, err := io.Copy(currentSegment, multi) + if err != nil { + return bytesRead, parseError(path, err) + } + + if n < d.ChunkSize { + // We wrote all the data + if cursor + n < currentLength { + // Copy the end of the chunk + headers := make(swift.Headers) + headers["Range"] = "bytes=" + strconv.FormatInt(cursor + n, 10) + "-" + strconv.FormatInt(cursor + d.ChunkSize, 10) + file, _, err := d.Conn.ObjectOpen(d.Container, d.swiftPath(path), false, headers) + if err != nil { + return bytesRead, parseError(path, err) + } + io.Copy(currentSegment, file) + file.Close() + } + if n > 0 { + currentSegment.Close() + bytesRead += n - max(0, offset - cursor) + } + break + } + + currentSegment.Close() + bytesRead += n - max(0, offset - cursor) + multi = io.MultiReader(io.LimitReader(reader, d.ChunkSize)) + cursor += d.ChunkSize + partNumber++ + } + + return bytesRead, nil +} + +// Stat retrieves the FileInfo for the given path, including the current size +// in bytes and the creation time. +func (d *driver) Stat(ctx context.Context, path string) (storagedriver.FileInfo, error) { + info, _, err := d.Conn.Object(d.Container, d.swiftPath(path)) + if err != nil { + return nil, parseError(path, err) + } + + fi := storagedriver.FileInfoFields{ + Path: path, + IsDir: info.ContentType == "application/directory", + Size: info.Bytes, + ModTime: info.LastModified, + } + + return storagedriver.FileInfoInternal{FileInfoFields: fi}, nil +} + +// List returns a list of the objects that are direct descendants of the given path. +func (d *driver) List(ctx context.Context, path string) ([]string, error) { + prefix := d.swiftPath(path) + if prefix != "" { + prefix += "/" + } + + opts := &swift.ObjectsOpts{ + Path: prefix, + Delimiter: '/', + } + + files, err := d.Conn.ObjectNames(d.Container, opts) + for index, name := range files { + files[index] = "/" + strings.TrimSuffix(name, "/") + } + + return files, parseError(path, err) +} + +// Move moves an object stored at sourcePath to destPath, removing the original +// object. +func (d *driver) Move(ctx context.Context, sourcePath string, destPath string) error { + err := d.Conn.ObjectMove(d.Container, d.swiftPath(sourcePath), + d.Container, d.swiftPath(destPath)) + if err != nil { + return parseError(sourcePath, err) + } + + return nil +} + +// Delete recursively deletes all objects stored at "path" and its subpaths. +func (d *driver) Delete(ctx context.Context, path string) error { + opts := swift.ObjectsOpts{ + Prefix: d.swiftPath(path), + } + + objects, err := d.Conn.ObjectNamesAll(d.Container, &opts) + if err != nil { + return parseError(path, err) + } + if len(objects) == 0 { + return storagedriver.PathNotFoundError{Path: path} + } + + for index, name := range objects { + objects[index] = name[len(d.Prefix):] + } + + var multiDelete = true + if d.BulkDeleteSupport { + _, err := d.Conn.BulkDelete(d.Container, objects) + multiDelete = err != nil + } + if multiDelete { + for _, name := range objects { + if _, headers, err := d.Conn.Object(d.Container, name); err == nil { + manifest, ok := headers["X-Object-Manifest"] + if ok { + components := strings.SplitN(manifest, "/", 2) + segContainer := components[0] + segments, err := d.Conn.ObjectNamesAll(segContainer, &swift.ObjectsOpts{ Prefix: components[1] }) + if err != nil { + return parseError(name, err) + } + + for _, s := range segments { + if err := d.Conn.ObjectDelete(segContainer, s); err != nil { + return parseError(s, err) + } + } + } + } else { + return parseError(name, err) + } + + if err := d.Conn.ObjectDelete(d.Container, name); err != nil { + return parseError(name, err) + } + } + } + + return nil +} + +// URLFor returns a URL which may be used to retrieve the content stored at the given path. +// May return an UnsupportedMethodErr in certain StorageDriver implementations. +func (d *driver) URLFor(ctx context.Context, path string, options map[string]interface{}) (string, error) { + return "", storagedriver.ErrUnsupportedMethod +} + +func (d *driver) swiftPath(path string) string { + return strings.TrimLeft(strings.TrimRight(d.Prefix, "/")+path, "/") +} + +func (d *driver) createParentFolder(path string) (string, error) { + dir := gopath.Dir(path) + if dir != "/" { + _, _, err := d.Conn.Object(d.Container, d.swiftPath(dir)) + if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == 404 { + _, err := d.Conn.ObjectPut(d.Container, d.swiftPath(dir), bytes.NewReader(make([]byte, 0)), + false, "", "application/directory", nil) + if err != nil { + return dir, err + } + } + } + + return dir, nil +} + +func (d *driver) getContentType() string { + return "application/octet-stream" +} + +func detectBulkDelete(authURL string) (bulkDelete bool) { + resp, err := http.Get(filepath.Join(authURL, "..", "..") + "/info") + if err == nil { + defer resp.Body.Close() + decoder := json.NewDecoder(resp.Body) + var infos swiftInfo + if decoder.Decode(&infos) == nil { + _, bulkDelete = infos["bulk_delete"] + } + } + return +} + +func parseError(path string, err error) error { + if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == 404 { + return storagedriver.PathNotFoundError{Path: path} + } + + return err +} diff --git a/docs/storage/driver/swift/swift_test.go b/docs/storage/driver/swift/swift_test.go new file mode 100644 index 000000000..6038c319c --- /dev/null +++ b/docs/storage/driver/swift/swift_test.go @@ -0,0 +1,141 @@ +package swift + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/lebauce/swift/swifttest" + + "github.com/docker/distribution/context" + storagedriver "github.com/docker/distribution/registry/storage/driver" + "github.com/docker/distribution/registry/storage/driver/testsuites" + + "gopkg.in/check.v1" +) + +// Hook up gocheck into the "go test" runner. +func Test(t *testing.T) { check.TestingT(t) } + +type SwiftDriverConstructor func(rootDirectory string) (*Driver, error) + +func init() { + var ( + username string + password string + authURL string + tenant string + container string + region string + prefix string + swiftServer *swifttest.SwiftServer + err error + ) + if username = os.Getenv("OS_USERNAME"); username == "" { + username = os.Getenv("ST_USER") + } + if password = os.Getenv("OS_PASSWORD"); password == "" { + password = os.Getenv("ST_KEY") + } + if authURL = os.Getenv("OS_AUTH_URL"); authURL == "" { + authURL = os.Getenv("ST_AUTH") + } + tenant = os.Getenv("OS_TENANT_NAME") + container = os.Getenv("OS_CONTAINER_NAME") + region = os.Getenv("OS_REGION_NAME") + prefix = os.Getenv("OS_CONTAINER_PREFIX") + + if username == "" || password == "" || authURL == "" || container == "" { + if swiftServer, err = swifttest.NewSwiftServer("localhost"); err != nil { + panic(err) + } + username = "swifttest" + password = "swifttest" + authURL = swiftServer.AuthURL + container = "test" + } + + root, err := ioutil.TempDir("", "driver-") + if err != nil { + panic(err) + } + defer os.Remove(root) + + swiftDriverConstructor := func(rootDirectory string) (*Driver, error) { + parameters := DriverParameters{ + username, + password, + authURL, + tenant, + region, + container, + prefix, + defaultChunkSize, + } + + return New(parameters) + } + + skipCheck := func() string { + return "" + } + + driverConstructor := func() (storagedriver.StorageDriver, error) { + return swiftDriverConstructor(root) + } + + testsuites.RegisterInProcessSuite(driverConstructor, skipCheck) + + RegisterSwiftDriverSuite(swiftDriverConstructor, skipCheck, swiftServer) +} + +func RegisterSwiftDriverSuite(swiftDriverConstructor SwiftDriverConstructor, skipCheck testsuites.SkipCheck, + swiftServer *swifttest.SwiftServer) { + check.Suite(&SwiftDriverSuite{ + Constructor: swiftDriverConstructor, + SkipCheck: skipCheck, + SwiftServer: swiftServer, + }) +} + +type SwiftDriverSuite struct { + Constructor SwiftDriverConstructor + SwiftServer *swifttest.SwiftServer + testsuites.SkipCheck +} + +func (suite *SwiftDriverSuite) SetUpSuite(c *check.C) { + if reason := suite.SkipCheck(); reason != "" { + c.Skip(reason) + } +} + +func (suite *SwiftDriverSuite) TestEmptyRootList(c *check.C) { + validRoot, err := ioutil.TempDir("", "driver-") + c.Assert(err, check.IsNil) + defer os.Remove(validRoot) + + rootedDriver, err := suite.Constructor(validRoot) + c.Assert(err, check.IsNil) + emptyRootDriver, err := suite.Constructor("") + c.Assert(err, check.IsNil) + slashRootDriver, err := suite.Constructor("/") + c.Assert(err, check.IsNil) + + filename := "/test" + contents := []byte("contents") + ctx := context.Background() + err = rootedDriver.PutContent(ctx, filename, contents) + c.Assert(err, check.IsNil) + defer rootedDriver.Delete(ctx, filename) + + keys, err := emptyRootDriver.List(ctx, "/") + for _, path := range keys { + c.Assert(storagedriver.PathRegexp.MatchString(path), check.Equals, true) + } + + keys, err = slashRootDriver.List(ctx, "/") + for _, path := range keys { + c.Assert(storagedriver.PathRegexp.MatchString(path), check.Equals, true) + } +} From 1f4eb7b73523d596b4314202d803f56475ac1bdf Mon Sep 17 00:00:00 2001 From: davidli Date: Fri, 22 May 2015 14:31:47 +0800 Subject: [PATCH 02/32] Use gofmt to format the code of swift driver. Signed-off-by: Li Wenquan --- docs/storage/driver/swift/swift.go | 36 ++++++++++++------------- docs/storage/driver/swift/swift_test.go | 16 +++++------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index 4a812e9e0..b4aaacf60 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -155,8 +155,8 @@ func New(params DriverParameters) (*Driver, error) { return nil, fmt.Errorf("Failed to create container %s (%s)", params.Container, err) } - if err := ct.ContainerCreate(params.Container + "_segments", nil); err != nil { - return nil, fmt.Errorf("Failed to create container %s (%s)", params.Container + "_segments", err) + if err := ct.ContainerCreate(params.Container+"_segments", nil); err != nil { + return nil, fmt.Errorf("Failed to create container %s (%s)", params.Container+"_segments", err) } d := &driver{ @@ -197,7 +197,7 @@ func (d *driver) PutContent(ctx context.Context, path string, contents []byte) e return parseError(dir, err) } err := d.Conn.ObjectPutBytes(d.Container, d.swiftPath(path), - contents, d.getContentType()) + contents, d.getContentType()) return parseError(path, err) } @@ -262,7 +262,7 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea headers := make(swift.Headers) headers["X-Object-Manifest"] = segmentsContainer + "/" + d.swiftPath(path) manifest, err := d.Conn.ObjectCreate(d.Container, d.swiftPath(path), false, "", - d.getContentType(), headers) + d.getContentType(), headers) manifest.Close() if err != nil { return bytesRead, parseError(path, err) @@ -279,7 +279,7 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea headers := make(swift.Headers) headers["Content-Type"] = "application/json" opts := &swift.ObjectsOpts{Prefix: d.swiftPath(path), Headers: headers} - segments, err = d.Conn.Objects(d.Container + "_segments", opts) + segments, err = d.Conn.Objects(d.Container+"_segments", opts) if err != nil { return bytesRead, parseError(path, err) } @@ -287,7 +287,7 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea // First, we skip the existing segments that are not modified by this call for i := range segments { - if offset < cursor + segments[i].Bytes { + if offset < cursor+segments[i].Bytes { break } cursor += segments[i].Bytes @@ -297,11 +297,11 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea // We reached the end of the file but we haven't reached 'offset' yet // Therefore we add blocks of zeros if offset >= currentLength { - for offset - currentLength >= d.ChunkSize { + for offset-currentLength >= d.ChunkSize { // Insert a block a zero d.Conn.ObjectPut(segmentsContainer, getSegment(), - bytes.NewReader(zeroBuf), false, "", - d.getContentType(), nil) + bytes.NewReader(zeroBuf), false, "", + d.getContentType(), nil) currentLength += d.ChunkSize partNumber++ } @@ -318,8 +318,8 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea } multi := io.MultiReader( - io.LimitReader(paddingReader, offset - cursor), - io.LimitReader(reader, d.ChunkSize - (offset - cursor)), + io.LimitReader(paddingReader, offset-cursor), + io.LimitReader(reader, d.ChunkSize-(offset-cursor)), ) for { @@ -335,10 +335,10 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea if n < d.ChunkSize { // We wrote all the data - if cursor + n < currentLength { + if cursor+n < currentLength { // Copy the end of the chunk headers := make(swift.Headers) - headers["Range"] = "bytes=" + strconv.FormatInt(cursor + n, 10) + "-" + strconv.FormatInt(cursor + d.ChunkSize, 10) + headers["Range"] = "bytes=" + strconv.FormatInt(cursor+n, 10) + "-" + strconv.FormatInt(cursor+d.ChunkSize, 10) file, _, err := d.Conn.ObjectOpen(d.Container, d.swiftPath(path), false, headers) if err != nil { return bytesRead, parseError(path, err) @@ -348,13 +348,13 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea } if n > 0 { currentSegment.Close() - bytesRead += n - max(0, offset - cursor) + bytesRead += n - max(0, offset-cursor) } break } currentSegment.Close() - bytesRead += n - max(0, offset - cursor) + bytesRead += n - max(0, offset-cursor) multi = io.MultiReader(io.LimitReader(reader, d.ChunkSize)) cursor += d.ChunkSize partNumber++ @@ -405,7 +405,7 @@ func (d *driver) List(ctx context.Context, path string) ([]string, error) { // object. func (d *driver) Move(ctx context.Context, sourcePath string, destPath string) error { err := d.Conn.ObjectMove(d.Container, d.swiftPath(sourcePath), - d.Container, d.swiftPath(destPath)) + d.Container, d.swiftPath(destPath)) if err != nil { return parseError(sourcePath, err) } @@ -443,7 +443,7 @@ func (d *driver) Delete(ctx context.Context, path string) error { if ok { components := strings.SplitN(manifest, "/", 2) segContainer := components[0] - segments, err := d.Conn.ObjectNamesAll(segContainer, &swift.ObjectsOpts{ Prefix: components[1] }) + segments, err := d.Conn.ObjectNamesAll(segContainer, &swift.ObjectsOpts{Prefix: components[1]}) if err != nil { return parseError(name, err) } @@ -483,7 +483,7 @@ func (d *driver) createParentFolder(path string) (string, error) { _, _, err := d.Conn.Object(d.Container, d.swiftPath(dir)) if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == 404 { _, err := d.Conn.ObjectPut(d.Container, d.swiftPath(dir), bytes.NewReader(make([]byte, 0)), - false, "", "application/directory", nil) + false, "", "application/directory", nil) if err != nil { return dir, err } diff --git a/docs/storage/driver/swift/swift_test.go b/docs/storage/driver/swift/swift_test.go index 6038c319c..03515bb2b 100644 --- a/docs/storage/driver/swift/swift_test.go +++ b/docs/storage/driver/swift/swift_test.go @@ -21,15 +21,15 @@ type SwiftDriverConstructor func(rootDirectory string) (*Driver, error) func init() { var ( - username string - password string - authURL string - tenant string - container string - region string - prefix string + username string + password string + authURL string + tenant string + container string + region string + prefix string swiftServer *swifttest.SwiftServer - err error + err error ) if username = os.Getenv("OS_USERNAME"); username == "" { username = os.Getenv("ST_USER") From 9f7f23e3738a3ce1474d8d651b7e6b76f9722219 Mon Sep 17 00:00:00 2001 From: nevermosby Date: Sat, 23 May 2015 15:22:41 +0800 Subject: [PATCH 03/32] Update the import path for swift driver test Signed-off-by: Li Wenquan --- docs/storage/driver/swift/swift.go | 2 +- docs/storage/driver/swift/swift_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index b4aaacf60..9287be417 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -21,7 +21,7 @@ import ( "strings" "time" - "github.com/lebauce/swift" + "github.com/ncw/swift" "github.com/docker/distribution/context" storagedriver "github.com/docker/distribution/registry/storage/driver" diff --git a/docs/storage/driver/swift/swift_test.go b/docs/storage/driver/swift/swift_test.go index 03515bb2b..5ead8d15a 100644 --- a/docs/storage/driver/swift/swift_test.go +++ b/docs/storage/driver/swift/swift_test.go @@ -5,7 +5,7 @@ import ( "os" "testing" - "github.com/lebauce/swift/swifttest" + "github.com/ncw/swift/swifttest" "github.com/docker/distribution/context" storagedriver "github.com/docker/distribution/registry/storage/driver" From 16a49ade166bd3d80c164c1798edf9e8cecbee39 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Fri, 29 May 2015 15:46:12 +0200 Subject: [PATCH 04/32] Handle error during copy of original content Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index 9287be417..2620de00b 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -343,7 +343,9 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea if err != nil { return bytesRead, parseError(path, err) } - io.Copy(currentSegment, file) + if _, err := io.Copy(currentSegment, file); err != nil { + return bytesRead, parseError(path, err) + } file.Close() } if n > 0 { From 8a22c0f4e10824ad58de51a1038e0effe77569e8 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Fri, 29 May 2015 15:50:22 +0200 Subject: [PATCH 05/32] Simplify code that handles non existing manifests Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index 2620de00b..44f61a1fe 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -253,23 +253,16 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea info, _, err := d.Conn.Object(d.Container, d.swiftPath(path)) if err != nil { - if swiftErr, ok := err.(*swift.Error); ok { - if swiftErr.StatusCode == 404 { - // Create a object manifest - if dir, err := d.createParentFolder(path); err != nil { - return bytesRead, parseError(dir, err) - } - headers := make(swift.Headers) - headers["X-Object-Manifest"] = segmentsContainer + "/" + d.swiftPath(path) - manifest, err := d.Conn.ObjectCreate(d.Container, d.swiftPath(path), false, "", - d.getContentType(), headers) - manifest.Close() - if err != nil { - return bytesRead, parseError(path, err) - } - } else { + if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == 404 { + // Create a object manifest + if dir, err := d.createParentFolder(path); err != nil { + return bytesRead, parseError(dir, err) + } + manifest, err := d.createManifest(path) + if err != nil { return bytesRead, parseError(path, err) } + manifest.Close() } else { return bytesRead, parseError(path, err) } From ea81e208a4263b73fc6d330256afacd0721af680 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Fri, 29 May 2015 16:09:05 +0200 Subject: [PATCH 06/32] Move Dynamic Large Object handling to dedicated methods Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index 44f61a1fe..c7c678cb2 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -237,8 +237,8 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea bytesRead := int64(0) currentLength := int64(0) zeroBuf := make([]byte, d.ChunkSize) - segmentsContainer := d.Container + "_segments" cursor := int64(0) + segmentsContainer := d.getSegmentsContainer() getSegment := func() string { return d.swiftPath(path) + "/" + fmt.Sprintf("%016d", partNumber) @@ -269,10 +269,7 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea } else { // The manifest already exists. Get all the segments currentLength = info.Bytes - headers := make(swift.Headers) - headers["Content-Type"] = "application/json" - opts := &swift.ObjectsOpts{Prefix: d.swiftPath(path), Headers: headers} - segments, err = d.Conn.Objects(d.Container+"_segments", opts) + segments, err = d.getAllSegments(segmentsContainer, path) if err != nil { return bytesRead, parseError(path, err) } @@ -438,14 +435,14 @@ func (d *driver) Delete(ctx context.Context, path string) error { if ok { components := strings.SplitN(manifest, "/", 2) segContainer := components[0] - segments, err := d.Conn.ObjectNamesAll(segContainer, &swift.ObjectsOpts{Prefix: components[1]}) + segments, err := d.getAllSegments(segContainer, components[1]) if err != nil { return parseError(name, err) } for _, s := range segments { - if err := d.Conn.ObjectDelete(segContainer, s); err != nil { - return parseError(s, err) + if err := d.Conn.ObjectDelete(segContainer, s.Name); err != nil { + return parseError(s.Name, err) } } } @@ -492,6 +489,21 @@ func (d *driver) getContentType() string { return "application/octet-stream" } +func (d *driver) getSegmentsContainer() string { + return d.Container + "_segments" +} + +func (d *driver) getAllSegments(container string, path string) ([]swift.Object, error) { + return d.Conn.Objects(container, &swift.ObjectsOpts{Prefix: d.swiftPath(path)}) +} + +func (d *driver) createManifest(path string) (*swift.ObjectCreateFile, error) { + headers := make(swift.Headers) + headers["X-Object-Manifest"] = d.getSegmentsContainer() + "/" + d.swiftPath(path) + return d.Conn.ObjectCreate(d.Container, d.swiftPath(path), false, "", + d.getContentType(), headers) +} + func detectBulkDelete(authURL string) (bulkDelete bool) { resp, err := http.Get(filepath.Join(authURL, "..", "..") + "/info") if err == nil { From 75ce67c469a634ea92d8793deb85457242548284 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Fri, 29 May 2015 16:12:58 +0200 Subject: [PATCH 07/32] Use mitchellh/mapstructure library to parse Swift parameters Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 102 +++++++++++++---------------- 1 file changed, 44 insertions(+), 58 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index c7c678cb2..a60f2029d 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -21,6 +21,7 @@ import ( "strings" "time" + "github.com/mitchellh/mapstructure" "github.com/ncw/swift" "github.com/docker/distribution/context" @@ -33,6 +34,10 @@ const driverName = "swift" const defaultChunkSize = 5 * 1024 * 1024 +const minChunkSize = 1 << 20 + +const directoryMimeType = "application/directory" + //DriverParameters A struct that encapsulates all of the driver parameters after all values have been set type DriverParameters struct { Username string @@ -42,7 +47,7 @@ type DriverParameters struct { Region string Container string Prefix string - ChunkSize int64 + ChunkSize int } type swiftInfo map[string]interface{} @@ -63,7 +68,7 @@ type driver struct { Container string Prefix string BulkDeleteSupport bool - ChunkSize int64 + ChunkSize int } type baseEmbed struct { @@ -83,52 +88,32 @@ type Driver struct { // - authurl // - container func FromParameters(parameters map[string]interface{}) (*Driver, error) { - username, ok := parameters["username"] - if !ok || fmt.Sprint(username) == "" { - return nil, fmt.Errorf("No username parameter provided") - } - password, ok := parameters["password"] - if !ok || fmt.Sprint(password) == "" { - return nil, fmt.Errorf("No password parameter provided") - } - authURL, ok := parameters["authurl"] - if !ok || fmt.Sprint(authURL) == "" { - return nil, fmt.Errorf("No container parameter provided") - } - container, ok := parameters["container"] - if !ok || fmt.Sprint(container) == "" { - return nil, fmt.Errorf("No container parameter provided") - } - tenant, ok := parameters["tenant"] - if !ok { - tenant = "" - } - region, ok := parameters["region"] - if !ok { - region = "" - } - rootDirectory, ok := parameters["rootdirectory"] - if !ok { - rootDirectory = "" - } - chunkSize := int64(defaultChunkSize) - chunkSizeParam, ok := parameters["chunksize"] - if ok { - chunkSize, ok = chunkSizeParam.(int64) - if !ok { - return nil, fmt.Errorf("The chunksize parameter should be a number") - } + params := DriverParameters{ + ChunkSize: defaultChunkSize, } - params := DriverParameters{ - fmt.Sprint(username), - fmt.Sprint(password), - fmt.Sprint(authURL), - fmt.Sprint(tenant), - fmt.Sprint(region), - fmt.Sprint(container), - fmt.Sprint(rootDirectory), - chunkSize, + if err := mapstructure.Decode(parameters, ¶ms); err != nil { + return nil, err + } + + if params.Username == "" { + return nil, fmt.Errorf("No username parameter provided") + } + + if params.Password == "" { + return nil, fmt.Errorf("No password parameter provided") + } + + if params.AuthURL == "" { + return nil, fmt.Errorf("No authurl parameter provided") + } + + if params.Container == "" { + return nil, fmt.Errorf("No container parameter provided") + } + + if params.ChunkSize < minChunkSize { + return nil, fmt.Errorf("The chunksize %#v parameter should be a number that is larger than or equal to %d", params.ChunkSize, minChunkSize) } return New(params) @@ -231,13 +216,14 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea var ( segments []swift.Object paddingReader io.Reader + bytesRead int64 + currentLength int64 + cursor int64 ) - partNumber := int64(1) - bytesRead := int64(0) - currentLength := int64(0) + partNumber := 1 + chunkSize := int64(d.ChunkSize) zeroBuf := make([]byte, d.ChunkSize) - cursor := int64(0) segmentsContainer := d.getSegmentsContainer() getSegment := func() string { @@ -287,12 +273,12 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea // We reached the end of the file but we haven't reached 'offset' yet // Therefore we add blocks of zeros if offset >= currentLength { - for offset-currentLength >= d.ChunkSize { + for offset-currentLength >= chunkSize { // Insert a block a zero d.Conn.ObjectPut(segmentsContainer, getSegment(), bytes.NewReader(zeroBuf), false, "", d.getContentType(), nil) - currentLength += d.ChunkSize + currentLength += chunkSize partNumber++ } @@ -309,7 +295,7 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea multi := io.MultiReader( io.LimitReader(paddingReader, offset-cursor), - io.LimitReader(reader, d.ChunkSize-(offset-cursor)), + io.LimitReader(reader, chunkSize-(offset-cursor)), ) for { @@ -323,12 +309,12 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea return bytesRead, parseError(path, err) } - if n < d.ChunkSize { + if n < chunkSize { // We wrote all the data if cursor+n < currentLength { // Copy the end of the chunk headers := make(swift.Headers) - headers["Range"] = "bytes=" + strconv.FormatInt(cursor+n, 10) + "-" + strconv.FormatInt(cursor+d.ChunkSize, 10) + headers["Range"] = "bytes=" + strconv.FormatInt(cursor+n, 10) + "-" + strconv.FormatInt(cursor+chunkSize, 10) file, _, err := d.Conn.ObjectOpen(d.Container, d.swiftPath(path), false, headers) if err != nil { return bytesRead, parseError(path, err) @@ -347,8 +333,8 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea currentSegment.Close() bytesRead += n - max(0, offset-cursor) - multi = io.MultiReader(io.LimitReader(reader, d.ChunkSize)) - cursor += d.ChunkSize + multi = io.MultiReader(io.LimitReader(reader, chunkSize)) + cursor += chunkSize partNumber++ } @@ -365,7 +351,7 @@ func (d *driver) Stat(ctx context.Context, path string) (storagedriver.FileInfo, fi := storagedriver.FileInfoFields{ Path: path, - IsDir: info.ContentType == "application/directory", + IsDir: info.ContentType == directoryMimeType, Size: info.Bytes, ModTime: info.LastModified, } From 4e619bc9b100a7afcd3018af5492e29dd964a8e5 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Fri, 29 May 2015 16:14:12 +0200 Subject: [PATCH 08/32] Remove one level of indentation in swift path handling code Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index a60f2029d..213dfc292 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -457,14 +457,16 @@ func (d *driver) swiftPath(path string) string { func (d *driver) createParentFolder(path string) (string, error) { dir := gopath.Dir(path) - if dir != "/" { - _, _, err := d.Conn.Object(d.Container, d.swiftPath(dir)) - if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == 404 { - _, err := d.Conn.ObjectPut(d.Container, d.swiftPath(dir), bytes.NewReader(make([]byte, 0)), - false, "", "application/directory", nil) - if err != nil { - return dir, err - } + if dir == "/" { + return dir, nil + } + + _, _, err := d.Conn.Object(d.Container, d.swiftPath(dir)) + if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == 404 { + _, err := d.Conn.ObjectPut(d.Container, d.swiftPath(dir), bytes.NewReader(make([]byte, 0)), + false, "", directoryMimeType, nil) + if err != nil { + return dir, err } } From 1d46bb2bccf69ebf81585d821467fdded6fd36fb Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Thu, 4 Jun 2015 10:10:21 +0200 Subject: [PATCH 09/32] Create full folder hierarchy instead of just the top level folder Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 31 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index 213dfc292..38e87239c 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -178,8 +178,8 @@ func (d *driver) GetContent(ctx context.Context, path string) ([]byte, error) { // PutContent stores the []byte content at a location designated by "path". func (d *driver) PutContent(ctx context.Context, path string, contents []byte) error { - if dir, err := d.createParentFolder(path); err != nil { - return parseError(dir, err) + if err := d.createParentFolders(path); err != nil { + return err } err := d.Conn.ObjectPutBytes(d.Container, d.swiftPath(path), contents, d.getContentType()) @@ -241,8 +241,8 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea if err != nil { if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == 404 { // Create a object manifest - if dir, err := d.createParentFolder(path); err != nil { - return bytesRead, parseError(dir, err) + if err := d.createParentFolders(path); err != nil { + return bytesRead, err } manifest, err := d.createManifest(path) if err != nil { @@ -455,22 +455,21 @@ func (d *driver) swiftPath(path string) string { return strings.TrimLeft(strings.TrimRight(d.Prefix, "/")+path, "/") } -func (d *driver) createParentFolder(path string) (string, error) { +func (d *driver) createParentFolders(path string) error { dir := gopath.Dir(path) - if dir == "/" { - return dir, nil - } - - _, _, err := d.Conn.Object(d.Container, d.swiftPath(dir)) - if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == 404 { - _, err := d.Conn.ObjectPut(d.Container, d.swiftPath(dir), bytes.NewReader(make([]byte, 0)), - false, "", directoryMimeType, nil) - if err != nil { - return dir, err + for dir != "/" { + _, _, err := d.Conn.Object(d.Container, d.swiftPath(dir)) + if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == 404 { + _, err := d.Conn.ObjectPut(d.Container, d.swiftPath(dir), bytes.NewReader(make([]byte, 0)), + false, "", directoryMimeType, nil) + if err != nil { + return parseError(dir, err) + } } + dir = gopath.Dir(dir) } - return dir, nil + return nil } func (d *driver) getContentType() string { From 3f9e7ed169af1bc5879d669e1b68cc52220f0ecb Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Thu, 4 Jun 2015 10:11:19 +0200 Subject: [PATCH 10/32] Use 'prefix' parameter instead of 'path' when listing files Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index 38e87239c..66c1a85b5 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -361,19 +361,23 @@ func (d *driver) Stat(ctx context.Context, path string) (storagedriver.FileInfo, // List returns a list of the objects that are direct descendants of the given path. func (d *driver) List(ctx context.Context, path string) ([]string, error) { + var files []string + prefix := d.swiftPath(path) if prefix != "" { prefix += "/" } opts := &swift.ObjectsOpts{ - Path: prefix, + Prefix: prefix, Delimiter: '/', } - files, err := d.Conn.ObjectNames(d.Container, opts) - for index, name := range files { - files[index] = "/" + strings.TrimSuffix(name, "/") + objects, err := d.Conn.Objects(d.Container, opts) + for _, obj := range objects { + if !obj.PseudoDirectory { + files = append(files, "/"+strings.TrimSuffix(obj.Name, "/")) + } } return files, parseError(path, err) From 062d6266cf5153bf40dbe01d781cdeace7653aa1 Mon Sep 17 00:00:00 2001 From: davidli Date: Mon, 8 Jun 2015 16:37:11 +0800 Subject: [PATCH 11/32] Add support for Openstack Identity v3 API Signed-off-by: Li Wenquan --- docs/storage/driver/swift/swift.go | 34 ++++++++++++++++++------- docs/storage/driver/swift/swift_test.go | 31 +++++++++++++++------- 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index 66c1a85b5..0875edefb 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -10,6 +10,7 @@ package swift import ( "bytes" + "crypto/tls" "encoding/json" "fmt" "io" @@ -40,14 +41,18 @@ const directoryMimeType = "application/directory" //DriverParameters A struct that encapsulates all of the driver parameters after all values have been set type DriverParameters struct { - Username string - Password string - AuthURL string - Tenant string - Region string - Container string - Prefix string - ChunkSize int + Username string + Password string + AuthURL string + Tenant string + TenantID string + Domain string + DomainID string + Region string + Container string + Prefix string + InsecureSkipVerify bool + ChunkSize int } type swiftInfo map[string]interface{} @@ -89,7 +94,8 @@ type Driver struct { // - container func FromParameters(parameters map[string]interface{}) (*Driver, error) { params := DriverParameters{ - ChunkSize: defaultChunkSize, + ChunkSize: defaultChunkSize, + InsecureSkipVerify: false, } if err := mapstructure.Decode(parameters, ¶ms); err != nil { @@ -121,6 +127,12 @@ func FromParameters(parameters map[string]interface{}) (*Driver, error) { // New constructs a new Driver with the given Openstack Swift credentials and container name func New(params DriverParameters) (*Driver, error) { + transport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + MaxIdleConnsPerHost: 2048, + TLSClientConfig: &tls.Config{InsecureSkipVerify: params.InsecureSkipVerify}, + } + ct := swift.Connection{ UserName: params.Username, ApiKey: params.Password, @@ -128,6 +140,10 @@ func New(params DriverParameters) (*Driver, error) { Region: params.Region, UserAgent: "distribution", Tenant: params.Tenant, + TenantId: params.TenantID, + Domain: params.Domain, + DomainId: params.DomainID, + Transport: transport, ConnectTimeout: 60 * time.Second, Timeout: 15 * 60 * time.Second, } diff --git a/docs/storage/driver/swift/swift_test.go b/docs/storage/driver/swift/swift_test.go index 5ead8d15a..fc66aa268 100644 --- a/docs/storage/driver/swift/swift_test.go +++ b/docs/storage/driver/swift/swift_test.go @@ -3,6 +3,7 @@ package swift import ( "io/ioutil" "os" + "strconv" "testing" "github.com/ncw/swift/swifttest" @@ -21,15 +22,19 @@ type SwiftDriverConstructor func(rootDirectory string) (*Driver, error) func init() { var ( - username string - password string - authURL string - tenant string - container string - region string - prefix string - swiftServer *swifttest.SwiftServer - err error + username string + password string + authURL string + tenant string + tenantID string + domain string + domainID string + container string + region string + prefix string + insecureSkipVerify bool + swiftServer *swifttest.SwiftServer + err error ) if username = os.Getenv("OS_USERNAME"); username == "" { username = os.Getenv("ST_USER") @@ -41,9 +46,13 @@ func init() { authURL = os.Getenv("ST_AUTH") } tenant = os.Getenv("OS_TENANT_NAME") + tenantID = os.Getenv("OS_TENANT_ID") + domain = os.Getenv("OS_DOMAIN_NAME") + domainID = os.Getenv("OS_DOMAIN_ID") container = os.Getenv("OS_CONTAINER_NAME") region = os.Getenv("OS_REGION_NAME") prefix = os.Getenv("OS_CONTAINER_PREFIX") + insecureSkipVerify, _ = strconv.ParseBool(os.Getenv("ST_INSECURESKIPVERIFY")) if username == "" || password == "" || authURL == "" || container == "" { if swiftServer, err = swifttest.NewSwiftServer("localhost"); err != nil { @@ -67,9 +76,13 @@ func init() { password, authURL, tenant, + tenantID, + domain, + domainID, region, container, prefix, + insecureSkipVerify, defaultChunkSize, } From a1ae7f712220347308f85d34d5a256aaa331149a Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Fri, 19 Jun 2015 15:55:34 +0200 Subject: [PATCH 12/32] Increase default chunk size to 20M Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index 0875edefb..cd195cc2b 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -33,7 +33,7 @@ import ( const driverName = "swift" -const defaultChunkSize = 5 * 1024 * 1024 +const defaultChunkSize = 20 * 1024 * 1024 const minChunkSize = 1 << 20 From 9ab55eae39b544aa3d9383cd315eaa4d7a541339 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Fri, 19 Jun 2015 16:44:55 +0200 Subject: [PATCH 13/32] Use only one Swift container for both files and manifests Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 150 ++++++++++++++++------------- 1 file changed, 81 insertions(+), 69 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index cd195cc2b..e5f49a953 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -156,10 +156,6 @@ func New(params DriverParameters) (*Driver, error) { return nil, fmt.Errorf("Failed to create container %s (%s)", params.Container, err) } - if err := ct.ContainerCreate(params.Container+"_segments", nil); err != nil { - return nil, fmt.Errorf("Failed to create container %s (%s)", params.Container+"_segments", err) - } - d := &driver{ Conn: ct, Container: params.Container, @@ -231,8 +227,8 @@ func (d *driver) ReadStream(ctx context.Context, path string, offset int64) (io. func (d *driver) WriteStream(ctx context.Context, path string, offset int64, reader io.Reader) (int64, error) { var ( segments []swift.Object + multi io.Reader paddingReader io.Reader - bytesRead int64 currentLength int64 cursor int64 ) @@ -240,10 +236,9 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea partNumber := 1 chunkSize := int64(d.ChunkSize) zeroBuf := make([]byte, d.ChunkSize) - segmentsContainer := d.getSegmentsContainer() getSegment := func() string { - return d.swiftPath(path) + "/" + fmt.Sprintf("%016d", partNumber) + return d.swiftSegmentPath(path) + "/" + fmt.Sprintf("%016d", partNumber) } max := func(a int64, b int64) int64 { @@ -258,22 +253,22 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == 404 { // Create a object manifest if err := d.createParentFolders(path); err != nil { - return bytesRead, err + return 0, err } manifest, err := d.createManifest(path) if err != nil { - return bytesRead, parseError(path, err) + return 0, parseError(path, err) } manifest.Close() } else { - return bytesRead, parseError(path, err) + return 0, parseError(path, err) } } else { // The manifest already exists. Get all the segments currentLength = info.Bytes - segments, err = d.getAllSegments(segmentsContainer, path) + segments, err = d.getAllSegments(path) if err != nil { - return bytesRead, parseError(path, err) + return 0, parseError(path, err) } } @@ -291,7 +286,7 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea if offset >= currentLength { for offset-currentLength >= chunkSize { // Insert a block a zero - d.Conn.ObjectPut(segmentsContainer, getSegment(), + d.Conn.ObjectPut(d.Container, getSegment(), bytes.NewReader(zeroBuf), false, "", d.getContentType(), nil) currentLength += chunkSize @@ -303,26 +298,34 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea } else { // Offset is inside the current segment : we need to read the // data from the beginning of the segment to offset - paddingReader, _, err = d.Conn.ObjectOpen(segmentsContainer, getSegment(), false, nil) + file, _, err := d.Conn.ObjectOpen(d.Container, getSegment(), false, nil) + defer file.Close() + paddingReader = file + if err != nil { - return bytesRead, parseError(getSegment(), err) + return 0, parseError(getSegment(), err) } } - multi := io.MultiReader( + multi = io.MultiReader( io.LimitReader(paddingReader, offset-cursor), io.LimitReader(reader, chunkSize-(offset-cursor)), ) - for { - currentSegment, err := d.Conn.ObjectCreate(segmentsContainer, getSegment(), false, "", d.getContentType(), nil) + writeSegment := func(segment string) (finished bool, bytesRead int64, err error) { + currentSegment, err := d.Conn.ObjectCreate(d.Container, segment, false, "", d.getContentType(), nil) if err != nil { - return bytesRead, parseError(path, err) + return false, bytesRead, parseError(path, err) } n, err := io.Copy(currentSegment, multi) if err != nil { - return bytesRead, parseError(path, err) + return false, bytesRead, parseError(path, err) + } + + if n > 0 { + defer currentSegment.Close() + bytesRead += n - max(0, offset-cursor) } if n < chunkSize { @@ -333,25 +336,39 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea headers["Range"] = "bytes=" + strconv.FormatInt(cursor+n, 10) + "-" + strconv.FormatInt(cursor+chunkSize, 10) file, _, err := d.Conn.ObjectOpen(d.Container, d.swiftPath(path), false, headers) if err != nil { - return bytesRead, parseError(path, err) + return false, bytesRead, parseError(path, err) } - if _, err := io.Copy(currentSegment, file); err != nil { - return bytesRead, parseError(path, err) + + _, copyErr := io.Copy(currentSegment, file) + + if err := file.Close(); err != nil { + return false, bytesRead, parseError(path, err) + } + + if copyErr != nil { + return false, bytesRead, parseError(path, copyErr) } - file.Close() } - if n > 0 { - currentSegment.Close() - bytesRead += n - max(0, offset-cursor) - } - break + + return true, bytesRead, nil } - currentSegment.Close() - bytesRead += n - max(0, offset-cursor) - multi = io.MultiReader(io.LimitReader(reader, chunkSize)) + multi = io.LimitReader(reader, chunkSize) cursor += chunkSize partNumber++ + + return false, bytesRead, nil + } + + finished := false + read := int64(0) + bytesRead := int64(0) + for finished == false { + finished, read, err = writeSegment(getSegment()) + bytesRead += read + if err != nil { + return bytesRead, err + } } return bytesRead, nil @@ -392,7 +409,7 @@ func (d *driver) List(ctx context.Context, path string) ([]string, error) { objects, err := d.Conn.Objects(d.Container, opts) for _, obj := range objects { if !obj.PseudoDirectory { - files = append(files, "/"+strings.TrimSuffix(obj.Name, "/")) + files = append(files, strings.TrimPrefix(strings.TrimSuffix(obj.Name, "/"), d.swiftPath("/"))) } } @@ -425,40 +442,35 @@ func (d *driver) Delete(ctx context.Context, path string) error { return storagedriver.PathNotFoundError{Path: path} } - for index, name := range objects { - objects[index] = name[len(d.Prefix):] - } - - var multiDelete = true if d.BulkDeleteSupport { - _, err := d.Conn.BulkDelete(d.Container, objects) - multiDelete = err != nil + if _, err := d.Conn.BulkDelete(d.Container, objects); err != swift.Forbidden { + return parseError(path, err) + } } - if multiDelete { - for _, name := range objects { - if _, headers, err := d.Conn.Object(d.Container, name); err == nil { - manifest, ok := headers["X-Object-Manifest"] - if ok { - components := strings.SplitN(manifest, "/", 2) - segContainer := components[0] - segments, err := d.getAllSegments(segContainer, components[1]) - if err != nil { - return parseError(name, err) - } - for _, s := range segments { - if err := d.Conn.ObjectDelete(segContainer, s.Name); err != nil { - return parseError(s.Name, err) - } + for _, name := range objects { + if _, headers, err := d.Conn.Object(d.Container, name); err == nil { + manifest, ok := headers["X-Object-Manifest"] + if ok { + components := strings.SplitN(manifest, "/", 2) + segContainer := components[0] + segments, err := d.getAllSegments(components[1]) + if err != nil { + return parseError(name, err) + } + + for _, s := range segments { + if err := d.Conn.ObjectDelete(segContainer, s.Name); err != nil { + return parseError(s.Name, err) } } - } else { - return parseError(name, err) } + } else { + return parseError(name, err) + } - if err := d.Conn.ObjectDelete(d.Container, name); err != nil { - return parseError(name, err) - } + if err := d.Conn.ObjectDelete(d.Container, name); err != nil { + return parseError(name, err) } } @@ -472,14 +484,18 @@ func (d *driver) URLFor(ctx context.Context, path string, options map[string]int } func (d *driver) swiftPath(path string) string { - return strings.TrimLeft(strings.TrimRight(d.Prefix, "/")+path, "/") + return strings.TrimLeft(strings.TrimRight(d.Prefix+"/files"+path, "/"), "/") +} + +func (d *driver) swiftSegmentPath(path string) string { + return strings.TrimLeft(strings.TrimRight(d.Prefix+"/segments"+path, "/"), "/") } func (d *driver) createParentFolders(path string) error { dir := gopath.Dir(path) for dir != "/" { _, _, err := d.Conn.Object(d.Container, d.swiftPath(dir)) - if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == 404 { + if err == swift.ContainerNotFound || err == swift.ObjectNotFound { _, err := d.Conn.ObjectPut(d.Container, d.swiftPath(dir), bytes.NewReader(make([]byte, 0)), false, "", directoryMimeType, nil) if err != nil { @@ -496,17 +512,13 @@ func (d *driver) getContentType() string { return "application/octet-stream" } -func (d *driver) getSegmentsContainer() string { - return d.Container + "_segments" -} - -func (d *driver) getAllSegments(container string, path string) ([]swift.Object, error) { - return d.Conn.Objects(container, &swift.ObjectsOpts{Prefix: d.swiftPath(path)}) +func (d *driver) getAllSegments(path string) ([]swift.Object, error) { + return d.Conn.Objects(d.Container, &swift.ObjectsOpts{Prefix: d.swiftSegmentPath(path)}) } func (d *driver) createManifest(path string) (*swift.ObjectCreateFile, error) { headers := make(swift.Headers) - headers["X-Object-Manifest"] = d.getSegmentsContainer() + "/" + d.swiftPath(path) + headers["X-Object-Manifest"] = d.Container + "/" + d.swiftSegmentPath(path) return d.Conn.ObjectCreate(d.Container, d.swiftPath(path), false, "", d.getContentType(), headers) } From d91c4cb6947559ff9c3dff44242d950fd5297b9f Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Fri, 19 Jun 2015 16:46:10 +0200 Subject: [PATCH 14/32] Improve 404 errors handling Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index e5f49a953..5c9107e99 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -250,7 +250,7 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea info, _, err := d.Conn.Object(d.Container, d.swiftPath(path)) if err != nil { - if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == 404 { + if err == swift.ContainerNotFound || err == swift.ObjectNotFound { // Create a object manifest if err := d.createParentFolders(path); err != nil { return 0, err @@ -537,7 +537,7 @@ func detectBulkDelete(authURL string) (bulkDelete bool) { } func parseError(path string, err error) error { - if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == 404 { + if err == swift.ContainerNotFound || err == swift.ObjectNotFound { return storagedriver.PathNotFoundError{Path: path} } From 5cce023aa987a73b1c2e78348f0592397075a454 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Mon, 22 Jun 2015 21:27:49 +0200 Subject: [PATCH 15/32] Do not read segment if no padding is necessary Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index 5c9107e99..e0284b9c5 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -295,7 +295,7 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea cursor = currentLength paddingReader = bytes.NewReader(zeroBuf) - } else { + } else if offset-cursor > 0 { // Offset is inside the current segment : we need to read the // data from the beginning of the segment to offset file, _, err := d.Conn.ObjectOpen(d.Container, getSegment(), false, nil) @@ -307,10 +307,12 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea } } - multi = io.MultiReader( - io.LimitReader(paddingReader, offset-cursor), - io.LimitReader(reader, chunkSize-(offset-cursor)), - ) + readers := []io.Reader{} + if paddingReader != nil { + readers = append(readers, io.LimitReader(paddingReader, offset-cursor)) + } + readers = append(readers, io.LimitReader(reader, chunkSize-(offset-cursor))) + multi = io.MultiReader(readers...) writeSegment := func(segment string) (finished bool, bytesRead int64, err error) { currentSegment, err := d.Conn.ObjectCreate(d.Container, segment, false, "", d.getContentType(), nil) From 7b0276dce55e95061bbedab1f2fa325de8e61a63 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Tue, 30 Jun 2015 14:17:12 +0200 Subject: [PATCH 16/32] Add code documentation Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index e0284b9c5..7de6f8de4 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -4,8 +4,18 @@ // This package leverages the ncw/swift client library for interfacing with // Swift. // -// Because Swift is a key, value store the Stat call does not support last modification -// time for directories (directories are an abstraction for key, value stores) +// It supports both TempAuth authentication and Keystone authentication +// (up to version 3). +// +// Since Swift has no concept of directories (directories are an abstration), +// empty objects are created with the MIME type application/vnd.swift.directory. +// +// As Swift has a limit on the size of a single uploaded object (by default +// this is 5GB), the driver makes use of the Swift Large Object Support +// (http://docs.openstack.org/developer/swift/overview_large_objects.html). +// Only one container is used for both manifests and data objects. Manifests +// are stored in the 'files' pseudo directory, data objects are stored under +// 'segments'. package swift import ( @@ -33,8 +43,10 @@ import ( const driverName = "swift" +// defaultChunkSize defines the default size of a segment const defaultChunkSize = 20 * 1024 * 1024 +// minChunkSize defines the minimum size of a segment const minChunkSize = 1 << 20 const directoryMimeType = "application/directory" @@ -80,8 +92,8 @@ type baseEmbed struct { base.Base } -// Driver is a storagedriver.StorageDriver implementation backed by Amazon Swift -// Objects are stored at absolute keys in the provided bucket. +// Driver is a storagedriver.StorageDriver implementation backed by Openstack Swift +// Objects are stored at absolute keys in the provided container. type Driver struct { baseEmbed } From 80bfcb68a87ecfda86e38fc0fc87000cf675e231 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Tue, 30 Jun 2015 14:21:03 +0200 Subject: [PATCH 17/32] Change folder mime type to application/vnc.swift.directory Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index 7de6f8de4..380d65da2 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -49,7 +49,8 @@ const defaultChunkSize = 20 * 1024 * 1024 // minChunkSize defines the minimum size of a segment const minChunkSize = 1 << 20 -const directoryMimeType = "application/directory" +// Vendor MIME type used for objects that act as directories +const directoryMimeType = "application/vnd.swift.directory" //DriverParameters A struct that encapsulates all of the driver parameters after all values have been set type DriverParameters struct { From fbc74a6457bedfead409567a4c2dc60e15cd5856 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Tue, 30 Jun 2015 14:22:41 +0200 Subject: [PATCH 18/32] Rename DriverParameters structure to Parameters Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 8 ++++---- docs/storage/driver/swift/swift_test.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index 380d65da2..f91af9081 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -52,8 +52,8 @@ const minChunkSize = 1 << 20 // Vendor MIME type used for objects that act as directories const directoryMimeType = "application/vnd.swift.directory" -//DriverParameters A struct that encapsulates all of the driver parameters after all values have been set -type DriverParameters struct { +// Parameters A struct that encapsulates all of the driver parameters after all values have been set +type Parameters struct { Username string Password string AuthURL string @@ -106,7 +106,7 @@ type Driver struct { // - authurl // - container func FromParameters(parameters map[string]interface{}) (*Driver, error) { - params := DriverParameters{ + params := Parameters{ ChunkSize: defaultChunkSize, InsecureSkipVerify: false, } @@ -139,7 +139,7 @@ func FromParameters(parameters map[string]interface{}) (*Driver, error) { } // New constructs a new Driver with the given Openstack Swift credentials and container name -func New(params DriverParameters) (*Driver, error) { +func New(params Parameters) (*Driver, error) { transport := &http.Transport{ Proxy: http.ProxyFromEnvironment, MaxIdleConnsPerHost: 2048, diff --git a/docs/storage/driver/swift/swift_test.go b/docs/storage/driver/swift/swift_test.go index fc66aa268..e0bab62e5 100644 --- a/docs/storage/driver/swift/swift_test.go +++ b/docs/storage/driver/swift/swift_test.go @@ -71,7 +71,7 @@ func init() { defer os.Remove(root) swiftDriverConstructor := func(rootDirectory string) (*Driver, error) { - parameters := DriverParameters{ + parameters := Parameters{ username, password, authURL, From 2524f300dcd381cd6cdedf20b001d690924e1500 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Tue, 30 Jun 2015 14:23:26 +0200 Subject: [PATCH 19/32] Check file has been opened before closing it Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index f91af9081..dd7238c21 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -312,12 +312,12 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea // Offset is inside the current segment : we need to read the // data from the beginning of the segment to offset file, _, err := d.Conn.ObjectOpen(d.Container, getSegment(), false, nil) - defer file.Close() - paddingReader = file - if err != nil { return 0, parseError(getSegment(), err) } + + defer file.Close() + paddingReader = file } readers := []io.Reader{} From 1b28eea2329483f8e381050f2e80e1a50913e1c2 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Tue, 30 Jun 2015 14:24:16 +0200 Subject: [PATCH 20/32] Rename environment variables to run Swift testsuite Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift_test.go | 28 ++++++++++--------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/docs/storage/driver/swift/swift_test.go b/docs/storage/driver/swift/swift_test.go index e0bab62e5..1e04ab24c 100644 --- a/docs/storage/driver/swift/swift_test.go +++ b/docs/storage/driver/swift/swift_test.go @@ -36,23 +36,17 @@ func init() { swiftServer *swifttest.SwiftServer err error ) - if username = os.Getenv("OS_USERNAME"); username == "" { - username = os.Getenv("ST_USER") - } - if password = os.Getenv("OS_PASSWORD"); password == "" { - password = os.Getenv("ST_KEY") - } - if authURL = os.Getenv("OS_AUTH_URL"); authURL == "" { - authURL = os.Getenv("ST_AUTH") - } - tenant = os.Getenv("OS_TENANT_NAME") - tenantID = os.Getenv("OS_TENANT_ID") - domain = os.Getenv("OS_DOMAIN_NAME") - domainID = os.Getenv("OS_DOMAIN_ID") - container = os.Getenv("OS_CONTAINER_NAME") - region = os.Getenv("OS_REGION_NAME") - prefix = os.Getenv("OS_CONTAINER_PREFIX") - insecureSkipVerify, _ = strconv.ParseBool(os.Getenv("ST_INSECURESKIPVERIFY")) + username = os.Getenv("SWIFT_USERNAME") + password = os.Getenv("SWIFT_PASSWORD") + authURL = os.Getenv("SWIFT_AUTH_URL") + tenant = os.Getenv("SWIFT_TENANT_NAME") + tenantID = os.Getenv("SWIFT_TENANT_ID") + domain = os.Getenv("SWIFT_DOMAIN_NAME") + domainID = os.Getenv("SWIFT_DOMAIN_ID") + container = os.Getenv("SWIFT_CONTAINER_NAME") + region = os.Getenv("SWIFT_REGION_NAME") + prefix = os.Getenv("SWIFT_CONTAINER_PREFIX") + insecureSkipVerify, _ = strconv.ParseBool(os.Getenv("SWIFT_INSECURESKIPVERIFY")) if username == "" || password == "" || authURL == "" || container == "" { if swiftServer, err = swifttest.NewSwiftServer("localhost"); err != nil { From 913fe195fd496e91b5167e19eb33c965d2171493 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Tue, 30 Jun 2015 23:09:02 +0200 Subject: [PATCH 21/32] Do not use suite style testing for Swift specific tests Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift_test.go | 81 +++++++++++-------------- 1 file changed, 34 insertions(+), 47 deletions(-) diff --git a/docs/storage/driver/swift/swift_test.go b/docs/storage/driver/swift/swift_test.go index 1e04ab24c..726b5666a 100644 --- a/docs/storage/driver/swift/swift_test.go +++ b/docs/storage/driver/swift/swift_test.go @@ -18,7 +18,7 @@ import ( // Hook up gocheck into the "go test" runner. func Test(t *testing.T) { check.TestingT(t) } -type SwiftDriverConstructor func(rootDirectory string) (*Driver, error) +var swiftDriverConstructor func(prefix string) (*Driver, error) func init() { var ( @@ -31,7 +31,6 @@ func init() { domainID string container string region string - prefix string insecureSkipVerify bool swiftServer *swifttest.SwiftServer err error @@ -45,7 +44,6 @@ func init() { domainID = os.Getenv("SWIFT_DOMAIN_ID") container = os.Getenv("SWIFT_CONTAINER_NAME") region = os.Getenv("SWIFT_REGION_NAME") - prefix = os.Getenv("SWIFT_CONTAINER_PREFIX") insecureSkipVerify, _ = strconv.ParseBool(os.Getenv("SWIFT_INSECURESKIPVERIFY")) if username == "" || password == "" || authURL == "" || container == "" { @@ -58,13 +56,13 @@ func init() { container = "test" } - root, err := ioutil.TempDir("", "driver-") + prefix, err := ioutil.TempDir("", "driver-") if err != nil { panic(err) } - defer os.Remove(root) + defer os.Remove(prefix) - swiftDriverConstructor := func(rootDirectory string) (*Driver, error) { + swiftDriverConstructor = func(root string) (*Driver, error) { parameters := Parameters{ username, password, @@ -75,7 +73,7 @@ func init() { domainID, region, container, - prefix, + root, insecureSkipVerify, defaultChunkSize, } @@ -83,66 +81,55 @@ func init() { return New(parameters) } - skipCheck := func() string { - return "" - } - driverConstructor := func() (storagedriver.StorageDriver, error) { - return swiftDriverConstructor(root) + return swiftDriverConstructor(prefix) } - testsuites.RegisterInProcessSuite(driverConstructor, skipCheck) - - RegisterSwiftDriverSuite(swiftDriverConstructor, skipCheck, swiftServer) + testsuites.RegisterInProcessSuite(driverConstructor, testsuites.NeverSkip) } -func RegisterSwiftDriverSuite(swiftDriverConstructor SwiftDriverConstructor, skipCheck testsuites.SkipCheck, - swiftServer *swifttest.SwiftServer) { - check.Suite(&SwiftDriverSuite{ - Constructor: swiftDriverConstructor, - SkipCheck: skipCheck, - SwiftServer: swiftServer, - }) -} - -type SwiftDriverSuite struct { - Constructor SwiftDriverConstructor - SwiftServer *swifttest.SwiftServer - testsuites.SkipCheck -} - -func (suite *SwiftDriverSuite) SetUpSuite(c *check.C) { - if reason := suite.SkipCheck(); reason != "" { - c.Skip(reason) - } -} - -func (suite *SwiftDriverSuite) TestEmptyRootList(c *check.C) { +func TestEmptyRootList(t *testing.T) { validRoot, err := ioutil.TempDir("", "driver-") - c.Assert(err, check.IsNil) + if err != nil { + t.Fatalf("unexpected error creating temporary directory: %v", err) + } defer os.Remove(validRoot) - rootedDriver, err := suite.Constructor(validRoot) - c.Assert(err, check.IsNil) - emptyRootDriver, err := suite.Constructor("") - c.Assert(err, check.IsNil) - slashRootDriver, err := suite.Constructor("/") - c.Assert(err, check.IsNil) + rootedDriver, err := swiftDriverConstructor(validRoot) + if err != nil { + t.Fatalf("unexpected error creating rooted driver: %v", err) + } + + emptyRootDriver, err := swiftDriverConstructor("") + if err != nil { + t.Fatalf("unexpected error creating empty root driver: %v", err) + } + + slashRootDriver, err := swiftDriverConstructor("/") + if err != nil { + t.Fatalf("unexpected error creating slash root driver: %v", err) + } filename := "/test" contents := []byte("contents") ctx := context.Background() err = rootedDriver.PutContent(ctx, filename, contents) - c.Assert(err, check.IsNil) + if err != nil { + t.Fatalf("unexpected error creating content: %v", err) + } defer rootedDriver.Delete(ctx, filename) keys, err := emptyRootDriver.List(ctx, "/") for _, path := range keys { - c.Assert(storagedriver.PathRegexp.MatchString(path), check.Equals, true) + if !storagedriver.PathRegexp.MatchString(path) { + t.Fatalf("unexpected string in path: %q != %q", path, storagedriver.PathRegexp) + } } keys, err = slashRootDriver.List(ctx, "/") for _, path := range keys { - c.Assert(storagedriver.PathRegexp.MatchString(path), check.Equals, true) + if !storagedriver.PathRegexp.MatchString(path) { + t.Fatalf("unexpected string in path: %q != %q", path, storagedriver.PathRegexp) + } } } From 01686e2c0754f039e42302251f2e5eff7a51e3e9 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Tue, 30 Jun 2015 23:39:04 +0200 Subject: [PATCH 22/32] Show distribution version in User-Agent Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index dd7238c21..c51cc31a2 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -39,6 +39,7 @@ import ( storagedriver "github.com/docker/distribution/registry/storage/driver" "github.com/docker/distribution/registry/storage/driver/base" "github.com/docker/distribution/registry/storage/driver/factory" + "github.com/docker/distribution/version" ) const driverName = "swift" @@ -151,7 +152,7 @@ func New(params Parameters) (*Driver, error) { ApiKey: params.Password, AuthUrl: params.AuthURL, Region: params.Region, - UserAgent: "distribution", + UserAgent: "distribution/" + version.Version, Tenant: params.Tenant, TenantId: params.TenantID, Domain: params.Domain, From 91d74a3ee2cafdd0117da4a12a3420309b66bc15 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Thu, 2 Jul 2015 10:59:14 +0200 Subject: [PATCH 23/32] Protect against deletion of objects with the same prefix Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 34 +++++++++++++++++++----------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index c51cc31a2..9570244a7 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -447,32 +447,36 @@ func (d *driver) Move(ctx context.Context, sourcePath string, destPath string) e // Delete recursively deletes all objects stored at "path" and its subpaths. func (d *driver) Delete(ctx context.Context, path string) error { opts := swift.ObjectsOpts{ - Prefix: d.swiftPath(path), + Prefix: d.swiftPath(path) + "/", } - objects, err := d.Conn.ObjectNamesAll(d.Container, &opts) + objects, err := d.Conn.Objects(d.Container, &opts) if err != nil { return parseError(path, err) } - if len(objects) == 0 { - return storagedriver.PathNotFoundError{Path: path} - } if d.BulkDeleteSupport { - if _, err := d.Conn.BulkDelete(d.Container, objects); err != swift.Forbidden { + filenames := make([]string, len(objects)) + for i, obj := range objects { + filenames[i] = obj.Name + } + if _, err := d.Conn.BulkDelete(d.Container, filenames); err != swift.Forbidden { return parseError(path, err) } } - for _, name := range objects { - if _, headers, err := d.Conn.Object(d.Container, name); err == nil { + for _, obj := range objects { + if obj.PseudoDirectory { + continue + } + if _, headers, err := d.Conn.Object(d.Container, obj.Name); err == nil { manifest, ok := headers["X-Object-Manifest"] if ok { components := strings.SplitN(manifest, "/", 2) segContainer := components[0] segments, err := d.getAllSegments(components[1]) if err != nil { - return parseError(name, err) + return parseError(obj.Name, err) } for _, s := range segments { @@ -482,14 +486,20 @@ func (d *driver) Delete(ctx context.Context, path string) error { } } } else { - return parseError(name, err) + return parseError(obj.Name, err) } - if err := d.Conn.ObjectDelete(d.Container, name); err != nil { - return parseError(name, err) + if err := d.Conn.ObjectDelete(d.Container, obj.Name); err != nil { + return parseError(obj.Name, err) } } + if _, err := d.Stat(ctx, path); err == nil { + return parseError(path, d.Conn.ObjectDelete(d.Container, d.swiftPath(path))) + } else if len(objects) == 0 { + return storagedriver.PathNotFoundError{Path: path} + } + return nil } From 7a5aa32a64abf390f92c7dd684c514664f0d9268 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Thu, 2 Jul 2015 11:02:47 +0200 Subject: [PATCH 24/32] Use file instead of filepath as it may cause troubles on Windows Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index 9570244a7..300090573 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -27,7 +27,6 @@ import ( "io/ioutil" "net/http" gopath "path" - "path/filepath" "strconv" "strings" "time" @@ -550,7 +549,7 @@ func (d *driver) createManifest(path string) (*swift.ObjectCreateFile, error) { } func detectBulkDelete(authURL string) (bulkDelete bool) { - resp, err := http.Get(filepath.Join(authURL, "..", "..") + "/info") + resp, err := http.Get(gopath.Join(authURL, "..", "..") + "/info") if err == nil { defer resp.Body.Close() decoder := json.NewDecoder(resp.Body) From 0807282859290e813d08c82f37ea4f0d8e100268 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Thu, 2 Jul 2015 11:04:10 +0200 Subject: [PATCH 25/32] Use http.StatusRequestedRangeNotSatisfiable instead of error code Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index 300090573..2f2e0c60a 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -220,7 +220,7 @@ func (d *driver) ReadStream(ctx context.Context, path string, offset int64) (io. file, _, err := d.Conn.ObjectOpen(d.Container, d.swiftPath(path), false, headers) if err != nil { - if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == 416 { + if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == http.StatusRequestedRangeNotSatisfiable { return ioutil.NopCloser(bytes.NewReader(nil)), nil } From f190aa4a7c7f1f922cc6239215d8cb5255beddcf Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Thu, 2 Jul 2015 18:23:34 +0200 Subject: [PATCH 26/32] Refactor segment path concatenation code Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index 2f2e0c60a..b5f4fcd2f 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -251,7 +251,7 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea zeroBuf := make([]byte, d.ChunkSize) getSegment := func() string { - return d.swiftSegmentPath(path) + "/" + fmt.Sprintf("%016d", partNumber) + return fmt.Sprintf("%s/%016d", d.swiftSegmentPath(path), partNumber) } max := func(a int64, b int64) int64 { From 704e08225447affb2b60c2c52d98657c0a72d5fc Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Thu, 2 Jul 2015 18:25:27 +0200 Subject: [PATCH 27/32] Do not create objects for directories Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 65 +++++++++++++----------------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index b5f4fcd2f..08c79c89e 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -49,9 +49,6 @@ const defaultChunkSize = 20 * 1024 * 1024 // minChunkSize defines the minimum size of a segment const minChunkSize = 1 << 20 -// Vendor MIME type used for objects that act as directories -const directoryMimeType = "application/vnd.swift.directory" - // Parameters A struct that encapsulates all of the driver parameters after all values have been set type Parameters struct { Username string @@ -203,9 +200,6 @@ func (d *driver) GetContent(ctx context.Context, path string) ([]byte, error) { // PutContent stores the []byte content at a location designated by "path". func (d *driver) PutContent(ctx context.Context, path string, contents []byte) error { - if err := d.createParentFolders(path); err != nil { - return err - } err := d.Conn.ObjectPutBytes(d.Container, d.swiftPath(path), contents, d.getContentType()) return parseError(path, err) @@ -265,9 +259,6 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea if err != nil { if err == swift.ContainerNotFound || err == swift.ObjectNotFound { // Create a object manifest - if err := d.createParentFolders(path); err != nil { - return 0, err - } manifest, err := d.createManifest(path) if err != nil { return 0, parseError(path, err) @@ -392,19 +383,40 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea // Stat retrieves the FileInfo for the given path, including the current size // in bytes and the creation time. func (d *driver) Stat(ctx context.Context, path string) (storagedriver.FileInfo, error) { - info, _, err := d.Conn.Object(d.Container, d.swiftPath(path)) + swiftPath := d.swiftPath(path) + opts := &swift.ObjectsOpts{ + Prefix: swiftPath, + Delimiter: '/', + } + + objects, err := d.Conn.ObjectsAll(d.Container, opts) if err != nil { - return nil, parseError(path, err) + return nil, err } fi := storagedriver.FileInfoFields{ - Path: path, - IsDir: info.ContentType == directoryMimeType, - Size: info.Bytes, - ModTime: info.LastModified, + Path: strings.TrimPrefix(strings.TrimSuffix(swiftPath, "/"), d.swiftPath("/")), } - return storagedriver.FileInfoInternal{FileInfoFields: fi}, nil + for _, obj := range objects { + if obj.PseudoDirectory && obj.Name == swiftPath+"/" { + fi.IsDir = true + return storagedriver.FileInfoInternal{FileInfoFields: fi}, nil + } else if obj.Name == swiftPath { + // On Swift 1.12, the 'bytes' field is always 0 + // so we need to do a second HEAD request + info, _, err := d.Conn.Object(d.Container, swiftPath) + if err != nil { + return nil, parseError(path, err) + } + fi.IsDir = false + fi.Size = info.Bytes + fi.ModTime = info.LastModified + return storagedriver.FileInfoInternal{FileInfoFields: fi}, nil + } + } + + return nil, storagedriver.PathNotFoundError{Path: path} } // List returns a list of the objects that are direct descendants of the given path. @@ -423,9 +435,7 @@ func (d *driver) List(ctx context.Context, path string) ([]string, error) { objects, err := d.Conn.Objects(d.Container, opts) for _, obj := range objects { - if !obj.PseudoDirectory { - files = append(files, strings.TrimPrefix(strings.TrimSuffix(obj.Name, "/"), d.swiftPath("/"))) - } + files = append(files, strings.TrimPrefix(strings.TrimSuffix(obj.Name, "/"), d.swiftPath("/"))) } return files, parseError(path, err) @@ -516,23 +526,6 @@ func (d *driver) swiftSegmentPath(path string) string { return strings.TrimLeft(strings.TrimRight(d.Prefix+"/segments"+path, "/"), "/") } -func (d *driver) createParentFolders(path string) error { - dir := gopath.Dir(path) - for dir != "/" { - _, _, err := d.Conn.Object(d.Container, d.swiftPath(dir)) - if err == swift.ContainerNotFound || err == swift.ObjectNotFound { - _, err := d.Conn.ObjectPut(d.Container, d.swiftPath(dir), bytes.NewReader(make([]byte, 0)), - false, "", directoryMimeType, nil) - if err != nil { - return parseError(dir, err) - } - } - dir = gopath.Dir(dir) - } - - return nil -} - func (d *driver) getContentType() string { return "application/octet-stream" } From 661f197f68ab05d292f4cf1bf13f2c96b778cab6 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Thu, 2 Jul 2015 18:27:13 +0200 Subject: [PATCH 28/32] Retrieve all the objects using pagination Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index 08c79c89e..4daf7cccb 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -433,7 +433,7 @@ func (d *driver) List(ctx context.Context, path string) ([]string, error) { Delimiter: '/', } - objects, err := d.Conn.Objects(d.Container, opts) + objects, err := d.Conn.ObjectsAll(d.Container, opts) for _, obj := range objects { files = append(files, strings.TrimPrefix(strings.TrimSuffix(obj.Name, "/"), d.swiftPath("/"))) } @@ -459,7 +459,7 @@ func (d *driver) Delete(ctx context.Context, path string) error { Prefix: d.swiftPath(path) + "/", } - objects, err := d.Conn.Objects(d.Container, &opts) + objects, err := d.Conn.ObjectsAll(d.Container, &opts) if err != nil { return parseError(path, err) } @@ -531,7 +531,7 @@ func (d *driver) getContentType() string { } func (d *driver) getAllSegments(path string) ([]swift.Object, error) { - return d.Conn.Objects(d.Container, &swift.ObjectsOpts{Prefix: d.swiftSegmentPath(path)}) + return d.Conn.ObjectsAll(d.Container, &swift.ObjectsOpts{Prefix: d.swiftSegmentPath(path)}) } func (d *driver) createManifest(path string) (*swift.ObjectCreateFile, error) { From 000dec3c6f6e92ec20cb86d1375ec82d2f6062b3 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Fri, 3 Jul 2015 12:29:54 +0200 Subject: [PATCH 29/32] Inline Swift errors handling Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 161 ++++++++++++++++++----------- 1 file changed, 102 insertions(+), 59 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index 4daf7cccb..e3c739828 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -192,17 +192,19 @@ func (d *driver) Name() string { // GetContent retrieves the content stored at "path" as a []byte. func (d *driver) GetContent(ctx context.Context, path string) ([]byte, error) { content, err := d.Conn.ObjectGetBytes(d.Container, d.swiftPath(path)) - if err != nil { - return nil, parseError(path, err) + if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + return nil, storagedriver.PathNotFoundError{Path: path} } return content, nil } // PutContent stores the []byte content at a location designated by "path". func (d *driver) PutContent(ctx context.Context, path string, contents []byte) error { - err := d.Conn.ObjectPutBytes(d.Container, d.swiftPath(path), - contents, d.getContentType()) - return parseError(path, err) + err := d.Conn.ObjectPutBytes(d.Container, d.swiftPath(path), contents, d.getContentType()) + if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + return storagedriver.PathNotFoundError{Path: path} + } + return err } // ReadStream retrieves an io.ReadCloser for the content stored at "path" with a @@ -212,16 +214,13 @@ func (d *driver) ReadStream(ctx context.Context, path string, offset int64) (io. headers["Range"] = "bytes=" + strconv.FormatInt(offset, 10) + "-" file, _, err := d.Conn.ObjectOpen(d.Container, d.swiftPath(path), false, headers) - - if err != nil { - if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == http.StatusRequestedRangeNotSatisfiable { - return ioutil.NopCloser(bytes.NewReader(nil)), nil - } - - return nil, parseError(path, err) + if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + return nil, storagedriver.PathNotFoundError{Path: path} } - - return file, nil + if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == http.StatusRequestedRangeNotSatisfiable { + return ioutil.NopCloser(bytes.NewReader(nil)), nil + } + return file, err } // WriteStream stores the contents of the provided io.Reader at a @@ -257,22 +256,23 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea info, _, err := d.Conn.Object(d.Container, d.swiftPath(path)) if err != nil { - if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + if err == swift.ObjectNotFound { // Create a object manifest manifest, err := d.createManifest(path) if err != nil { - return 0, parseError(path, err) + return 0, err } manifest.Close() + } else if err == swift.ContainerNotFound { + return 0, storagedriver.PathNotFoundError{Path: path} } else { - return 0, parseError(path, err) + return 0, err } } else { // The manifest already exists. Get all the segments currentLength = info.Bytes - segments, err = d.getAllSegments(path) - if err != nil { - return 0, parseError(path, err) + if segments, err = d.getAllSegments(path); err != nil { + return 0, err } } @@ -290,9 +290,13 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea if offset >= currentLength { for offset-currentLength >= chunkSize { // Insert a block a zero - d.Conn.ObjectPut(d.Container, getSegment(), - bytes.NewReader(zeroBuf), false, "", - d.getContentType(), nil) + _, err := d.Conn.ObjectPut(d.Container, getSegment(), bytes.NewReader(zeroBuf), false, "", d.getContentType(), nil) + if err != nil { + if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + return 0, storagedriver.PathNotFoundError{Path: getSegment()} + } + return 0, err + } currentLength += chunkSize partNumber++ } @@ -304,9 +308,11 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea // data from the beginning of the segment to offset file, _, err := d.Conn.ObjectOpen(d.Container, getSegment(), false, nil) if err != nil { - return 0, parseError(getSegment(), err) + if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + return 0, storagedriver.PathNotFoundError{Path: getSegment()} + } + return 0, err } - defer file.Close() paddingReader = file } @@ -321,12 +327,15 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea writeSegment := func(segment string) (finished bool, bytesRead int64, err error) { currentSegment, err := d.Conn.ObjectCreate(d.Container, segment, false, "", d.getContentType(), nil) if err != nil { - return false, bytesRead, parseError(path, err) + if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + return false, bytesRead, storagedriver.PathNotFoundError{Path: segment} + } + return false, bytesRead, err } n, err := io.Copy(currentSegment, multi) if err != nil { - return false, bytesRead, parseError(path, err) + return false, bytesRead, err } if n > 0 { @@ -342,17 +351,23 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea headers["Range"] = "bytes=" + strconv.FormatInt(cursor+n, 10) + "-" + strconv.FormatInt(cursor+chunkSize, 10) file, _, err := d.Conn.ObjectOpen(d.Container, d.swiftPath(path), false, headers) if err != nil { - return false, bytesRead, parseError(path, err) + if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + return false, bytesRead, storagedriver.PathNotFoundError{Path: path} + } + return false, bytesRead, err } _, copyErr := io.Copy(currentSegment, file) if err := file.Close(); err != nil { - return false, bytesRead, parseError(path, err) + if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + return false, bytesRead, storagedriver.PathNotFoundError{Path: path} + } + return false, bytesRead, err } if copyErr != nil { - return false, bytesRead, parseError(path, copyErr) + return false, bytesRead, copyErr } } @@ -391,6 +406,9 @@ func (d *driver) Stat(ctx context.Context, path string) (storagedriver.FileInfo, objects, err := d.Conn.ObjectsAll(d.Container, opts) if err != nil { + if err == swift.ContainerNotFound { + return nil, storagedriver.PathNotFoundError{Path: path} + } return nil, err } @@ -407,7 +425,10 @@ func (d *driver) Stat(ctx context.Context, path string) (storagedriver.FileInfo, // so we need to do a second HEAD request info, _, err := d.Conn.Object(d.Container, swiftPath) if err != nil { - return nil, parseError(path, err) + if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + return nil, storagedriver.PathNotFoundError{Path: path} + } + return nil, err } fi.IsDir = false fi.Size = info.Bytes @@ -438,19 +459,20 @@ func (d *driver) List(ctx context.Context, path string) ([]string, error) { files = append(files, strings.TrimPrefix(strings.TrimSuffix(obj.Name, "/"), d.swiftPath("/"))) } - return files, parseError(path, err) + if err == swift.ContainerNotFound { + return files, storagedriver.PathNotFoundError{Path: path} + } + return files, err } // Move moves an object stored at sourcePath to destPath, removing the original // object. func (d *driver) Move(ctx context.Context, sourcePath string, destPath string) error { - err := d.Conn.ObjectMove(d.Container, d.swiftPath(sourcePath), - d.Container, d.swiftPath(destPath)) - if err != nil { - return parseError(sourcePath, err) + err := d.Conn.ObjectMove(d.Container, d.swiftPath(sourcePath), d.Container, d.swiftPath(destPath)) + if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + return storagedriver.PathNotFoundError{Path: sourcePath} } - - return nil + return err } // Delete recursively deletes all objects stored at "path" and its subpaths. @@ -461,7 +483,10 @@ func (d *driver) Delete(ctx context.Context, path string) error { objects, err := d.Conn.ObjectsAll(d.Container, &opts) if err != nil { - return parseError(path, err) + if err == swift.ContainerNotFound { + return storagedriver.PathNotFoundError{Path: path} + } + return err } if d.BulkDeleteSupport { @@ -470,7 +495,10 @@ func (d *driver) Delete(ctx context.Context, path string) error { filenames[i] = obj.Name } if _, err := d.Conn.BulkDelete(d.Container, filenames); err != swift.Forbidden { - return parseError(path, err) + if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + return storagedriver.PathNotFoundError{Path: path} + } + return err } } @@ -485,30 +513,46 @@ func (d *driver) Delete(ctx context.Context, path string) error { segContainer := components[0] segments, err := d.getAllSegments(components[1]) if err != nil { - return parseError(obj.Name, err) + return err } for _, s := range segments { if err := d.Conn.ObjectDelete(segContainer, s.Name); err != nil { - return parseError(s.Name, err) + if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + return storagedriver.PathNotFoundError{Path: s.Name} + } + return err } } } } else { - return parseError(obj.Name, err) + if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + return storagedriver.PathNotFoundError{Path: obj.Name} + } + return err } if err := d.Conn.ObjectDelete(d.Container, obj.Name); err != nil { - return parseError(obj.Name, err) + if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + return storagedriver.PathNotFoundError{Path: obj.Name} + } + return err } } - if _, err := d.Stat(ctx, path); err == nil { - return parseError(path, d.Conn.ObjectDelete(d.Container, d.swiftPath(path))) - } else if len(objects) == 0 { + _, _, err = d.Conn.Object(d.Container, d.swiftPath(path)) + if err == nil { + if err := d.Conn.ObjectDelete(d.Container, d.swiftPath(path)); err != nil { + if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + return storagedriver.PathNotFoundError{Path: path} + } + return err + } + } else if err == swift.ObjectNotFound && len(objects) == 0 { + return storagedriver.PathNotFoundError{Path: path} + } else if err == swift.ContainerNotFound { return storagedriver.PathNotFoundError{Path: path} } - return nil } @@ -531,14 +575,21 @@ func (d *driver) getContentType() string { } func (d *driver) getAllSegments(path string) ([]swift.Object, error) { - return d.Conn.ObjectsAll(d.Container, &swift.ObjectsOpts{Prefix: d.swiftSegmentPath(path)}) + segments, err := d.Conn.ObjectsAll(d.Container, &swift.ObjectsOpts{Prefix: d.swiftSegmentPath(path)}) + if err == swift.ContainerNotFound { + return nil, storagedriver.PathNotFoundError{Path: path} + } + return segments, err } func (d *driver) createManifest(path string) (*swift.ObjectCreateFile, error) { headers := make(swift.Headers) headers["X-Object-Manifest"] = d.Container + "/" + d.swiftSegmentPath(path) - return d.Conn.ObjectCreate(d.Container, d.swiftPath(path), false, "", - d.getContentType(), headers) + file, err := d.Conn.ObjectCreate(d.Container, d.swiftPath(path), false, "", d.getContentType(), headers) + if err == swift.ContainerNotFound { + return nil, storagedriver.PathNotFoundError{Path: path} + } + return file, err } func detectBulkDelete(authURL string) (bulkDelete bool) { @@ -553,11 +604,3 @@ func detectBulkDelete(authURL string) (bulkDelete bool) { } return } - -func parseError(path string, err error) error { - if err == swift.ContainerNotFound || err == swift.ObjectNotFound { - return storagedriver.PathNotFoundError{Path: path} - } - - return err -} From 52d28ec81a9e826ada36069e6709beb4db64b563 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Wed, 8 Jul 2015 12:59:29 +0200 Subject: [PATCH 30/32] Do not use Swift server side copy for manifests to handle >5G files Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 103 +++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 27 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index e3c739828..ce5df88d5 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -20,7 +20,10 @@ package swift import ( "bytes" + "crypto/rand" + "crypto/sha1" "crypto/tls" + "encoding/hex" "encoding/json" "fmt" "io" @@ -237,6 +240,7 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea paddingReader io.Reader currentLength int64 cursor int64 + segmentPath string ) partNumber := 1 @@ -244,7 +248,7 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea zeroBuf := make([]byte, d.ChunkSize) getSegment := func() string { - return fmt.Sprintf("%s/%016d", d.swiftSegmentPath(path), partNumber) + return fmt.Sprintf("%s/%016d", segmentPath, partNumber) } max := func(a int64, b int64) int64 { @@ -254,24 +258,36 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea return b } - info, _, err := d.Conn.Object(d.Container, d.swiftPath(path)) - if err != nil { - if err == swift.ObjectNotFound { - // Create a object manifest - manifest, err := d.createManifest(path) - if err != nil { + createManifest := true + info, headers, err := d.Conn.Object(d.Container, d.swiftPath(path)) + if err == nil { + manifest, ok := headers["X-Object-Manifest"] + if !ok { + if segmentPath, err = d.swiftSegmentPath(path); err != nil { return 0, err } - manifest.Close() - } else if err == swift.ContainerNotFound { - return 0, storagedriver.PathNotFoundError{Path: path} + if err := d.Conn.ObjectMove(d.Container, d.swiftPath(path), d.Container, getSegment()); err != nil { + return 0, err + } + segments = append(segments, info) } else { + _, segmentPath = parseManifest(manifest) + if segments, err = d.getAllSegments(segmentPath); err != nil { + return 0, err + } + createManifest = false + } + currentLength = info.Bytes + } else if err == swift.ObjectNotFound { + if segmentPath, err = d.swiftSegmentPath(path); err != nil { return 0, err } } else { - // The manifest already exists. Get all the segments - currentLength = info.Bytes - if segments, err = d.getAllSegments(path); err != nil { + return 0, err + } + + if createManifest { + if err := d.createManifest(path, d.Container+"/"+segmentPath); err != nil { return 0, err } } @@ -468,8 +484,18 @@ func (d *driver) List(ctx context.Context, path string) ([]string, error) { // Move moves an object stored at sourcePath to destPath, removing the original // object. func (d *driver) Move(ctx context.Context, sourcePath string, destPath string) error { - err := d.Conn.ObjectMove(d.Container, d.swiftPath(sourcePath), d.Container, d.swiftPath(destPath)) - if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + _, headers, err := d.Conn.Object(d.Container, d.swiftPath(sourcePath)) + if err == nil { + if manifest, ok := headers["X-Object-Manifest"]; ok { + if err = d.createManifest(destPath, manifest); err != nil { + return err + } + err = d.Conn.ObjectDelete(d.Container, d.swiftPath(sourcePath)) + } else { + err = d.Conn.ObjectMove(d.Container, d.swiftPath(sourcePath), d.Container, d.swiftPath(destPath)) + } + } + if err == swift.ObjectNotFound { return storagedriver.PathNotFoundError{Path: sourcePath} } return err @@ -509,9 +535,8 @@ func (d *driver) Delete(ctx context.Context, path string) error { if _, headers, err := d.Conn.Object(d.Container, obj.Name); err == nil { manifest, ok := headers["X-Object-Manifest"] if ok { - components := strings.SplitN(manifest, "/", 2) - segContainer := components[0] - segments, err := d.getAllSegments(components[1]) + segContainer, prefix := parseManifest(manifest) + segments, err := d.getAllSegments(prefix) if err != nil { return err } @@ -566,8 +591,14 @@ func (d *driver) swiftPath(path string) string { return strings.TrimLeft(strings.TrimRight(d.Prefix+"/files"+path, "/"), "/") } -func (d *driver) swiftSegmentPath(path string) string { - return strings.TrimLeft(strings.TrimRight(d.Prefix+"/segments"+path, "/"), "/") +func (d *driver) swiftSegmentPath(path string) (string, error) { + checksum := sha1.New() + random := make([]byte, 32) + if _, err := rand.Read(random); err != nil { + return "", err + } + path = hex.EncodeToString(checksum.Sum(append([]byte(path), random...))) + return strings.TrimLeft(strings.TrimRight(d.Prefix+"/segments/"+path[0:3]+"/"+path[3:], "/"), "/"), nil } func (d *driver) getContentType() string { @@ -575,21 +606,30 @@ func (d *driver) getContentType() string { } func (d *driver) getAllSegments(path string) ([]swift.Object, error) { - segments, err := d.Conn.ObjectsAll(d.Container, &swift.ObjectsOpts{Prefix: d.swiftSegmentPath(path)}) + segments, err := d.Conn.ObjectsAll(d.Container, &swift.ObjectsOpts{Prefix: path}) if err == swift.ContainerNotFound { return nil, storagedriver.PathNotFoundError{Path: path} } return segments, err } -func (d *driver) createManifest(path string) (*swift.ObjectCreateFile, error) { +func (d *driver) createManifest(path string, segments string) error { headers := make(swift.Headers) - headers["X-Object-Manifest"] = d.Container + "/" + d.swiftSegmentPath(path) - file, err := d.Conn.ObjectCreate(d.Container, d.swiftPath(path), false, "", d.getContentType(), headers) - if err == swift.ContainerNotFound { - return nil, storagedriver.PathNotFoundError{Path: path} + headers["X-Object-Manifest"] = segments + manifest, err := d.Conn.ObjectCreate(d.Container, d.swiftPath(path), false, "", d.getContentType(), headers) + if err != nil { + if err == swift.ObjectNotFound { + return storagedriver.PathNotFoundError{Path: path} + } + return err } - return file, err + if err := manifest.Close(); err != nil { + if err == swift.ObjectNotFound { + return storagedriver.PathNotFoundError{Path: path} + } + return err + } + return nil } func detectBulkDelete(authURL string) (bulkDelete bool) { @@ -604,3 +644,12 @@ func detectBulkDelete(authURL string) (bulkDelete bool) { } return } + +func parseManifest(manifest string) (container string, prefix string) { + components := strings.SplitN(manifest, "/", 2) + container = components[0] + if len(components) > 1 { + prefix = components[1] + } + return container, prefix +} From 81765f8cbb7b5d426b1444937ff22c672a87b217 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Wed, 8 Jul 2015 13:01:34 +0200 Subject: [PATCH 31/32] Catch either missing containers or objects Signed-off-by: Sylvain Baubeau --- docs/storage/driver/swift/swift.go | 38 ++++++++++++++++-------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/docs/storage/driver/swift/swift.go b/docs/storage/driver/swift/swift.go index ce5df88d5..0921ccc03 100644 --- a/docs/storage/driver/swift/swift.go +++ b/docs/storage/driver/swift/swift.go @@ -195,7 +195,7 @@ func (d *driver) Name() string { // GetContent retrieves the content stored at "path" as a []byte. func (d *driver) GetContent(ctx context.Context, path string) ([]byte, error) { content, err := d.Conn.ObjectGetBytes(d.Container, d.swiftPath(path)) - if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + if err == swift.ObjectNotFound { return nil, storagedriver.PathNotFoundError{Path: path} } return content, nil @@ -204,7 +204,7 @@ func (d *driver) GetContent(ctx context.Context, path string) ([]byte, error) { // PutContent stores the []byte content at a location designated by "path". func (d *driver) PutContent(ctx context.Context, path string, contents []byte) error { err := d.Conn.ObjectPutBytes(d.Container, d.swiftPath(path), contents, d.getContentType()) - if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + if err == swift.ObjectNotFound { return storagedriver.PathNotFoundError{Path: path} } return err @@ -217,7 +217,7 @@ func (d *driver) ReadStream(ctx context.Context, path string, offset int64) (io. headers["Range"] = "bytes=" + strconv.FormatInt(offset, 10) + "-" file, _, err := d.Conn.ObjectOpen(d.Container, d.swiftPath(path), false, headers) - if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + if err == swift.ObjectNotFound { return nil, storagedriver.PathNotFoundError{Path: path} } if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == http.StatusRequestedRangeNotSatisfiable { @@ -308,7 +308,7 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea // Insert a block a zero _, err := d.Conn.ObjectPut(d.Container, getSegment(), bytes.NewReader(zeroBuf), false, "", d.getContentType(), nil) if err != nil { - if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + if err == swift.ObjectNotFound { return 0, storagedriver.PathNotFoundError{Path: getSegment()} } return 0, err @@ -324,7 +324,7 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea // data from the beginning of the segment to offset file, _, err := d.Conn.ObjectOpen(d.Container, getSegment(), false, nil) if err != nil { - if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + if err == swift.ObjectNotFound { return 0, storagedriver.PathNotFoundError{Path: getSegment()} } return 0, err @@ -343,7 +343,7 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea writeSegment := func(segment string) (finished bool, bytesRead int64, err error) { currentSegment, err := d.Conn.ObjectCreate(d.Container, segment, false, "", d.getContentType(), nil) if err != nil { - if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + if err == swift.ObjectNotFound { return false, bytesRead, storagedriver.PathNotFoundError{Path: segment} } return false, bytesRead, err @@ -367,7 +367,7 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea headers["Range"] = "bytes=" + strconv.FormatInt(cursor+n, 10) + "-" + strconv.FormatInt(cursor+chunkSize, 10) file, _, err := d.Conn.ObjectOpen(d.Container, d.swiftPath(path), false, headers) if err != nil { - if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + if err == swift.ObjectNotFound { return false, bytesRead, storagedriver.PathNotFoundError{Path: path} } return false, bytesRead, err @@ -376,7 +376,7 @@ func (d *driver) WriteStream(ctx context.Context, path string, offset int64, rea _, copyErr := io.Copy(currentSegment, file) if err := file.Close(); err != nil { - if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + if err == swift.ObjectNotFound { return false, bytesRead, storagedriver.PathNotFoundError{Path: path} } return false, bytesRead, err @@ -441,7 +441,7 @@ func (d *driver) Stat(ctx context.Context, path string) (storagedriver.FileInfo, // so we need to do a second HEAD request info, _, err := d.Conn.Object(d.Container, swiftPath) if err != nil { - if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + if err == swift.ObjectNotFound { return nil, storagedriver.PathNotFoundError{Path: path} } return nil, err @@ -521,7 +521,7 @@ func (d *driver) Delete(ctx context.Context, path string) error { filenames[i] = obj.Name } if _, err := d.Conn.BulkDelete(d.Container, filenames); err != swift.Forbidden { - if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + if err == swift.ContainerNotFound { return storagedriver.PathNotFoundError{Path: path} } return err @@ -543,7 +543,7 @@ func (d *driver) Delete(ctx context.Context, path string) error { for _, s := range segments { if err := d.Conn.ObjectDelete(segContainer, s.Name); err != nil { - if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + if err == swift.ObjectNotFound { return storagedriver.PathNotFoundError{Path: s.Name} } return err @@ -551,14 +551,14 @@ func (d *driver) Delete(ctx context.Context, path string) error { } } } else { - if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + if err == swift.ObjectNotFound { return storagedriver.PathNotFoundError{Path: obj.Name} } return err } if err := d.Conn.ObjectDelete(d.Container, obj.Name); err != nil { - if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + if err == swift.ObjectNotFound { return storagedriver.PathNotFoundError{Path: obj.Name} } return err @@ -568,15 +568,17 @@ func (d *driver) Delete(ctx context.Context, path string) error { _, _, err = d.Conn.Object(d.Container, d.swiftPath(path)) if err == nil { if err := d.Conn.ObjectDelete(d.Container, d.swiftPath(path)); err != nil { - if err == swift.ContainerNotFound || err == swift.ObjectNotFound { + if err == swift.ObjectNotFound { return storagedriver.PathNotFoundError{Path: path} } return err } - } else if err == swift.ObjectNotFound && len(objects) == 0 { - return storagedriver.PathNotFoundError{Path: path} - } else if err == swift.ContainerNotFound { - return storagedriver.PathNotFoundError{Path: path} + } else if err == swift.ObjectNotFound { + if len(objects) == 0 { + return storagedriver.PathNotFoundError{Path: path} + } + } else { + return err } return nil } From b2935158b2c8f88ccbf332f9361960eebeb0e979 Mon Sep 17 00:00:00 2001 From: davidli Date: Fri, 17 Jul 2015 14:02:51 +0800 Subject: [PATCH 32/32] Remove IPC support from test file Signed-off-by: Li Wenquan --- docs/storage/driver/swift/swift_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/storage/driver/swift/swift_test.go b/docs/storage/driver/swift/swift_test.go index 726b5666a..6be2238a5 100644 --- a/docs/storage/driver/swift/swift_test.go +++ b/docs/storage/driver/swift/swift_test.go @@ -85,7 +85,7 @@ func init() { return swiftDriverConstructor(prefix) } - testsuites.RegisterInProcessSuite(driverConstructor, testsuites.NeverSkip) + testsuites.RegisterSuite(driverConstructor, testsuites.NeverSkip) } func TestEmptyRootList(t *testing.T) {