Import upstream version 2.0.1
Debian Janitor
2 years ago
0 | --- | |
1 | # Github Actions build for swift | |
2 | # -*- compile-command: "yamllint -f parsable build.yml" -*- | |
3 | ||
4 | name: build | |
5 | ||
6 | # Trigger the workflow on push or pull request | |
7 | on: | |
8 | push: | |
9 | branches: | |
10 | - '*' | |
11 | tags: | |
12 | - '*' | |
13 | pull_request: | |
14 | workflow_dispatch: | |
15 | inputs: | |
16 | manual: | |
17 | required: true | |
18 | default: true | |
19 | ||
20 | jobs: | |
21 | build: | |
22 | if: ${{ github.repository == 'ncw/swift' || github.event.inputs.manual }} | |
23 | timeout-minutes: 60 | |
24 | strategy: | |
25 | fail-fast: false | |
26 | matrix: | |
27 | job_name: ['go1.17', 'go1.16', 'go1.15'] | |
28 | ||
29 | include: | |
30 | - job_name: go1.17 | |
31 | os: ubuntu-latest | |
32 | go: '1.17.x' | |
33 | gotests: true | |
34 | integrationtest: true | |
35 | check: true | |
36 | ||
37 | - job_name: go1.16 | |
38 | os: ubuntu-latest | |
39 | go: '1.16.x' | |
40 | gotests: true | |
41 | integrationtest: true | |
42 | check: false | |
43 | ||
44 | - job_name: go1.15 | |
45 | os: ubuntu-latest | |
46 | go: '1.15.x' | |
47 | gotests: true | |
48 | integrationtest: true | |
49 | check: false | |
50 | ||
51 | name: ${{ matrix.job_name }} | |
52 | ||
53 | runs-on: ${{ matrix.os }} | |
54 | ||
55 | steps: | |
56 | - name: Checkout | |
57 | uses: actions/checkout@v2 | |
58 | with: | |
59 | fetch-depth: 0 | |
60 | ||
61 | - name: Install Go | |
62 | uses: actions/setup-go@v2 | |
63 | with: | |
64 | stable: 'false' | |
65 | go-version: ${{ matrix.go }} | |
66 | ||
67 | - name: Print Go version and environment | |
68 | shell: bash | |
69 | run: | | |
70 | printf "Using go at: $(which go)\n" | |
71 | printf "Go version: $(go version)\n" | |
72 | printf "\n\nGo environment:\n\n" | |
73 | go env | |
74 | printf "\n\nSystem environment:\n\n" | |
75 | env | |
76 | ||
77 | - name: Go module cache | |
78 | uses: actions/cache@v2 | |
79 | with: | |
80 | path: ~/go/pkg/mod | |
81 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} | |
82 | restore-keys: | | |
83 | ${{ runner.os }}-go- | |
84 | ||
85 | - name: Build | |
86 | shell: bash | |
87 | run: | | |
88 | go build ./... | |
89 | ||
90 | - name: Unit tests | |
91 | shell: bash | |
92 | run: | | |
93 | go test -v | |
94 | if: matrix.gotests | |
95 | ||
96 | - name: Integration tests | |
97 | shell: bash | |
98 | run: | | |
99 | ./integration_test.sh | |
100 | if: matrix.integrationtest | |
101 | ||
102 | - name: Code quality test | |
103 | uses: golangci/golangci-lint-action@v2 | |
104 | with: | |
105 | # Version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version | |
106 | version: latest | |
107 | if: matrix.check |
0 | # golangci-lint configuration options | |
1 | ||
2 | linters: | |
3 | enable: | |
4 | - deadcode | |
5 | - errcheck | |
6 | - goimports | |
7 | # - revive | |
8 | - ineffassign | |
9 | - structcheck | |
10 | - varcheck | |
11 | - govet | |
12 | - unconvert | |
13 | disable-all: true | |
14 | ||
15 | issues: | |
16 | # Enable some lints excluded by default | |
17 | exclude-use-default: false | |
18 | ||
19 | # Maximum issues count per one linter. Set to 0 to disable. Default is 50. | |
20 | max-per-linter: 0 | |
21 | ||
22 | # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. | |
23 | max-same-issues: 0 |
0 | language: go | |
1 | sudo: false | |
2 | ||
3 | go: | |
4 | - 1.2.x | |
5 | - 1.3.x | |
6 | - 1.4.x | |
7 | - 1.5.x | |
8 | - 1.6.x | |
9 | - 1.7.x | |
10 | - 1.8.x | |
11 | - 1.9.x | |
12 | - 1.10.x | |
13 | - 1.11.x | |
14 | - 1.12.x | |
15 | - 1.13.x | |
16 | - 1.14.x | |
17 | - master | |
18 | ||
19 | matrix: | |
20 | include: | |
21 | - go: 1.14.x | |
22 | env: TEST_REAL_SERVER=rackspace | |
23 | - go: 1.14.x | |
24 | env: TEST_REAL_SERVER=memset | |
25 | allow_failures: | |
26 | - go: 1.14.x | |
27 | env: TEST_REAL_SERVER=rackspace | |
28 | - go: 1.14.x | |
29 | env: TEST_REAL_SERVER=memset | |
30 | install: go test -i ./... | |
31 | script: | |
32 | - test -z "$(go fmt ./...)" | |
33 | - go test | |
34 | - ./travis_realserver.sh |
0 | 0 | Swift |
1 | 1 | ===== |
2 | 2 | |
3 | This package provides an easy to use library for interfacing with | |
4 | Swift / Openstack Object Storage / Rackspace cloud files from the Go | |
5 | Language | |
3 | This package provides an easy to use library for interfacing with Swift / Openstack Object Storage / Rackspace cloud | |
4 | files from the Go Language | |
6 | 5 | |
7 | See here for package docs | |
8 | ||
9 | http://godoc.org/github.com/ncw/swift | |
10 | ||
11 | [![Build Status](https://api.travis-ci.org/ncw/swift.svg?branch=master)](https://travis-ci.org/ncw/swift) [![GoDoc](https://godoc.org/github.com/ncw/swift?status.svg)](https://godoc.org/github.com/ncw/swift) | |
6 | [![Build Status](https://github.com/ncw/swift/workflows/build/badge.svg?branch=master)](https://github.com/ncw/swift/actions) | |
7 | [![Go Reference](https://pkg.go.dev/badge/github.com/ncw/v2/swift.svg)](https://pkg.go.dev/github.com/ncw/swift/v2) | |
12 | 8 | |
13 | 9 | Install |
14 | 10 | ------- |
15 | 11 | |
16 | 12 | Use go to install the library |
17 | 13 | |
18 | go get github.com/ncw/swift | |
14 | go get github.com/ncw/swift/v2 | |
19 | 15 | |
20 | 16 | Usage |
21 | 17 | ----- |
22 | 18 | |
23 | 19 | See here for full package docs |
24 | 20 | |
25 | - http://godoc.org/github.com/ncw/swift | |
21 | - https://pkg.go.dev/github.com/ncw/swift/v2 | |
26 | 22 | |
27 | 23 | Here is a short example from the docs |
24 | ||
28 | 25 | ```go |
29 | import "github.com/ncw/swift" | |
26 | import "github.com/ncw/swift/v2" | |
30 | 27 | |
31 | 28 | // Create a connection |
32 | 29 | c := swift.Connection{ |
33 | UserName: "user", | |
34 | ApiKey: "key", | |
35 | AuthUrl: "auth_url", | |
36 | Domain: "domain", // Name of the domain (v3 auth only) | |
37 | Tenant: "tenant", // Name of the tenant (v2 auth only) | |
30 | UserName: "user", | |
31 | ApiKey: "key", | |
32 | AuthUrl: "auth_url", | |
33 | Domain: "domain", // Name of the domain (v3 auth only) | |
34 | Tenant: "tenant", // Name of the tenant (v2 auth only) | |
38 | 35 | } |
39 | 36 | // Authenticate |
40 | 37 | err := c.Authenticate() |
41 | 38 | if err != nil { |
42 | panic(err) | |
39 | panic(err) | |
43 | 40 | } |
44 | 41 | // List all the containers |
45 | 42 | containers, err := c.ContainerNames(nil) |
46 | 43 | fmt.Println(containers) |
47 | 44 | // etc... |
48 | 45 | ``` |
46 | ||
47 | Migrating from `v1` | |
48 | ----- | |
49 | The library has current major version v2. If you want to migrate from the first version of | |
50 | library `github.com/ncw/swift` you have to explicitly add the `/v2` suffix to the imports. | |
51 | ||
52 | Most of the exported functions were added a new `context.Context` parameter in the `v2`, which you will have to provide | |
53 | when migrating. | |
49 | 54 | |
50 | 55 | Additions |
51 | 56 | --------- |
55 | 60 | Testing |
56 | 61 | ------- |
57 | 62 | |
58 | To run the tests you can either use an embedded fake Swift server | |
59 | either use a real Openstack Swift server or a Rackspace Cloud files account. | |
63 | To run the tests you can either use an embedded fake Swift server either use a real Openstack Swift server or a | |
64 | Rackspace Cloud files account. | |
60 | 65 | |
61 | When using a real Swift server, you need to set these environment variables | |
62 | before running the tests | |
66 | When using a real Swift server, you need to set these environment variables before running the tests | |
63 | 67 | |
64 | 68 | export SWIFT_API_USER='user' |
65 | 69 | export SWIFT_API_KEY='key' |
98 | 102 | License |
99 | 103 | ------- |
100 | 104 | |
101 | This is free software under the terms of MIT license (check COPYING file | |
102 | included in this package). | |
105 | This is free software under the terms of MIT license (check COPYING file included in this package). | |
103 | 106 | |
104 | 107 | Contact and support |
105 | 108 | ------------------- |
160 | 163 | - Brandon WELSCH <dev@brandon-welsch.eu> |
161 | 164 | - Damien Tournoud <damien@platform.sh> |
162 | 165 | - Pedro Kiefer <pedro@kiefer.com.br> |
166 | - Martin Chodur <m.chodur@seznam.cz> | |
167 | - Devendra <devendranath.thadi3@gmail.com> | |
168 | - timss <timsateroy@gmail.com> | |
169 | - Jos Houtman <jos@houtman.it> | |
170 | - Paul Collins <paul.collins@canonical.com> |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "bytes" |
4 | "context" | |
4 | 5 | "encoding/json" |
5 | 6 | "net/http" |
6 | 7 | "net/url" |
13 | 14 | // This encapsulates the different authentication schemes in use |
14 | 15 | type Authenticator interface { |
15 | 16 | // Request creates an http.Request for the auth - return nil if not needed |
16 | Request(*Connection) (*http.Request, error) | |
17 | Request(context.Context, *Connection) (*http.Request, error) | |
17 | 18 | // Response parses the http.Response |
18 | Response(resp *http.Response) error | |
19 | Response(ctx context.Context, resp *http.Response) error | |
19 | 20 | // The public storage URL - set Internal to true to read |
20 | 21 | // internal/service net URL |
21 | 22 | StorageUrl(Internal bool) string |
87 | 88 | } |
88 | 89 | |
89 | 90 | // v1 Authentication - make request |
90 | func (auth *v1Auth) Request(c *Connection) (*http.Request, error) { | |
91 | req, err := http.NewRequest("GET", c.AuthUrl, nil) | |
91 | func (auth *v1Auth) Request(ctx context.Context, c *Connection) (*http.Request, error) { | |
92 | req, err := http.NewRequestWithContext(ctx, "GET", c.AuthUrl, nil) | |
92 | 93 | if err != nil { |
93 | 94 | return nil, err |
94 | 95 | } |
99 | 100 | } |
100 | 101 | |
101 | 102 | // v1 Authentication - read response |
102 | func (auth *v1Auth) Response(resp *http.Response) error { | |
103 | func (auth *v1Auth) Response(_ context.Context, resp *http.Response) error { | |
103 | 104 | auth.Headers = resp.Header |
104 | 105 | return nil |
105 | 106 | } |
140 | 141 | } |
141 | 142 | |
142 | 143 | // v2 Authentication - make request |
143 | func (auth *v2Auth) Request(c *Connection) (*http.Request, error) { | |
144 | func (auth *v2Auth) Request(ctx context.Context, c *Connection) (*http.Request, error) { | |
144 | 145 | auth.Region = c.Region |
145 | 146 | // Toggle useApiKey if not first run and not OK yet |
146 | 147 | if auth.notFirst && !auth.useApiKeyOk { |
175 | 176 | url += "/" |
176 | 177 | } |
177 | 178 | url += "tokens" |
178 | req, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) | |
179 | req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(body)) | |
179 | 180 | if err != nil { |
180 | 181 | return nil, err |
181 | 182 | } |
185 | 186 | } |
186 | 187 | |
187 | 188 | // v2 Authentication - read response |
188 | func (auth *v2Auth) Response(resp *http.Response) error { | |
189 | func (auth *v2Auth) Response(_ context.Context, resp *http.Response) error { | |
189 | 190 | auth.Auth = new(v2AuthResponse) |
190 | 191 | err := readJson(resp, auth.Auth) |
191 | 192 | // If successfully read Auth then no need to toggle useApiKey any more |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "bytes" |
4 | "context" | |
4 | 5 | "encoding/json" |
5 | 6 | "fmt" |
6 | 7 | "net/http" |
12 | 13 | v3AuthMethodToken = "token" |
13 | 14 | v3AuthMethodPassword = "password" |
14 | 15 | v3AuthMethodApplicationCredential = "application_credential" |
15 | v3CatalogTypeObjectStore = "object-store" | |
16 | 16 | ) |
17 | 17 | |
18 | 18 | // V3 Authentication request |
121 | 121 | Headers http.Header |
122 | 122 | } |
123 | 123 | |
124 | func (auth *v3Auth) Request(c *Connection) (*http.Request, error) { | |
124 | func (auth *v3Auth) Request(ctx context.Context, c *Connection) (*http.Request, error) { | |
125 | 125 | auth.Region = c.Region |
126 | 126 | |
127 | 127 | var v3i interface{} |
241 | 241 | url += "/" |
242 | 242 | } |
243 | 243 | url += "auth/tokens" |
244 | req, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) | |
244 | req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(body)) | |
245 | 245 | if err != nil { |
246 | 246 | return nil, err |
247 | 247 | } |
250 | 250 | return req, nil |
251 | 251 | } |
252 | 252 | |
253 | func (auth *v3Auth) Response(resp *http.Response) error { | |
253 | func (auth *v3Auth) Response(_ context.Context, resp *http.Response) error { | |
254 | 254 | auth.Auth = &v3AuthResponse{} |
255 | 255 | auth.Headers = resp.Header |
256 | 256 | err := readJson(resp, auth.Auth) |
0 | #!/usr/bin/env python | |
0 | #!/usr/bin/env python3 | |
1 | 1 | """ |
2 | 2 | Update the README.md file with the authors from the git log |
3 | 3 | """ |
22 | 22 | """ |
23 | 23 | adds the email passed in to the end of authors |
24 | 24 | """ |
25 | print "Adding %s <%s>" % (name, email) | |
25 | print("Adding %s <%s>" % (name, email)) | |
26 | 26 | with open(AUTHORS, "a+") as fd: |
27 | print >>fd, "- %s <%s>" % (name, email) | |
27 | print("- %s <%s>" % (name, email), file=fd) | |
28 | 28 | subprocess.check_call(["git", "commit", "-m", "Add %s to contributors" % name, AUTHORS]) |
29 | 29 | |
30 | 30 | def main(): |
31 | 31 | out = subprocess.check_output(["git", "log", '--reverse', '--format=%an|%ae', "master"]) |
32 | 32 | |
33 | 33 | previous = load() |
34 | for line in out.split("\n"): | |
34 | for line in out.split(b"\n"): | |
35 | line = line.decode("utf-8") | |
35 | 36 | line = line.strip() |
36 | 37 | if line == "": |
37 | 38 | continue |
0 | 0 | // Go 1.1 and later compatibility functions |
1 | 1 | // |
2 | //go:build go1.1 | |
2 | 3 | // +build go1.1 |
3 | 4 | |
4 | 5 | package swift |
0 | 0 | package swift |
1 | 1 | |
2 | 2 | import ( |
3 | "context" | |
3 | 4 | "os" |
4 | 5 | "strings" |
5 | 6 | ) |
13 | 14 | // returning an object which satisfies io.Writer, io.Seeker, io.Closer |
14 | 15 | // and io.ReaderFrom. The flags are as passes to the |
15 | 16 | // largeObjectCreate method. |
16 | func (c *Connection) DynamicLargeObjectCreateFile(opts *LargeObjectOpts) (LargeObjectFile, error) { | |
17 | lo, err := c.largeObjectCreate(opts) | |
17 | func (c *Connection) DynamicLargeObjectCreateFile(ctx context.Context, opts *LargeObjectOpts) (LargeObjectFile, error) { | |
18 | lo, err := c.largeObjectCreate(ctx, opts) | |
18 | 19 | if err != nil { |
19 | 20 | return nil, err |
20 | 21 | } |
27 | 28 | // DynamicLargeObjectCreate creates or truncates an existing dynamic |
28 | 29 | // large object returning a writeable object. This sets opts.Flags to |
29 | 30 | // an appropriate value before calling DynamicLargeObjectCreateFile |
30 | func (c *Connection) DynamicLargeObjectCreate(opts *LargeObjectOpts) (LargeObjectFile, error) { | |
31 | func (c *Connection) DynamicLargeObjectCreate(ctx context.Context, opts *LargeObjectOpts) (LargeObjectFile, error) { | |
31 | 32 | opts.Flags = os.O_TRUNC | os.O_CREATE |
32 | return c.DynamicLargeObjectCreateFile(opts) | |
33 | return c.DynamicLargeObjectCreateFile(ctx, opts) | |
33 | 34 | } |
34 | 35 | |
35 | 36 | // DynamicLargeObjectDelete deletes a dynamic large object and all of its segments. |
36 | func (c *Connection) DynamicLargeObjectDelete(container string, path string) error { | |
37 | return c.LargeObjectDelete(container, path) | |
37 | func (c *Connection) DynamicLargeObjectDelete(ctx context.Context, container string, path string) error { | |
38 | return c.LargeObjectDelete(ctx, container, path) | |
38 | 39 | } |
39 | 40 | |
40 | 41 | // DynamicLargeObjectMove moves a dynamic large object from srcContainer, srcObjectName to dstContainer, dstObjectName |
41 | func (c *Connection) DynamicLargeObjectMove(srcContainer string, srcObjectName string, dstContainer string, dstObjectName string) error { | |
42 | info, headers, err := c.Object(srcContainer, srcObjectName) | |
42 | func (c *Connection) DynamicLargeObjectMove(ctx context.Context, srcContainer string, srcObjectName string, dstContainer string, dstObjectName string) error { | |
43 | info, headers, err := c.Object(ctx, srcContainer, srcObjectName) | |
43 | 44 | if err != nil { |
44 | 45 | return err |
45 | 46 | } |
46 | 47 | |
47 | segmentContainer, segmentPath := parseFullPath(headers["X-Object-Manifest"]) | |
48 | if err := c.createDLOManifest(dstContainer, dstObjectName, segmentContainer+"/"+segmentPath, info.ContentType, sanitizeLargeObjectMoveHeaders(headers)); err != nil { | |
48 | segmentContainer, segmentPath, err := parseFullPath(headers["X-Object-Manifest"]) | |
49 | if err != nil { | |
49 | 50 | return err |
50 | 51 | } |
51 | 52 | |
52 | if err := c.ObjectDelete(srcContainer, srcObjectName); err != nil { | |
53 | if err := c.createDLOManifest(ctx, dstContainer, dstObjectName, segmentContainer+"/"+segmentPath, info.ContentType, sanitizeLargeObjectMoveHeaders(headers)); err != nil { | |
54 | return err | |
55 | } | |
56 | ||
57 | if err := c.ObjectDelete(ctx, srcContainer, srcObjectName); err != nil { | |
53 | 58 | return err |
54 | 59 | } |
55 | 60 | |
67 | 72 | } |
68 | 73 | |
69 | 74 | // createDLOManifest creates a dynamic large object manifest |
70 | func (c *Connection) createDLOManifest(container string, objectName string, prefix string, contentType string, headers Headers) error { | |
75 | func (c *Connection) createDLOManifest(ctx context.Context, container string, objectName string, prefix string, contentType string, headers Headers) error { | |
71 | 76 | if headers == nil { |
72 | 77 | headers = make(Headers) |
73 | 78 | } |
74 | 79 | headers["X-Object-Manifest"] = prefix |
75 | manifest, err := c.ObjectCreate(container, objectName, false, "", contentType, headers) | |
80 | manifest, err := c.ObjectCreate(ctx, container, objectName, false, "", contentType, headers) | |
76 | 81 | if err != nil { |
77 | 82 | return err |
78 | 83 | } |
86 | 91 | |
87 | 92 | // Close satisfies the io.Closer interface |
88 | 93 | func (file *DynamicLargeObjectCreateFile) Close() error { |
89 | return file.Flush() | |
94 | return file.CloseWithContext(context.Background()) | |
90 | 95 | } |
91 | 96 | |
92 | func (file *DynamicLargeObjectCreateFile) Flush() error { | |
93 | err := file.conn.createDLOManifest(file.container, file.objectName, file.segmentContainer+"/"+file.prefix, file.contentType, file.headers) | |
97 | func (file *DynamicLargeObjectCreateFile) CloseWithContext(ctx context.Context) error { | |
98 | return file.Flush(ctx) | |
99 | } | |
100 | ||
101 | func (file *DynamicLargeObjectCreateFile) Flush(ctx context.Context) error { | |
102 | err := file.conn.createDLOManifest(ctx, file.container, file.objectName, file.segmentContainer+"/"+file.prefix, file.contentType, file.headers) | |
94 | 103 | if err != nil { |
95 | 104 | return err |
96 | 105 | } |
97 | return file.conn.waitForSegmentsToShowUp(file.container, file.objectName, file.Size()) | |
106 | return file.conn.waitForSegmentsToShowUp(ctx, file.container, file.objectName, file.Size()) | |
98 | 107 | } |
99 | 108 | |
100 | func (c *Connection) getAllDLOSegments(segmentContainer, segmentPath string) ([]Object, error) { | |
109 | func (c *Connection) getAllDLOSegments(ctx context.Context, segmentContainer, segmentPath string) ([]Object, error) { | |
101 | 110 | //a simple container listing works 99.9% of the time |
102 | segments, err := c.ObjectsAll(segmentContainer, &ObjectsOpts{Prefix: segmentPath}) | |
111 | segments, err := c.ObjectsAll(ctx, segmentContainer, &ObjectsOpts{Prefix: segmentPath}) | |
103 | 112 | if err != nil { |
104 | 113 | return nil, err |
105 | 114 | } |
125 | 134 | //guaranteed to return the correct metadata, except for the pathological |
126 | 135 | //case of an outage of large parts of the Swift cluster or its network, |
127 | 136 | //since every segment is only written once.) |
128 | segment, _, err := c.Object(segmentContainer, segmentName) | |
137 | segment, _, err := c.Object(ctx, segmentContainer, segmentName) | |
129 | 138 | switch err { |
130 | 139 | case nil: |
131 | 140 | //found new segment -> add it in the correct position and keep |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "bytes" |
4 | "github.com/ncw/swift/swifttest" | |
4 | "context" | |
5 | 5 | "net/http" |
6 | 6 | "testing" |
7 | 7 | "time" |
8 | ||
9 | "github.com/ncw/swift/v2/swifttest" | |
8 | 10 | ) |
9 | 11 | |
10 | 12 | var srv *swifttest.SwiftServer |
46 | 48 | Domain: "Default", |
47 | 49 | AuthVersion: 1, |
48 | 50 | } |
49 | err = swiftCon.Authenticate() | |
51 | err = swiftCon.Authenticate(context.Background()) | |
50 | 52 | return &swiftCon, err |
51 | 53 | } |
52 | 54 | func TestCases(t *testing.T) { |
62 | 64 | } |
63 | 65 | |
64 | 66 | func createContainers(containers []string, t *testing.T) { |
67 | ctx := context.Background() | |
65 | 68 | for i := 0; i < len(containers); i++ { |
66 | err = con.ContainerCreate(containers[i], nil) // Create container | |
69 | err = con.ContainerCreate(ctx, containers[i], nil) // Create container | |
67 | 70 | if err != nil { |
68 | 71 | t.Errorf("Fail at create container %s", containers[i]) |
69 | 72 | } |
71 | 74 | } |
72 | 75 | |
73 | 76 | func createDynamicObject(container, object string, t *testing.T) { |
77 | ctx := context.Background() | |
74 | 78 | metadata := map[string]string{} |
75 | 79 | metadata["Custom-Field"] = "SomeValue" |
76 | 80 | ops := LargeObjectOpts{ |
82 | 86 | SegmentContainer: segmentContainer, // Name of the container to place segments |
83 | 87 | SegmentPrefix: "sg", // Prefix to use for the segments |
84 | 88 | } |
85 | bigfile, err := con.DynamicLargeObjectCreate(&ops) | |
89 | bigfile, err := con.DynamicLargeObjectCreate(ctx, &ops) | |
86 | 90 | if err != nil { |
87 | 91 | t.Errorf("Fail at dynamic create Large Object") |
88 | 92 | } |
89 | bigfile.Write(filecontent) | |
90 | bigfile.Close() | |
93 | _, err = bigfile.WriteWithContext(ctx, filecontent) | |
94 | if err != nil { | |
95 | t.Errorf("WriteWithContext failed: %v", err) | |
96 | } | |
97 | err = bigfile.CloseWithContext(ctx) | |
98 | if err != nil { | |
99 | t.Errorf("CloseWithContext failed: %v", err) | |
100 | } | |
91 | 101 | checkObject(container, object, t) |
92 | 102 | } |
93 | 103 | func checkObject(container, object string, t *testing.T) { |
94 | info, header, err := con.Object(container, object) | |
104 | ctx := context.Background() | |
105 | info, header, err := con.Object(ctx, container, object) | |
95 | 106 | if err != nil { |
96 | 107 | t.Errorf("Fail at get Large Object metadata: %s", err.Error()) |
97 | 108 | } |
105 | 116 | t.Errorf("Fail: lost custom metadata header") |
106 | 117 | } |
107 | 118 | |
108 | content, err := con.ObjectGetBytes(container, object) | |
119 | content, err := con.ObjectGetBytes(ctx, container, object) | |
109 | 120 | if err != nil { |
110 | 121 | t.Errorf("Fail at read Large Object : %s", err.Error()) |
111 | 122 | } |
115 | 126 | |
116 | 127 | } |
117 | 128 | func checkNotExistObject(container, object string, t *testing.T) { |
118 | _, _, err = con.Object(container, object) | |
129 | _, _, err = con.Object(context.Background(), container, object) | |
119 | 130 | if err == nil || err.Error() != "Object Not Found" { |
120 | 131 | t.Errorf("Fail at checkNotExistObject object: %s", err) |
121 | 132 | } |
122 | 133 | } |
123 | 134 | func moveDynamicObject(sc, so, dc, do string, t *testing.T) { |
124 | err = con.DynamicLargeObjectMove(sc, so, dc, do) | |
135 | err = con.DynamicLargeObjectMove(context.Background(), sc, so, dc, do) | |
125 | 136 | if err != nil { |
126 | 137 | t.Errorf("Fail at dynamic move Large Object: %s", err.Error()) |
127 | 138 | } |
129 | 140 | checkObject(dc, do, t) |
130 | 141 | } |
131 | 142 | func deleteDynamicObject(container, object string, t *testing.T) { |
132 | err = con.DynamicLargeObjectDelete(container, object) | |
143 | ctx := context.Background() | |
144 | err = con.DynamicLargeObjectDelete(ctx, container, object) | |
133 | 145 | if err != nil { |
134 | 146 | t.Errorf("Fail at delte dynamic Large Object: %s", err.Error()) |
135 | 147 | } |
136 | 148 | checkNotExistObject(container, object, t) |
137 | objs, err := con.ObjectsAll(segmentContainer, nil) | |
149 | objs, err := con.ObjectsAll(ctx, segmentContainer, nil) | |
138 | 150 | if err != nil { |
139 | 151 | t.Errorf("Fail at check delte dynamic Large Object: %s", err.Error()) |
140 | 152 | } |
143 | 155 | } |
144 | 156 | } |
145 | 157 | func createStaticObject(container, object string, t *testing.T) { |
158 | ctx := context.Background() | |
146 | 159 | metadata := map[string]string{} |
147 | 160 | metadata["Custom-Field"] = "SomeValue" |
148 | 161 | ops := LargeObjectOpts{ |
154 | 167 | SegmentContainer: segmentContainer, // Name of the container to place segments |
155 | 168 | SegmentPrefix: "sg", // Prefix to use for the segments |
156 | 169 | } |
157 | bigfile, err := con.StaticLargeObjectCreate(&ops) | |
170 | bigfile, err := con.StaticLargeObjectCreate(ctx, &ops) | |
158 | 171 | if err != nil { |
159 | 172 | t.Errorf("Fail at static create Large Object") |
160 | 173 | } |
161 | bigfile.Write(filecontent) | |
162 | bigfile.Close() | |
174 | _, err = bigfile.WriteWithContext(ctx, filecontent) | |
175 | if err != nil { | |
176 | t.Errorf("WriteWithContext failed: %v", err) | |
177 | } | |
178 | err = bigfile.CloseWithContext(ctx) | |
179 | if err != nil { | |
180 | t.Errorf("CloseWithContext failed: %v", err) | |
181 | } | |
163 | 182 | checkObject(container, object, t) |
164 | 183 | } |
165 | 184 | func moveStaticObject(sc, so, dc, do string, t *testing.T) { |
166 | err = con.StaticLargeObjectMove(sc, so, dc, do) | |
185 | err = con.StaticLargeObjectMove(context.Background(), sc, so, dc, do) | |
167 | 186 | if err != nil { |
168 | 187 | t.Errorf("Fail at static move Large Object: %s", err.Error()) |
169 | 188 | } |
171 | 190 | checkObject(dc, do, t) |
172 | 191 | } |
173 | 192 | func deleteStaticObject(container, object string, t *testing.T) { |
174 | err = con.StaticLargeObjectDelete(container, object) | |
193 | ctx := context.Background() | |
194 | err = con.StaticLargeObjectDelete(ctx, container, object) | |
175 | 195 | if err != nil { |
176 | 196 | t.Errorf("Fail at delte dynamic Large Object: %s", err.Error()) |
177 | 197 | } |
178 | 198 | checkNotExistObject(container, object, t) |
179 | objs, err := con.ObjectsAll(segmentContainer, nil) | |
199 | objs, err := con.ObjectsAll(ctx, segmentContainer, nil) | |
180 | 200 | if err != nil { |
181 | 201 | t.Errorf("Fail at check delte dynamic Large Object: %s", err.Error()) |
182 | 202 | } |
3 | 3 | package swift_test |
4 | 4 | |
5 | 5 | import ( |
6 | "context" | |
6 | 7 | "fmt" |
7 | 8 | |
8 | "github.com/ncw/swift" | |
9 | "github.com/ncw/swift/v2" | |
9 | 10 | ) |
10 | 11 | |
11 | 12 | func ExampleConnection() { |
13 | ctx := context.Background() | |
12 | 14 | // Create a v1 auth connection |
13 | 15 | c := &swift.Connection{ |
14 | 16 | // This should be your username |
23 | 25 | } |
24 | 26 | |
25 | 27 | // Authenticate |
26 | err := c.Authenticate() | |
28 | err := c.Authenticate(ctx) | |
27 | 29 | if err != nil { |
28 | 30 | panic(err) |
29 | 31 | } |
30 | 32 | // List all the containers |
31 | containers, err := c.ContainerNames(nil) | |
33 | containers, err := c.ContainerNames(ctx, nil) | |
34 | if err != nil { | |
35 | panic(err) | |
36 | } | |
32 | 37 | fmt.Println(containers) |
33 | 38 | // etc... |
34 | 39 | |
60 | 65 | defer rollback() |
61 | 66 | |
62 | 67 | objects := make([]string, 0) |
63 | err := c.ObjectsWalk(container, nil, func(opts *swift.ObjectsOpts) (interface{}, error) { | |
64 | newObjects, err := c.ObjectNames(container, opts) | |
68 | err := c.ObjectsWalk(context.Background(), container, nil, func(ctx context.Context, opts *swift.ObjectsOpts) (interface{}, error) { | |
69 | newObjects, err := c.ObjectNames(ctx, container, opts) | |
65 | 70 | if err == nil { |
66 | 71 | objects = append(objects, newObjects...) |
67 | 72 | } |
75 | 80 | defer rollback() |
76 | 81 | |
77 | 82 | // Use the helper method to create the current and versions container. |
78 | if err := c.VersionContainerCreate("cds", "cd-versions"); err != nil { | |
83 | if err := c.VersionContainerCreate(context.Background(), "cds", "cd-versions"); err != nil { | |
79 | 84 | fmt.Print(err.Error()) |
80 | 85 | } |
81 | 86 | } |
82 | 87 | |
83 | 88 | func ExampleConnection_VersionEnable() { |
89 | ctx := context.Background() | |
84 | 90 | c, rollback := makeConnection(nil) |
85 | 91 | defer rollback() |
86 | 92 | |
87 | 93 | // Build the containers manually and enable them. |
88 | if err := c.ContainerCreate("movie-versions", nil); err != nil { | |
94 | if err := c.ContainerCreate(ctx, "movie-versions", nil); err != nil { | |
89 | 95 | fmt.Print(err.Error()) |
90 | 96 | } |
91 | if err := c.ContainerCreate("movies", nil); err != nil { | |
97 | if err := c.ContainerCreate(ctx, "movies", nil); err != nil { | |
92 | 98 | fmt.Print(err.Error()) |
93 | 99 | } |
94 | if err := c.VersionEnable("movies", "movie-versions"); err != nil { | |
100 | if err := c.VersionEnable(ctx, "movies", "movie-versions"); err != nil { | |
95 | 101 | fmt.Print(err.Error()) |
96 | 102 | } |
97 | 103 | |
104 | 110 | defer rollback() |
105 | 111 | |
106 | 112 | // Disable versioning on a container. Note that this does not delete the versioning container. |
107 | c.VersionDisable("movies") | |
113 | err := c.VersionDisable(context.Background(), "movies") | |
114 | if err != nil { | |
115 | panic(err) | |
116 | } | |
108 | 117 | } |
0 | #!/bin/bash | |
1 | # Run the swift tests against an openstack server from a swift all in | |
2 | # one docker image | |
3 | ||
4 | set -e | |
5 | ||
6 | NAME=swift-aio | |
7 | HOST=127.0.0.1 | |
8 | PORT=8294 | |
9 | AUTH=v1 | |
10 | ||
11 | case $AUTH in | |
12 | v1) | |
13 | export SWIFT_AUTH_URL="http://${HOST}:${PORT}/auth/v1.0" | |
14 | export SWIFT_API_USER='test:tester' | |
15 | export SWIFT_API_KEY='testing' | |
16 | ;; | |
17 | v2) | |
18 | # NB v2 auth doesn't work for unknown reasons! | |
19 | export SWIFT_AUTH_URL="http://${HOST}:${PORT}/auth/v2.0" | |
20 | export SWIFT_TENANT='tester' | |
21 | export SWIFT_API_USER='test' | |
22 | export SWIFT_API_KEY='testing' | |
23 | ;; | |
24 | *) | |
25 | echo "Bad AUTH %AUTH" | |
26 | exit 1 | |
27 | ;; | |
28 | esac | |
29 | ||
30 | ||
31 | echo "Starting test server" | |
32 | docker run --rm -d --name ${NAME} -p ${HOST}:${PORT}:8080 bouncestorage/swift-aio | |
33 | ||
34 | function cleanup { | |
35 | echo "Killing test server" | |
36 | docker kill ${NAME} | |
37 | } | |
38 | ||
39 | trap cleanup EXIT | |
40 | ||
41 | echo -n "Waiting for test server to startup" | |
42 | tries=30 | |
43 | while [[ $tries -gt 0 ]]; do | |
44 | echo -n "." | |
45 | STATUS_RECEIVED=$(curl -s -o /dev/null -L -w ''%{http_code}'' ${SWIFT_AUTH_URL} || true) | |
46 | if [[ ${STATUS_RECEIVED} -ge 200 ]]; then | |
47 | break | |
48 | fi | |
49 | let tries-=1 | |
50 | sleep 1 | |
51 | done | |
52 | echo "OK" | |
53 | ||
54 | echo "Running tests" | |
55 | go test -v | |
56 |
2 | 2 | import ( |
3 | 3 | "bufio" |
4 | 4 | "bytes" |
5 | "context" | |
5 | 6 | "crypto/rand" |
6 | 7 | "crypto/sha1" |
7 | 8 | "encoding/hex" |
8 | 9 | "errors" |
9 | 10 | "fmt" |
10 | 11 | "io" |
12 | "net/url" | |
11 | 13 | "os" |
12 | 14 | gopath "path" |
13 | 15 | "strconv" |
55 | 57 | return fmt.Sprintf("%s/%016d", segmentPath, partNumber) |
56 | 58 | } |
57 | 59 | |
58 | func parseFullPath(manifest string) (container string, prefix string) { | |
60 | func parseFullPath(manifest string) (container string, prefix string, err error) { | |
61 | manifest, err = url.PathUnescape(manifest) | |
62 | if err != nil { | |
63 | return | |
64 | } | |
59 | 65 | components := strings.SplitN(manifest, "/", 2) |
60 | 66 | container = components[0] |
61 | 67 | if len(components) > 1 { |
62 | 68 | prefix = components[1] |
63 | 69 | } |
64 | return container, prefix | |
70 | return container, prefix, nil | |
65 | 71 | } |
66 | 72 | |
67 | 73 | func (headers Headers) IsLargeObjectDLO() bool { |
78 | 84 | return headers.IsLargeObjectSLO() || headers.IsLargeObjectDLO() |
79 | 85 | } |
80 | 86 | |
81 | func (c *Connection) getAllSegments(container string, path string, headers Headers) (string, []Object, error) { | |
87 | func (c *Connection) getAllSegments(ctx context.Context, container string, path string, headers Headers) (string, []Object, error) { | |
82 | 88 | if manifest, isDLO := headers["X-Object-Manifest"]; isDLO { |
83 | segmentContainer, segmentPath := parseFullPath(manifest) | |
84 | segments, err := c.getAllDLOSegments(segmentContainer, segmentPath) | |
89 | segmentContainer, segmentPath, err := parseFullPath(manifest) | |
90 | if err != nil { | |
91 | return segmentContainer, nil, err | |
92 | } | |
93 | segments, err := c.getAllDLOSegments(ctx, segmentContainer, segmentPath) | |
85 | 94 | return segmentContainer, segments, err |
86 | 95 | } |
87 | 96 | if headers.IsLargeObjectSLO() { |
88 | return c.getAllSLOSegments(container, path) | |
97 | return c.getAllSLOSegments(ctx, container, path) | |
89 | 98 | } |
90 | 99 | return "", nil, NotLargeObject |
91 | 100 | } |
107 | 116 | } |
108 | 117 | |
109 | 118 | type LargeObjectFile interface { |
119 | io.Seeker | |
110 | 120 | io.Writer |
111 | io.Seeker | |
112 | 121 | io.Closer |
122 | ||
123 | WriteWithContext(ctx context.Context, p []byte) (n int, err error) | |
124 | CloseWithContext(ctx context.Context) error | |
113 | 125 | Size() int64 |
114 | Flush() error | |
126 | Flush(ctx context.Context) error | |
115 | 127 | } |
116 | 128 | |
117 | 129 | // largeObjectCreate creates a large object at opts.Container, opts.ObjectName. |
119 | 131 | // opts.Flags can have the following bits set |
120 | 132 | // os.TRUNC - remove the contents of the large object if it exists |
121 | 133 | // os.APPEND - write at the end of the large object |
122 | func (c *Connection) largeObjectCreate(opts *LargeObjectOpts) (*largeObjectCreateFile, error) { | |
134 | func (c *Connection) largeObjectCreate(ctx context.Context, opts *LargeObjectOpts) (*largeObjectCreateFile, error) { | |
123 | 135 | var ( |
124 | 136 | segmentPath string |
125 | 137 | segmentContainer string |
134 | 146 | return nil, err |
135 | 147 | } |
136 | 148 | |
137 | if info, headers, err := c.Object(opts.Container, opts.ObjectName); err == nil { | |
149 | if info, headers, err := c.Object(ctx, opts.Container, opts.ObjectName); err == nil { | |
138 | 150 | if opts.Flags&os.O_TRUNC != 0 { |
139 | c.LargeObjectDelete(opts.Container, opts.ObjectName) | |
151 | err := c.LargeObjectDelete(ctx, opts.Container, opts.ObjectName) | |
152 | if err != nil { | |
153 | return nil, err | |
154 | } | |
140 | 155 | } else { |
141 | 156 | currentLength = info.Bytes |
142 | 157 | if headers.IsLargeObject() { |
143 | segmentContainer, segments, err = c.getAllSegments(opts.Container, opts.ObjectName, headers) | |
158 | segmentContainer, segments, err = c.getAllSegments(ctx, opts.Container, opts.ObjectName, headers) | |
144 | 159 | if err != nil { |
145 | 160 | return nil, err |
146 | 161 | } |
148 | 163 | segmentPath = gopath.Dir(segments[0].Name) |
149 | 164 | } |
150 | 165 | } else { |
151 | if err = c.ObjectMove(opts.Container, opts.ObjectName, opts.Container, getSegment(segmentPath, 1)); err != nil { | |
166 | if err = c.ObjectMove(ctx, opts.Container, opts.ObjectName, opts.Container, getSegment(segmentPath, 1)); err != nil { | |
152 | 167 | return nil, err |
153 | 168 | } |
154 | 169 | segments = append(segments, info) |
197 | 212 | } |
198 | 213 | |
199 | 214 | // LargeObjectDelete deletes the large object named by container, path |
200 | func (c *Connection) LargeObjectDelete(container string, objectName string) error { | |
201 | _, headers, err := c.Object(container, objectName) | |
215 | func (c *Connection) LargeObjectDelete(ctx context.Context, container string, objectName string) error { | |
216 | _, headers, err := c.Object(ctx, container, objectName) | |
202 | 217 | if err != nil { |
203 | 218 | return err |
204 | 219 | } |
205 | 220 | |
206 | 221 | var objects [][]string |
207 | 222 | if headers.IsLargeObject() { |
208 | segmentContainer, segments, err := c.getAllSegments(container, objectName, headers) | |
223 | segmentContainer, segments, err := c.getAllSegments(ctx, container, objectName, headers) | |
209 | 224 | if err != nil { |
210 | 225 | return err |
211 | 226 | } |
215 | 230 | } |
216 | 231 | objects = append(objects, []string{container, objectName}) |
217 | 232 | |
218 | info, err := c.cachedQueryInfo() | |
233 | info, err := c.cachedQueryInfo(ctx) | |
219 | 234 | if err == nil && info.SupportsBulkDelete() && len(objects) > 0 { |
220 | 235 | filenames := make([]string, len(objects)) |
221 | 236 | for i, obj := range objects { |
222 | 237 | filenames[i] = obj[0] + "/" + obj[1] |
223 | 238 | } |
224 | _, err = c.doBulkDelete(filenames, nil) | |
239 | _, err = c.doBulkDelete(ctx, filenames, nil) | |
225 | 240 | // Don't fail on ObjectNotFound because eventual consistency |
226 | 241 | // makes this situation normal. |
227 | 242 | if err != nil && err != Forbidden && err != ObjectNotFound { |
229 | 244 | } |
230 | 245 | } else { |
231 | 246 | for _, obj := range objects { |
232 | if err := c.ObjectDelete(obj[0], obj[1]); err != nil { | |
247 | if err := c.ObjectDelete(ctx, obj[0], obj[1]); err != nil { | |
233 | 248 | return err |
234 | 249 | } |
235 | 250 | } |
243 | 258 | // that have the prefix as indicated by the manifest. |
244 | 259 | // If the object is a Static Large Object (SLO), it retrieves the JSON content |
245 | 260 | // of the manifest and return all the segments of it. |
246 | func (c *Connection) LargeObjectGetSegments(container string, path string) (string, []Object, error) { | |
247 | _, headers, err := c.Object(container, path) | |
261 | func (c *Connection) LargeObjectGetSegments(ctx context.Context, container string, path string) (string, []Object, error) { | |
262 | _, headers, err := c.Object(ctx, container, path) | |
248 | 263 | if err != nil { |
249 | 264 | return "", nil, err |
250 | 265 | } |
251 | 266 | |
252 | return c.getAllSegments(container, path, headers) | |
267 | return c.getAllSegments(ctx, container, path, headers) | |
253 | 268 | } |
254 | 269 | |
255 | 270 | // Seek sets the offset for the next write operation |
300 | 315 | } |
301 | 316 | } |
302 | 317 | |
303 | func (c *Connection) waitForSegmentsToShowUp(container, objectName string, expectedSize int64) (err error) { | |
318 | func (c *Connection) waitForSegmentsToShowUp(ctx context.Context, container, objectName string, expectedSize int64) (err error) { | |
304 | 319 | err = withLORetry(expectedSize, func() (Headers, int64, error) { |
305 | 320 | var info Object |
306 | 321 | var headers Headers |
307 | info, headers, err = c.objectBase(container, objectName) | |
322 | info, headers, err = c.objectBase(ctx, container, objectName) | |
308 | 323 | if err != nil { |
309 | 324 | return headers, 0, err |
310 | 325 | } |
313 | 328 | return |
314 | 329 | } |
315 | 330 | |
316 | // Write satisfies the io.Writer interface | |
317 | 331 | func (file *largeObjectCreateFile) Write(buf []byte) (int, error) { |
332 | return file.WriteWithContext(context.Background(), buf) | |
333 | } | |
334 | ||
335 | func (file *largeObjectCreateFile) WriteWithContext(ctx context.Context, buf []byte) (int, error) { | |
318 | 336 | var sz int64 |
319 | 337 | var relativeFilePos int |
320 | 338 | writeSegmentIdx := 0 |
328 | 346 | } |
329 | 347 | sizeToWrite := len(buf) |
330 | 348 | for offset := 0; offset < sizeToWrite; { |
331 | newSegment, n, err := file.writeSegment(buf[offset:], writeSegmentIdx, relativeFilePos) | |
349 | newSegment, n, err := file.writeSegment(ctx, buf[offset:], writeSegmentIdx, relativeFilePos) | |
332 | 350 | if err != nil { |
333 | 351 | return 0, err |
334 | 352 | } |
349 | 367 | return sizeToWrite, nil |
350 | 368 | } |
351 | 369 | |
352 | func (file *largeObjectCreateFile) writeSegment(buf []byte, writeSegmentIdx int, relativeFilePos int) (*Object, int, error) { | |
370 | func (file *largeObjectCreateFile) writeSegment(ctx context.Context, buf []byte, writeSegmentIdx int, relativeFilePos int) (obj *Object, n int, err error) { | |
353 | 371 | var ( |
354 | 372 | readers []io.Reader |
355 | 373 | existingSegment *Object |
365 | 383 | if relativeFilePos > 0 { |
366 | 384 | headers := make(Headers) |
367 | 385 | headers["Range"] = "bytes=0-" + strconv.FormatInt(int64(relativeFilePos-1), 10) |
368 | existingSegmentReader, _, err := file.conn.ObjectOpen(file.segmentContainer, segmentName, true, headers) | |
386 | existingSegmentReader, _, err := file.conn.ObjectOpen(ctx, file.segmentContainer, segmentName, true, headers) | |
369 | 387 | if err != nil { |
370 | 388 | return nil, 0, err |
371 | 389 | } |
372 | defer existingSegmentReader.Close() | |
390 | defer func() { | |
391 | closeErr := existingSegmentReader.Close() | |
392 | if closeErr != nil { | |
393 | err = closeErr | |
394 | } | |
395 | }() | |
373 | 396 | sizeToRead -= relativeFilePos |
374 | 397 | segmentSize += relativeFilePos |
375 | 398 | readers = []io.Reader{existingSegmentReader} |
383 | 406 | if existingSegment != nil && segmentSize < int(existingSegment.Bytes) { |
384 | 407 | headers := make(Headers) |
385 | 408 | headers["Range"] = "bytes=" + strconv.FormatInt(int64(segmentSize), 10) + "-" |
386 | tailSegmentReader, _, err := file.conn.ObjectOpen(file.segmentContainer, segmentName, true, headers) | |
409 | tailSegmentReader, _, err := file.conn.ObjectOpen(ctx, file.segmentContainer, segmentName, true, headers) | |
387 | 410 | if err != nil { |
388 | 411 | return nil, 0, err |
389 | 412 | } |
390 | defer tailSegmentReader.Close() | |
413 | defer func() { | |
414 | closeErr := tailSegmentReader.Close() | |
415 | if closeErr != nil { | |
416 | err = closeErr | |
417 | } | |
418 | }() | |
391 | 419 | segmentSize = int(existingSegment.Bytes) |
392 | 420 | readers = append(readers, tailSegmentReader) |
393 | 421 | } |
394 | 422 | segmentReader := io.MultiReader(readers...) |
395 | headers, err := file.conn.ObjectPut(file.segmentContainer, segmentName, segmentReader, true, "", file.contentType, nil) | |
423 | headers, err := file.conn.ObjectPut(ctx, file.segmentContainer, segmentName, segmentReader, true, "", file.contentType, nil) | |
396 | 424 | if err != nil { |
397 | 425 | return nil, 0, err |
398 | 426 | } |
415 | 443 | } |
416 | 444 | |
417 | 445 | func (blo *bufferedLargeObjectFile) Close() error { |
446 | return blo.CloseWithContext(context.Background()) | |
447 | } | |
448 | ||
449 | func (blo *bufferedLargeObjectFile) CloseWithContext(ctx context.Context) error { | |
418 | 450 | err := blo.bw.Flush() |
419 | 451 | if err != nil { |
420 | 452 | return err |
421 | 453 | } |
422 | return blo.LargeObjectFile.Close() | |
454 | return blo.LargeObjectFile.CloseWithContext(ctx) | |
455 | } | |
456 | ||
457 | func (blo *bufferedLargeObjectFile) WriteWithContext(_ context.Context, p []byte) (n int, err error) { | |
458 | return blo.Write(p) | |
423 | 459 | } |
424 | 460 | |
425 | 461 | func (blo *bufferedLargeObjectFile) Write(p []byte) (n int, err error) { |
438 | 474 | return blo.LargeObjectFile.Size() + int64(blo.bw.Buffered()) |
439 | 475 | } |
440 | 476 | |
441 | func (blo *bufferedLargeObjectFile) Flush() error { | |
477 | func (blo *bufferedLargeObjectFile) Flush(ctx context.Context) error { | |
442 | 478 | err := blo.bw.Flush() |
443 | 479 | if err != nil { |
444 | 480 | return err |
445 | 481 | } |
446 | return blo.LargeObjectFile.Flush() | |
447 | } | |
482 | return blo.LargeObjectFile.Flush(ctx) | |
483 | } |
0 | 0 | package rs |
1 | 1 | |
2 | 2 | import ( |
3 | "context" | |
3 | 4 | "errors" |
4 | 5 | "net/http" |
5 | 6 | "strconv" |
6 | 7 | |
7 | "github.com/ncw/swift" | |
8 | "github.com/ncw/swift/v2" | |
8 | 9 | ) |
9 | 10 | |
10 | 11 | // RsConnection is a RackSpace specific wrapper to the core swift library which |
15 | 16 | } |
16 | 17 | |
17 | 18 | // manage is similar to the swift storage method, but uses the CDN Management URL for CDN specific calls. |
18 | func (c *RsConnection) manage(p swift.RequestOpts) (resp *http.Response, headers swift.Headers, err error) { | |
19 | func (c *RsConnection) manage(ctx context.Context, p swift.RequestOpts) (resp *http.Response, headers swift.Headers, err error) { | |
19 | 20 | p.OnReAuth = func() (string, error) { |
20 | 21 | if c.cdnUrl == "" { |
21 | 22 | c.cdnUrl = c.Auth.CdnUrl() |
31 | 32 | return nil, nil, err |
32 | 33 | } |
33 | 34 | } |
34 | return c.Connection.Call(c.cdnUrl, p) | |
35 | return c.Connection.Call(ctx, c.cdnUrl, p) | |
35 | 36 | } |
36 | 37 | |
37 | 38 | // ContainerCDNEnable enables a container for public CDN usage. |
39 | 40 | // Change the default TTL of 259200 seconds (72 hours) by passing in an integer value. |
40 | 41 | // |
41 | 42 | // This method can be called again to change the TTL. |
42 | func (c *RsConnection) ContainerCDNEnable(container string, ttl int) (swift.Headers, error) { | |
43 | func (c *RsConnection) ContainerCDNEnable(ctx context.Context, container string, ttl int) (swift.Headers, error) { | |
43 | 44 | h := swift.Headers{"X-CDN-Enabled": "true"} |
44 | 45 | if ttl > 0 { |
45 | 46 | h["X-TTL"] = strconv.Itoa(ttl) |
46 | 47 | } |
47 | 48 | |
48 | _, headers, err := c.manage(swift.RequestOpts{ | |
49 | _, headers, err := c.manage(ctx, swift.RequestOpts{ | |
49 | 50 | Container: container, |
50 | 51 | Operation: "PUT", |
51 | 52 | ErrorMap: swift.ContainerErrorMap, |
56 | 57 | } |
57 | 58 | |
58 | 59 | // ContainerCDNDisable disables CDN access to a container. |
59 | func (c *RsConnection) ContainerCDNDisable(container string) error { | |
60 | func (c *RsConnection) ContainerCDNDisable(ctx context.Context, container string) error { | |
60 | 61 | h := swift.Headers{"X-CDN-Enabled": "false"} |
61 | 62 | |
62 | _, _, err := c.manage(swift.RequestOpts{ | |
63 | _, _, err := c.manage(ctx, swift.RequestOpts{ | |
63 | 64 | Container: container, |
64 | 65 | Operation: "PUT", |
65 | 66 | ErrorMap: swift.ContainerErrorMap, |
70 | 71 | } |
71 | 72 | |
72 | 73 | // ContainerCDNMeta returns the CDN metadata for a container. |
73 | func (c *RsConnection) ContainerCDNMeta(container string) (swift.Headers, error) { | |
74 | _, headers, err := c.manage(swift.RequestOpts{ | |
74 | func (c *RsConnection) ContainerCDNMeta(ctx context.Context, container string) (swift.Headers, error) { | |
75 | _, headers, err := c.manage(ctx, swift.RequestOpts{ | |
75 | 76 | Container: container, |
76 | 77 | Operation: "HEAD", |
77 | 78 | ErrorMap: swift.ContainerErrorMap, |
1 | 1 | package rs_test |
2 | 2 | |
3 | 3 | import ( |
4 | "context" | |
4 | 5 | "os" |
5 | 6 | "testing" |
6 | 7 | |
7 | "github.com/ncw/swift/rs" | |
8 | "github.com/ncw/swift/v2/rs" | |
8 | 9 | ) |
9 | 10 | |
10 | 11 | var ( |
31 | 32 | c.UserName = UserName |
32 | 33 | c.ApiKey = ApiKey |
33 | 34 | c.AuthUrl = AuthUrl |
34 | err := c.Authenticate() | |
35 | err := c.Authenticate(context.Background()) | |
35 | 36 | if err != nil { |
36 | 37 | t.Fatal("Auth failed", err) |
37 | 38 | } |
42 | 43 | |
43 | 44 | // Setup |
44 | 45 | func TestContainerCreate(t *testing.T) { |
45 | err := c.ContainerCreate(CONTAINER, nil) | |
46 | err := c.ContainerCreate(context.Background(), CONTAINER, nil) | |
46 | 47 | if err != nil { |
47 | 48 | t.Fatal(err) |
48 | 49 | } |
49 | 50 | } |
50 | 51 | |
51 | 52 | func TestCDNEnable(t *testing.T) { |
52 | headers, err := c.ContainerCDNEnable(CONTAINER, 0) | |
53 | headers, err := c.ContainerCDNEnable(context.Background(), CONTAINER, 0) | |
53 | 54 | if err != nil { |
54 | 55 | t.Error(err) |
55 | 56 | } |
63 | 64 | c2.UserName = c.UserName |
64 | 65 | c2.ApiKey = c.ApiKey |
65 | 66 | c2.AuthUrl = c.AuthUrl |
66 | _, err := c2.ContainerCDNEnable(CONTAINER, 0) | |
67 | _, err := c2.ContainerCDNEnable(context.Background(), CONTAINER, 0) | |
67 | 68 | if err != nil { |
68 | 69 | t.Fatalf("Failed to reauthenticate: %v", err) |
69 | 70 | } |
70 | 71 | } |
71 | 72 | |
72 | 73 | func TestCDNMeta(t *testing.T) { |
73 | headers, err := c.ContainerCDNMeta(CONTAINER) | |
74 | headers, err := c.ContainerCDNMeta(context.Background(), CONTAINER) | |
74 | 75 | if err != nil { |
75 | 76 | t.Error(err) |
76 | 77 | } |
80 | 81 | } |
81 | 82 | |
82 | 83 | func TestCDNDisable(t *testing.T) { |
83 | err := c.ContainerCDNDisable(CONTAINER) // files stick in CDN until TTL expires | |
84 | err := c.ContainerCDNDisable(context.Background(), CONTAINER) // files stick in CDN until TTL expires | |
84 | 85 | if err != nil { |
85 | 86 | t.Error(err) |
86 | 87 | } |
88 | 89 | |
89 | 90 | // Teardown |
90 | 91 | func TestContainerDelete(t *testing.T) { |
91 | err := c.ContainerDelete(CONTAINER) | |
92 | err := c.ContainerDelete(context.Background(), CONTAINER) | |
92 | 93 | if err != nil { |
93 | 94 | t.Fatal(err) |
94 | 95 | } |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "bytes" |
4 | "context" | |
4 | 5 | "encoding/json" |
5 | 6 | "errors" |
6 | 7 | "fmt" |
36 | 37 | // an object which satisfies io.Writer, io.Seeker, io.Closer and |
37 | 38 | // io.ReaderFrom. The flags are as passed to the largeObjectCreate |
38 | 39 | // method. |
39 | func (c *Connection) StaticLargeObjectCreateFile(opts *LargeObjectOpts) (LargeObjectFile, error) { | |
40 | info, err := c.cachedQueryInfo() | |
40 | func (c *Connection) StaticLargeObjectCreateFile(ctx context.Context, opts *LargeObjectOpts) (LargeObjectFile, error) { | |
41 | info, err := c.cachedQueryInfo(ctx) | |
41 | 42 | if err != nil || !info.SupportsSLO() { |
42 | 43 | return nil, SLONotSupported |
43 | 44 | } |
45 | 46 | if realMinChunkSize > opts.MinChunkSize { |
46 | 47 | opts.MinChunkSize = realMinChunkSize |
47 | 48 | } |
48 | lo, err := c.largeObjectCreate(opts) | |
49 | lo, err := c.largeObjectCreate(ctx, opts) | |
49 | 50 | if err != nil { |
50 | 51 | return nil, err |
51 | 52 | } |
57 | 58 | // StaticLargeObjectCreate creates or truncates an existing static |
58 | 59 | // large object returning a writeable object. This sets opts.Flags to |
59 | 60 | // an appropriate value before calling StaticLargeObjectCreateFile |
60 | func (c *Connection) StaticLargeObjectCreate(opts *LargeObjectOpts) (LargeObjectFile, error) { | |
61 | func (c *Connection) StaticLargeObjectCreate(ctx context.Context, opts *LargeObjectOpts) (LargeObjectFile, error) { | |
61 | 62 | opts.Flags = os.O_TRUNC | os.O_CREATE |
62 | return c.StaticLargeObjectCreateFile(opts) | |
63 | return c.StaticLargeObjectCreateFile(ctx, opts) | |
63 | 64 | } |
64 | 65 | |
65 | 66 | // StaticLargeObjectDelete deletes a static large object and all of its segments. |
66 | func (c *Connection) StaticLargeObjectDelete(container string, path string) error { | |
67 | info, err := c.cachedQueryInfo() | |
67 | func (c *Connection) StaticLargeObjectDelete(ctx context.Context, container string, path string) error { | |
68 | info, err := c.cachedQueryInfo(ctx) | |
68 | 69 | if err != nil || !info.SupportsSLO() { |
69 | 70 | return SLONotSupported |
70 | 71 | } |
71 | return c.LargeObjectDelete(container, path) | |
72 | return c.LargeObjectDelete(ctx, container, path) | |
72 | 73 | } |
73 | 74 | |
74 | 75 | // StaticLargeObjectMove moves a static large object from srcContainer, srcObjectName to dstContainer, dstObjectName |
75 | func (c *Connection) StaticLargeObjectMove(srcContainer string, srcObjectName string, dstContainer string, dstObjectName string) error { | |
76 | swiftInfo, err := c.cachedQueryInfo() | |
76 | func (c *Connection) StaticLargeObjectMove(ctx context.Context, srcContainer string, srcObjectName string, dstContainer string, dstObjectName string) error { | |
77 | swiftInfo, err := c.cachedQueryInfo(ctx) | |
77 | 78 | if err != nil || !swiftInfo.SupportsSLO() { |
78 | 79 | return SLONotSupported |
79 | 80 | } |
80 | info, headers, err := c.Object(srcContainer, srcObjectName) | |
81 | info, headers, err := c.Object(ctx, srcContainer, srcObjectName) | |
81 | 82 | if err != nil { |
82 | 83 | return err |
83 | 84 | } |
84 | 85 | |
85 | container, segments, err := c.getAllSegments(srcContainer, srcObjectName, headers) | |
86 | container, segments, err := c.getAllSegments(ctx, srcContainer, srcObjectName, headers) | |
86 | 87 | if err != nil { |
87 | 88 | return err |
88 | 89 | } |
90 | 91 | //copy only metadata during move (other headers might not be safe for copying) |
91 | 92 | headers = headers.ObjectMetadata().ObjectHeaders() |
92 | 93 | |
93 | if err := c.createSLOManifest(dstContainer, dstObjectName, info.ContentType, container, segments, headers); err != nil { | |
94 | if err := c.createSLOManifest(ctx, dstContainer, dstObjectName, info.ContentType, container, segments, headers); err != nil { | |
94 | 95 | return err |
95 | 96 | } |
96 | 97 | |
97 | if err := c.ObjectDelete(srcContainer, srcObjectName); err != nil { | |
98 | if err := c.ObjectDelete(ctx, srcContainer, srcObjectName); err != nil { | |
98 | 99 | return err |
99 | 100 | } |
100 | 101 | |
102 | 103 | } |
103 | 104 | |
104 | 105 | // createSLOManifest creates a static large object manifest |
105 | func (c *Connection) createSLOManifest(container string, path string, contentType string, segmentContainer string, segments []Object, h Headers) error { | |
106 | func (c *Connection) createSLOManifest(ctx context.Context, container string, path string, contentType string, segmentContainer string, segments []Object, h Headers) error { | |
106 | 107 | sloSegments := make([]swiftSegment, len(segments)) |
107 | 108 | for i, segment := range segments { |
108 | 109 | sloSegments[i].Path = fmt.Sprintf("%s/%s", segmentContainer, segment.Name) |
117 | 118 | |
118 | 119 | values := url.Values{} |
119 | 120 | values.Set("multipart-manifest", "put") |
120 | if _, err := c.objectPut(container, path, bytes.NewBuffer(content), false, "", contentType, h, values); err != nil { | |
121 | if _, err := c.objectPut(ctx, container, path, bytes.NewBuffer(content), false, "", contentType, h, values); err != nil { | |
121 | 122 | return err |
122 | 123 | } |
123 | 124 | |
125 | 126 | } |
126 | 127 | |
127 | 128 | func (file *StaticLargeObjectCreateFile) Close() error { |
128 | return file.Flush() | |
129 | return file.CloseWithContext(context.Background()) | |
129 | 130 | } |
130 | 131 | |
131 | func (file *StaticLargeObjectCreateFile) Flush() error { | |
132 | if err := file.conn.createSLOManifest(file.container, file.objectName, file.contentType, file.segmentContainer, file.segments, file.headers); err != nil { | |
132 | func (file *StaticLargeObjectCreateFile) CloseWithContext(ctx context.Context) error { | |
133 | return file.Flush(ctx) | |
134 | } | |
135 | ||
136 | func (file *StaticLargeObjectCreateFile) Flush(ctx context.Context) error { | |
137 | if err := file.conn.createSLOManifest(ctx, file.container, file.objectName, file.contentType, file.segmentContainer, file.segments, file.headers); err != nil { | |
133 | 138 | return err |
134 | 139 | } |
135 | return file.conn.waitForSegmentsToShowUp(file.container, file.objectName, file.Size()) | |
140 | return file.conn.waitForSegmentsToShowUp(ctx, file.container, file.objectName, file.Size()) | |
136 | 141 | } |
137 | 142 | |
138 | func (c *Connection) getAllSLOSegments(container, path string) (string, []Object, error) { | |
143 | func (c *Connection) getAllSLOSegments(ctx context.Context, container, path string) (string, []Object, error) { | |
139 | 144 | var ( |
140 | 145 | segmentList []swiftSegment |
141 | 146 | segments []Object |
146 | 151 | values := url.Values{} |
147 | 152 | values.Set("multipart-manifest", "get") |
148 | 153 | |
149 | file, _, err := c.objectOpen(container, path, true, nil, values) | |
154 | file, _, err := c.objectOpen(ctx, container, path, true, nil, values) | |
150 | 155 | if err != nil { |
151 | 156 | return "", nil, err |
152 | 157 | } |
156 | 161 | return "", nil, err |
157 | 162 | } |
158 | 163 | |
159 | json.Unmarshal(content, &segmentList) | |
164 | err = json.Unmarshal(content, &segmentList) | |
165 | if err != nil { | |
166 | return "", nil, err | |
167 | } | |
160 | 168 | for _, segment := range segmentList { |
161 | segmentContainer, segPath = parseFullPath(segment.Name[1:]) | |
169 | segmentContainer, segPath, err = parseFullPath(segment.Name[1:]) | |
170 | if err != nil { | |
171 | return "", nil, err | |
172 | } | |
162 | 173 | segments = append(segments, Object{ |
163 | 174 | Name: segPath, |
164 | 175 | Bytes: segment.Bytes, |
2 | 2 | import ( |
3 | 3 | "bufio" |
4 | 4 | "bytes" |
5 | "context" | |
5 | 6 | "crypto/hmac" |
6 | 7 | "crypto/md5" |
7 | 8 | "crypto/sha1" |
30 | 31 | UploadTarGzip = "tar.gz" // Data format specifier for Connection.BulkUpload(). |
31 | 32 | UploadTarBzip2 = "tar.bz2" // Data format specifier for Connection.BulkUpload(). |
32 | 33 | allContainersLimit = 10000 // Number of containers to fetch at once |
33 | allObjectsLimit = 10000 // Number objects to fetch at once | |
34 | allObjectsChanLimit = 1000 // ...when fetching to a channel | |
34 | allObjectsChanLimit = 1000 // Number objects to fetch when fetching to a channel | |
35 | 35 | ) |
36 | 36 | |
37 | 37 | // ObjectType is the type of the swift object, regular, static large, |
71 | 71 | // import ( |
72 | 72 | // "appengine/urlfetch" |
73 | 73 | // "fmt" |
74 | // "github.com/ncw/swift" | |
74 | // "github.com/ncw/swift/v2" | |
75 | 75 | // ) |
76 | 76 | // |
77 | 77 | // func handler(w http.ResponseWriter, r *http.Request) { |
124 | 124 | Expires time.Time // time the token expires, may be Zero if unknown |
125 | 125 | client *http.Client |
126 | 126 | Auth Authenticator `json:"-" xml:"-"` // the current authenticator |
127 | authLock *sync.Mutex // lock when R/W StorageUrl, AuthToken, Auth | |
127 | authLock sync.Mutex // lock when R/W StorageUrl, AuthToken, Auth | |
128 | 128 | // swiftInfo is filled after QueryInfo is called |
129 | 129 | swiftInfo SwiftInfo |
130 | // Workarounds for non-compliant servers that don't always return opts.Limit items per page | |
131 | FetchUntilEmptyPage bool // Always fetch unless we received an empty page | |
132 | PartialPageFetchThreshold int // Fetch if the current page is this percentage of opts.Limit | |
130 | 133 | } |
131 | 134 | |
132 | 135 | // setFromEnv reads the value that param points to (it must be a |
385 | 388 | func readHeaders(resp *http.Response) Headers { |
386 | 389 | headers := Headers{} |
387 | 390 | for key, values := range resp.Header { |
388 | headers[key] = values[0] | |
391 | // ETag header may be double quoted if following RFC 7232 | |
392 | // https://github.com/openstack/swift/blob/2.24.0/CHANGELOG#L9 | |
393 | if key == "Etag" { | |
394 | headers[key] = strings.Trim(values[0], "\"") | |
395 | } else { | |
396 | headers[key] = values[0] | |
397 | } | |
389 | 398 | } |
390 | 399 | return headers |
391 | 400 | } |
414 | 423 | cancelRequest(c.Transport, req) |
415 | 424 | return nil, TimeoutError |
416 | 425 | } |
417 | panic("unreachable") // For Go 1.0 | |
418 | 426 | } |
419 | 427 | |
420 | 428 | // Set defaults for any unset values |
456 | 464 | // |
457 | 465 | // If you don't call it before calling one of the connection methods |
458 | 466 | // then it will be called for you on the first access. |
459 | func (c *Connection) Authenticate() (err error) { | |
460 | if c.authLock == nil { | |
461 | c.authLock = &sync.Mutex{} | |
462 | } | |
467 | func (c *Connection) Authenticate(ctx context.Context) (err error) { | |
463 | 468 | c.authLock.Lock() |
464 | 469 | defer c.authLock.Unlock() |
465 | return c.authenticate() | |
470 | return c.authenticate(ctx) | |
466 | 471 | } |
467 | 472 | |
468 | 473 | // Internal implementation of Authenticate |
469 | 474 | // |
470 | 475 | // Call with authLock held |
471 | func (c *Connection) authenticate() (err error) { | |
476 | func (c *Connection) authenticate(ctx context.Context) (err error) { | |
472 | 477 | c.setDefaults() |
473 | 478 | |
474 | 479 | // Flush the keepalives connection - if we are |
485 | 490 | retries := 1 |
486 | 491 | again: |
487 | 492 | var req *http.Request |
488 | req, err = c.Auth.Request(c) | |
493 | req, err = c.Auth.Request(ctx, c) | |
489 | 494 | if err != nil { |
490 | 495 | return |
491 | 496 | } |
513 | 518 | } |
514 | 519 | return |
515 | 520 | } |
516 | err = c.Auth.Response(resp) | |
521 | err = c.Auth.Response(ctx, resp) | |
517 | 522 | if err != nil { |
518 | 523 | return |
519 | 524 | } |
540 | 545 | // Get an authToken and url |
541 | 546 | // |
542 | 547 | // The Url may be updated if it needed to authenticate using the OnReAuth function |
543 | func (c *Connection) getUrlAndAuthToken(targetUrlIn string, OnReAuth func() (string, error)) (targetUrlOut, authToken string, err error) { | |
548 | func (c *Connection) getUrlAndAuthToken(ctx context.Context, targetUrlIn string, OnReAuth func() (string, error)) (targetUrlOut, authToken string, err error) { | |
544 | 549 | c.authLock.Lock() |
545 | 550 | defer c.authLock.Unlock() |
546 | 551 | targetUrlOut = targetUrlIn |
547 | 552 | if !c.authenticated() { |
548 | err = c.authenticate() | |
553 | err = c.authenticate(ctx) | |
549 | 554 | if err != nil { |
550 | 555 | return |
551 | 556 | } |
582 | 587 | // |
583 | 588 | // Doesn't actually check the credentials against the server. |
584 | 589 | func (c *Connection) Authenticated() bool { |
585 | if c.authLock == nil { | |
586 | c.authLock = &sync.Mutex{} | |
587 | } | |
588 | 590 | c.authLock.Lock() |
589 | 591 | defer c.authLock.Unlock() |
590 | 592 | return c.authenticated() |
628 | 630 | } |
629 | 631 | |
630 | 632 | // Discover Swift configuration by doing a request against /info |
631 | func (c *Connection) QueryInfo() (infos SwiftInfo, err error) { | |
632 | infoUrl, err := url.Parse(c.StorageUrl) | |
633 | func (c *Connection) QueryInfo(ctx context.Context) (infos SwiftInfo, err error) { | |
634 | storageUrl, err := c.GetStorageUrl(ctx) | |
633 | 635 | if err != nil { |
634 | 636 | return nil, err |
635 | 637 | } |
638 | infoUrl, err := url.Parse(storageUrl) | |
639 | if err != nil { | |
640 | return nil, err | |
641 | } | |
636 | 642 | infoUrl.Path = path.Join(infoUrl.Path, "..", "..", "info") |
637 | resp, err := c.client.Get(infoUrl.String()) | |
643 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, infoUrl.String(), nil) | |
644 | if err != nil { | |
645 | return nil, err | |
646 | } | |
647 | resp, err := c.client.Do(req) | |
638 | 648 | if err == nil { |
639 | 649 | if resp.StatusCode != http.StatusOK { |
640 | 650 | drainAndClose(resp.Body, nil) |
651 | 661 | return nil, err |
652 | 662 | } |
653 | 663 | |
654 | func (c *Connection) cachedQueryInfo() (infos SwiftInfo, err error) { | |
664 | func (c *Connection) cachedQueryInfo(ctx context.Context) (infos SwiftInfo, err error) { | |
655 | 665 | c.authLock.Lock() |
656 | 666 | infos = c.swiftInfo |
657 | 667 | c.authLock.Unlock() |
658 | 668 | if infos == nil { |
659 | infos, err = c.QueryInfo() | |
669 | infos, err = c.QueryInfo(ctx) | |
660 | 670 | if err != nil { |
661 | 671 | return |
662 | 672 | } |
699 | 709 | // receives a 401 error which means the token has expired |
700 | 710 | // |
701 | 711 | // This method is exported so extensions can call it. |
702 | func (c *Connection) Call(targetUrl string, p RequestOpts) (resp *http.Response, headers Headers, err error) { | |
712 | func (c *Connection) Call(ctx context.Context, targetUrl string, p RequestOpts) (resp *http.Response, headers Headers, err error) { | |
703 | 713 | c.authLock.Lock() |
704 | 714 | c.setDefaults() |
705 | 715 | c.authLock.Unlock() |
710 | 720 | var req *http.Request |
711 | 721 | for { |
712 | 722 | var authToken string |
713 | if targetUrl, authToken, err = c.getUrlAndAuthToken(targetUrl, p.OnReAuth); err != nil { | |
723 | if targetUrl, authToken, err = c.getUrlAndAuthToken(ctx, targetUrl, p.OnReAuth); err != nil { | |
714 | 724 | return //authentication failure |
715 | 725 | } |
716 | 726 | var URL *url.URL |
733 | 743 | if reader != nil { |
734 | 744 | reader = newWatchdogReader(reader, c.Timeout, timer) |
735 | 745 | } |
736 | req, err = http.NewRequest(p.Operation, URL.String(), reader) | |
746 | req, err = http.NewRequestWithContext(ctx, p.Operation, URL.String(), reader) | |
737 | 747 | if err != nil { |
738 | 748 | return |
739 | 749 | } |
808 | 818 | // |
809 | 819 | // This will Authenticate if necessary, and re-authenticate if it |
810 | 820 | // receives a 401 error which means the token has expired |
811 | func (c *Connection) storage(p RequestOpts) (resp *http.Response, headers Headers, err error) { | |
821 | func (c *Connection) storage(ctx context.Context, p RequestOpts) (resp *http.Response, headers Headers, err error) { | |
812 | 822 | p.OnReAuth = func() (string, error) { |
813 | 823 | return c.StorageUrl, nil |
814 | 824 | } |
815 | 825 | c.authLock.Lock() |
816 | 826 | url := c.StorageUrl |
817 | 827 | c.authLock.Unlock() |
818 | return c.Call(url, p) | |
828 | return c.Call(ctx, url, p) | |
819 | 829 | } |
820 | 830 | |
821 | 831 | // readLines reads the response into an array of strings. |
886 | 896 | } |
887 | 897 | |
888 | 898 | // ContainerNames returns a slice of names of containers in this account. |
889 | func (c *Connection) ContainerNames(opts *ContainersOpts) ([]string, error) { | |
899 | func (c *Connection) ContainerNames(ctx context.Context, opts *ContainersOpts) ([]string, error) { | |
890 | 900 | v, h := opts.parse() |
891 | resp, _, err := c.storage(RequestOpts{ | |
901 | resp, _, err := c.storage(ctx, RequestOpts{ | |
892 | 902 | Operation: "GET", |
893 | 903 | Parameters: v, |
894 | 904 | ErrorMap: ContainerErrorMap, |
910 | 920 | |
911 | 921 | // Containers returns a slice of structures with full information as |
912 | 922 | // described in Container. |
913 | func (c *Connection) Containers(opts *ContainersOpts) ([]Container, error) { | |
923 | func (c *Connection) Containers(ctx context.Context, opts *ContainersOpts) ([]Container, error) { | |
914 | 924 | v, h := opts.parse() |
915 | 925 | v.Set("format", "json") |
916 | resp, _, err := c.storage(RequestOpts{ | |
926 | resp, _, err := c.storage(ctx, RequestOpts{ | |
917 | 927 | Operation: "GET", |
918 | 928 | Parameters: v, |
919 | 929 | ErrorMap: ContainerErrorMap, |
941 | 951 | return &newOpts |
942 | 952 | } |
943 | 953 | |
954 | func (c *Connection) isLastPage(length int, limit int) bool { | |
955 | if c.FetchUntilEmptyPage && length > 0 { | |
956 | return false | |
957 | } | |
958 | if c.PartialPageFetchThreshold > 0 && limit > 0 { | |
959 | if length*100/limit >= c.PartialPageFetchThreshold { | |
960 | return false | |
961 | } | |
962 | } | |
963 | if length < limit { | |
964 | return true | |
965 | } | |
966 | return false | |
967 | } | |
968 | ||
944 | 969 | // ContainersAll is like Containers but it returns all the Containers |
945 | 970 | // |
946 | 971 | // It calls Containers multiple times using the Marker parameter |
947 | 972 | // |
948 | 973 | // It has a default Limit parameter but you may pass in your own |
949 | func (c *Connection) ContainersAll(opts *ContainersOpts) ([]Container, error) { | |
974 | func (c *Connection) ContainersAll(ctx context.Context, opts *ContainersOpts) ([]Container, error) { | |
950 | 975 | opts = containersAllOpts(opts) |
951 | 976 | containers := make([]Container, 0) |
952 | 977 | for { |
953 | newContainers, err := c.Containers(opts) | |
978 | newContainers, err := c.Containers(ctx, opts) | |
954 | 979 | if err != nil { |
955 | 980 | return nil, err |
956 | 981 | } |
957 | 982 | containers = append(containers, newContainers...) |
958 | if len(newContainers) < opts.Limit { | |
983 | if c.isLastPage(len(newContainers), opts.Limit) { | |
959 | 984 | break |
960 | 985 | } |
961 | 986 | opts.Marker = newContainers[len(newContainers)-1].Name |
968 | 993 | // It calls ContainerNames multiple times using the Marker parameter |
969 | 994 | // |
970 | 995 | // It has a default Limit parameter but you may pass in your own |
971 | func (c *Connection) ContainerNamesAll(opts *ContainersOpts) ([]string, error) { | |
996 | func (c *Connection) ContainerNamesAll(ctx context.Context, opts *ContainersOpts) ([]string, error) { | |
972 | 997 | opts = containersAllOpts(opts) |
973 | 998 | containers := make([]string, 0) |
974 | 999 | for { |
975 | newContainers, err := c.ContainerNames(opts) | |
1000 | newContainers, err := c.ContainerNames(ctx, opts) | |
976 | 1001 | if err != nil { |
977 | 1002 | return nil, err |
978 | 1003 | } |
979 | 1004 | containers = append(containers, newContainers...) |
980 | if len(newContainers) < opts.Limit { | |
1005 | if c.isLastPage(len(newContainers), opts.Limit) { | |
981 | 1006 | break |
982 | 1007 | } |
983 | 1008 | opts.Marker = newContainers[len(newContainers)-1] |
1028 | 1053 | } |
1029 | 1054 | |
1030 | 1055 | // ObjectNames returns a slice of names of objects in a given container. |
1031 | func (c *Connection) ObjectNames(container string, opts *ObjectsOpts) ([]string, error) { | |
1056 | func (c *Connection) ObjectNames(ctx context.Context, container string, opts *ObjectsOpts) ([]string, error) { | |
1032 | 1057 | v, h := opts.parse() |
1033 | resp, _, err := c.storage(RequestOpts{ | |
1058 | resp, _, err := c.storage(ctx, RequestOpts{ | |
1034 | 1059 | Container: container, |
1035 | 1060 | Operation: "GET", |
1036 | 1061 | Parameters: v, |
1064 | 1089 | // with ContentType 'application/directory'. These are not real |
1065 | 1090 | // objects but represent directories of objects which haven't had an |
1066 | 1091 | // object created for them. |
1067 | func (c *Connection) Objects(container string, opts *ObjectsOpts) ([]Object, error) { | |
1092 | func (c *Connection) Objects(ctx context.Context, container string, opts *ObjectsOpts) ([]Object, error) { | |
1068 | 1093 | v, h := opts.parse() |
1069 | 1094 | v.Set("format", "json") |
1070 | resp, _, err := c.storage(RequestOpts{ | |
1095 | resp, _, err := c.storage(ctx, RequestOpts{ | |
1071 | 1096 | Container: container, |
1072 | 1097 | Operation: "GET", |
1073 | 1098 | Parameters: v, |
1129 | 1154 | |
1130 | 1155 | // A closure defined by the caller to iterate through all objects |
1131 | 1156 | // |
1132 | // Call Objects or ObjectNames from here with the *ObjectOpts passed in | |
1157 | // Call Objects or ObjectNames from here with the context.Context and *ObjectOpts passed in | |
1133 | 1158 | // |
1134 | 1159 | // Do whatever is required with the results then return them |
1135 | type ObjectsWalkFn func(*ObjectsOpts) (interface{}, error) | |
1160 | type ObjectsWalkFn func(context.Context, *ObjectsOpts) (interface{}, error) | |
1136 | 1161 | |
1137 | 1162 | // ObjectsWalk is uses to iterate through all the objects in chunks as |
1138 | 1163 | // returned by Objects or ObjectNames using the Marker and Limit |
1144 | 1169 | // Errors will be returned from this function |
1145 | 1170 | // |
1146 | 1171 | // It has a default Limit parameter but you may pass in your own |
1147 | func (c *Connection) ObjectsWalk(container string, opts *ObjectsOpts, walkFn ObjectsWalkFn) error { | |
1172 | func (c *Connection) ObjectsWalk(ctx context.Context, container string, opts *ObjectsOpts, walkFn ObjectsWalkFn) error { | |
1148 | 1173 | opts = objectsAllOpts(opts, allObjectsChanLimit) |
1149 | 1174 | for { |
1150 | objects, err := walkFn(opts) | |
1175 | objects, err := walkFn(ctx, opts) | |
1151 | 1176 | if err != nil { |
1152 | 1177 | return err |
1153 | 1178 | } |
1167 | 1192 | default: |
1168 | 1193 | panic("Unknown type returned to ObjectsWalk") |
1169 | 1194 | } |
1170 | if n < opts.Limit { | |
1195 | if c.isLastPage(n, opts.Limit) { | |
1171 | 1196 | break |
1172 | 1197 | } |
1173 | 1198 | opts.Marker = last |
1178 | 1203 | // ObjectsAll is like Objects but it returns an unlimited number of Objects in a slice |
1179 | 1204 | // |
1180 | 1205 | // It calls Objects multiple times using the Marker parameter |
1181 | func (c *Connection) ObjectsAll(container string, opts *ObjectsOpts) ([]Object, error) { | |
1206 | func (c *Connection) ObjectsAll(ctx context.Context, container string, opts *ObjectsOpts) ([]Object, error) { | |
1182 | 1207 | objects := make([]Object, 0) |
1183 | err := c.ObjectsWalk(container, opts, func(opts *ObjectsOpts) (interface{}, error) { | |
1184 | newObjects, err := c.Objects(container, opts) | |
1208 | err := c.ObjectsWalk(ctx, container, opts, func(ctx context.Context, opts *ObjectsOpts) (interface{}, error) { | |
1209 | newObjects, err := c.Objects(ctx, container, opts) | |
1185 | 1210 | if err == nil { |
1186 | 1211 | objects = append(objects, newObjects...) |
1187 | 1212 | } |
1196 | 1221 | // reset unless KeepMarker is set |
1197 | 1222 | // |
1198 | 1223 | // It has a default Limit parameter but you may pass in your own |
1199 | func (c *Connection) ObjectNamesAll(container string, opts *ObjectsOpts) ([]string, error) { | |
1224 | func (c *Connection) ObjectNamesAll(ctx context.Context, container string, opts *ObjectsOpts) ([]string, error) { | |
1200 | 1225 | objects := make([]string, 0) |
1201 | err := c.ObjectsWalk(container, opts, func(opts *ObjectsOpts) (interface{}, error) { | |
1202 | newObjects, err := c.ObjectNames(container, opts) | |
1226 | err := c.ObjectsWalk(ctx, container, opts, func(ctx context.Context, opts *ObjectsOpts) (interface{}, error) { | |
1227 | newObjects, err := c.ObjectNames(ctx, container, opts) | |
1203 | 1228 | if err == nil { |
1204 | 1229 | objects = append(objects, newObjects...) |
1205 | 1230 | } |
1226 | 1251 | } |
1227 | 1252 | |
1228 | 1253 | // Account returns info about the account in an Account struct. |
1229 | func (c *Connection) Account() (info Account, headers Headers, err error) { | |
1254 | func (c *Connection) Account(ctx context.Context) (info Account, headers Headers, err error) { | |
1230 | 1255 | var resp *http.Response |
1231 | resp, headers, err = c.storage(RequestOpts{ | |
1256 | resp, headers, err = c.storage(ctx, RequestOpts{ | |
1232 | 1257 | Operation: "HEAD", |
1233 | 1258 | ErrorMap: ContainerErrorMap, |
1234 | 1259 | NoResponse: true, |
1261 | 1286 | // Add or update keys by mentioning them in the Headers. |
1262 | 1287 | // |
1263 | 1288 | // Remove keys by setting them to an empty string. |
1264 | func (c *Connection) AccountUpdate(h Headers) error { | |
1265 | _, _, err := c.storage(RequestOpts{ | |
1289 | func (c *Connection) AccountUpdate(ctx context.Context, h Headers) error { | |
1290 | _, _, err := c.storage(ctx, RequestOpts{ | |
1266 | 1291 | Operation: "POST", |
1267 | 1292 | ErrorMap: ContainerErrorMap, |
1268 | 1293 | NoResponse: true, |
1276 | 1301 | // If you don't want to add Headers just pass in nil |
1277 | 1302 | // |
1278 | 1303 | // No error is returned if it already exists but the metadata if any will be updated. |
1279 | func (c *Connection) ContainerCreate(container string, h Headers) error { | |
1280 | _, _, err := c.storage(RequestOpts{ | |
1304 | func (c *Connection) ContainerCreate(ctx context.Context, container string, h Headers) error { | |
1305 | _, _, err := c.storage(ctx, RequestOpts{ | |
1281 | 1306 | Container: container, |
1282 | 1307 | Operation: "PUT", |
1283 | 1308 | ErrorMap: ContainerErrorMap, |
1290 | 1315 | // ContainerDelete deletes a container. |
1291 | 1316 | // |
1292 | 1317 | // May return ContainerDoesNotExist or ContainerNotEmpty |
1293 | func (c *Connection) ContainerDelete(container string) error { | |
1294 | _, _, err := c.storage(RequestOpts{ | |
1318 | func (c *Connection) ContainerDelete(ctx context.Context, container string) error { | |
1319 | _, _, err := c.storage(ctx, RequestOpts{ | |
1295 | 1320 | Container: container, |
1296 | 1321 | Operation: "DELETE", |
1297 | 1322 | ErrorMap: ContainerErrorMap, |
1302 | 1327 | |
1303 | 1328 | // Container returns info about a single container including any |
1304 | 1329 | // metadata in the headers. |
1305 | func (c *Connection) Container(container string) (info Container, headers Headers, err error) { | |
1330 | func (c *Connection) Container(ctx context.Context, container string) (info Container, headers Headers, err error) { | |
1306 | 1331 | var resp *http.Response |
1307 | resp, headers, err = c.storage(RequestOpts{ | |
1332 | resp, headers, err = c.storage(ctx, RequestOpts{ | |
1308 | 1333 | Container: container, |
1309 | 1334 | Operation: "HEAD", |
1310 | 1335 | ErrorMap: ContainerErrorMap, |
1331 | 1356 | // Remove keys by setting them to an empty string. |
1332 | 1357 | // |
1333 | 1358 | // Container metadata can only be read with Container() not with Containers(). |
1334 | func (c *Connection) ContainerUpdate(container string, h Headers) error { | |
1335 | _, _, err := c.storage(RequestOpts{ | |
1359 | func (c *Connection) ContainerUpdate(ctx context.Context, container string, h Headers) error { | |
1360 | _, _, err := c.storage(ctx, RequestOpts{ | |
1336 | 1361 | Container: container, |
1337 | 1362 | Operation: "POST", |
1338 | 1363 | ErrorMap: ContainerErrorMap, |
1468 | 1493 | // |
1469 | 1494 | // If contentType is set it will be used, otherwise one will be |
1470 | 1495 | // guessed from objectName using mime.TypeByExtension |
1471 | func (c *Connection) ObjectCreate(container string, objectName string, checkHash bool, Hash string, contentType string, h Headers) (file *ObjectCreateFile, err error) { | |
1496 | func (c *Connection) ObjectCreate(ctx context.Context, container string, objectName string, checkHash bool, Hash string, contentType string, h Headers) (file *ObjectCreateFile, err error) { | |
1472 | 1497 | extraHeaders := objectPutHeaders(objectName, &checkHash, Hash, contentType, h) |
1473 | 1498 | pipeReader, pipeWriter := io.Pipe() |
1474 | 1499 | file = &ObjectCreateFile{ |
1489 | 1514 | NoResponse: true, |
1490 | 1515 | ErrorMap: objectErrorMap, |
1491 | 1516 | } |
1492 | file.resp, file.headers, file.err = c.storage(opts) | |
1517 | file.resp, file.headers, file.err = c.storage(ctx, opts) | |
1493 | 1518 | // Signal finished |
1494 | pipeReader.Close() | |
1519 | _ = pipeReader.Close() | |
1495 | 1520 | close(file.done) |
1496 | 1521 | }() |
1497 | 1522 | return |
1498 | 1523 | } |
1499 | 1524 | |
1500 | func (c *Connection) ObjectSymlinkCreate(container string, symlink string, targetAccount string, targetContainer string, targetObject string, targetEtag string) (headers Headers, err error) { | |
1525 | func (c *Connection) ObjectSymlinkCreate(ctx context.Context, container string, symlink string, targetAccount string, targetContainer string, targetObject string, targetEtag string) (headers Headers, err error) { | |
1501 | 1526 | |
1502 | 1527 | EMPTY_MD5 := "d41d8cd98f00b204e9800998ecf8427e" |
1503 | 1528 | symHeaders := Headers{} |
1509 | 1534 | symHeaders["X-Symlink-Target-Etag"] = targetEtag |
1510 | 1535 | } |
1511 | 1536 | symHeaders["X-Symlink-Target"] = fmt.Sprintf("%s/%s", targetContainer, targetObject) |
1512 | _, err = c.ObjectPut(container, symlink, contents, true, EMPTY_MD5, "application/symlink", symHeaders) | |
1513 | return | |
1514 | } | |
1515 | ||
1516 | func (c *Connection) objectPut(container string, objectName string, contents io.Reader, checkHash bool, Hash string, contentType string, h Headers, parameters url.Values) (headers Headers, err error) { | |
1537 | _, err = c.ObjectPut(ctx, container, symlink, contents, true, EMPTY_MD5, "application/symlink", symHeaders) | |
1538 | return | |
1539 | } | |
1540 | ||
1541 | func (c *Connection) objectPut(ctx context.Context, container string, objectName string, contents io.Reader, checkHash bool, Hash string, contentType string, h Headers, parameters url.Values) (headers Headers, err error) { | |
1517 | 1542 | extraHeaders := objectPutHeaders(objectName, &checkHash, Hash, contentType, h) |
1518 | 1543 | hash := md5.New() |
1519 | 1544 | var body io.Reader = contents |
1520 | 1545 | if checkHash { |
1521 | 1546 | body = io.TeeReader(contents, hash) |
1522 | 1547 | } |
1523 | _, headers, err = c.storage(RequestOpts{ | |
1548 | _, headers, err = c.storage(ctx, RequestOpts{ | |
1524 | 1549 | Container: container, |
1525 | 1550 | ObjectName: objectName, |
1526 | 1551 | Operation: "PUT", |
1565 | 1590 | // |
1566 | 1591 | // If contentType is set it will be used, otherwise one will be |
1567 | 1592 | // guessed from objectName using mime.TypeByExtension |
1568 | func (c *Connection) ObjectPut(container string, objectName string, contents io.Reader, checkHash bool, Hash string, contentType string, h Headers) (headers Headers, err error) { | |
1569 | return c.objectPut(container, objectName, contents, checkHash, Hash, contentType, h, nil) | |
1593 | func (c *Connection) ObjectPut(ctx context.Context, container string, objectName string, contents io.Reader, checkHash bool, Hash string, contentType string, h Headers) (headers Headers, err error) { | |
1594 | return c.objectPut(ctx, container, objectName, contents, checkHash, Hash, contentType, h, nil) | |
1570 | 1595 | } |
1571 | 1596 | |
1572 | 1597 | // ObjectPutBytes creates an object from a []byte in a container. |
1573 | 1598 | // |
1574 | 1599 | // This is a simplified interface which checks the MD5. |
1575 | func (c *Connection) ObjectPutBytes(container string, objectName string, contents []byte, contentType string) (err error) { | |
1600 | func (c *Connection) ObjectPutBytes(ctx context.Context, container string, objectName string, contents []byte, contentType string) (err error) { | |
1576 | 1601 | buf := bytes.NewBuffer(contents) |
1577 | 1602 | h := Headers{"Content-Length": strconv.Itoa(len(contents))} |
1578 | _, err = c.ObjectPut(container, objectName, buf, true, "", contentType, h) | |
1603 | hash := md5.Sum(contents) | |
1604 | hashStr := hex.EncodeToString(hash[:]) | |
1605 | _, err = c.ObjectPut(ctx, container, objectName, buf, true, hashStr, contentType, h) | |
1579 | 1606 | return |
1580 | 1607 | } |
1581 | 1608 | |
1582 | 1609 | // ObjectPutString creates an object from a string in a container. |
1583 | 1610 | // |
1584 | 1611 | // This is a simplified interface which checks the MD5 |
1585 | func (c *Connection) ObjectPutString(container string, objectName string, contents string, contentType string) (err error) { | |
1612 | func (c *Connection) ObjectPutString(ctx context.Context, container string, objectName string, contents string, contentType string) (err error) { | |
1586 | 1613 | buf := strings.NewReader(contents) |
1587 | 1614 | h := Headers{"Content-Length": strconv.Itoa(len(contents))} |
1588 | _, err = c.ObjectPut(container, objectName, buf, true, "", contentType, h) | |
1615 | hash := md5.Sum([]byte(contents)) | |
1616 | hashStr := hex.EncodeToString(hash[:]) | |
1617 | _, err = c.ObjectPut(ctx, container, objectName, buf, true, hashStr, contentType, h) | |
1589 | 1618 | return |
1590 | 1619 | } |
1591 | 1620 | |
1635 | 1664 | // unlike os.File |
1636 | 1665 | // |
1637 | 1666 | // Seek(0, 1) will return the current file pointer. |
1638 | func (file *ObjectOpenFile) Seek(offset int64, whence int) (newPos int64, err error) { | |
1667 | func (file *ObjectOpenFile) Seek(ctx context.Context, offset int64, whence int) (newPos int64, err error) { | |
1639 | 1668 | file.overSeeked = false |
1640 | 1669 | switch whence { |
1641 | 1670 | case 0: // relative to start |
1673 | 1702 | } else { |
1674 | 1703 | delete(file.headers, "Range") |
1675 | 1704 | } |
1676 | newFile, _, err := file.connection.ObjectOpen(file.container, file.objectName, false, file.headers) | |
1705 | newFile, _, err := file.connection.ObjectOpen(ctx, file.container, file.objectName, false, file.headers) | |
1677 | 1706 | if err != nil { |
1678 | 1707 | return |
1679 | 1708 | } |
1687 | 1716 | |
1688 | 1717 | // Length gets the objects content length either from a cached copy or |
1689 | 1718 | // from the server. |
1690 | func (file *ObjectOpenFile) Length() (int64, error) { | |
1719 | func (file *ObjectOpenFile) Length(ctx context.Context) (int64, error) { | |
1691 | 1720 | if !file.lengthOk { |
1692 | info, _, err := file.connection.Object(file.container, file.objectName) | |
1721 | info, _, err := file.connection.Object(ctx, file.container, file.objectName) | |
1693 | 1722 | file.length = info.Bytes |
1694 | 1723 | file.lengthOk = (err == nil) |
1695 | 1724 | return file.length, err |
1710 | 1739 | |
1711 | 1740 | // Check the MD5 sum if requested |
1712 | 1741 | if file.checkHash { |
1713 | receivedMd5 := strings.ToLower(file.resp.Header.Get("Etag")) | |
1742 | // ETag header may be double quoted if following RFC 7232 | |
1743 | // https://github.com/openstack/swift/blob/2.24.0/CHANGELOG#L9 | |
1744 | receivedMd5 := strings.ToLower(strings.Trim(file.resp.Header.Get("Etag"), "\"")) | |
1714 | 1745 | calculatedMd5 := fmt.Sprintf("%x", file.hash.Sum(nil)) |
1715 | 1746 | if receivedMd5 != calculatedMd5 { |
1716 | 1747 | err = ObjectCorrupted |
1726 | 1757 | return |
1727 | 1758 | } |
1728 | 1759 | |
1729 | // Check it satisfies the interfaces | |
1730 | var _ io.ReadCloser = &ObjectOpenFile{} | |
1731 | var _ io.Seeker = &ObjectOpenFile{} | |
1732 | ||
1733 | func (c *Connection) objectOpenBase(container string, objectName string, checkHash bool, h Headers, parameters url.Values) (file *ObjectOpenFile, headers Headers, err error) { | |
1760 | func (c *Connection) objectOpenBase(ctx context.Context, container string, objectName string, checkHash bool, h Headers, parameters url.Values) (file *ObjectOpenFile, headers Headers, err error) { | |
1734 | 1761 | var resp *http.Response |
1735 | 1762 | opts := RequestOpts{ |
1736 | 1763 | Container: container, |
1740 | 1767 | Headers: h, |
1741 | 1768 | Parameters: parameters, |
1742 | 1769 | } |
1743 | resp, headers, err = c.storage(opts) | |
1770 | resp, headers, err = c.storage(ctx, opts) | |
1744 | 1771 | if err != nil { |
1745 | 1772 | return |
1746 | 1773 | } |
1770 | 1797 | return |
1771 | 1798 | } |
1772 | 1799 | |
1773 | func (c *Connection) objectOpen(container string, objectName string, checkHash bool, h Headers, parameters url.Values) (file *ObjectOpenFile, headers Headers, err error) { | |
1800 | func (c *Connection) objectOpen(ctx context.Context, container string, objectName string, checkHash bool, h Headers, parameters url.Values) (file *ObjectOpenFile, headers Headers, err error) { | |
1774 | 1801 | err = withLORetry(0, func() (Headers, int64, error) { |
1775 | file, headers, err = c.objectOpenBase(container, objectName, checkHash, h, parameters) | |
1802 | file, headers, err = c.objectOpenBase(ctx, container, objectName, checkHash, h, parameters) | |
1776 | 1803 | if err != nil { |
1777 | 1804 | return headers, 0, err |
1778 | 1805 | } |
1804 | 1831 | // you will need to download everything in the manifest separately. |
1805 | 1832 | // |
1806 | 1833 | // headers["Content-Type"] will give the content type if desired. |
1807 | func (c *Connection) ObjectOpen(container string, objectName string, checkHash bool, h Headers) (file *ObjectOpenFile, headers Headers, err error) { | |
1808 | return c.objectOpen(container, objectName, checkHash, h, nil) | |
1834 | func (c *Connection) ObjectOpen(ctx context.Context, container string, objectName string, checkHash bool, h Headers) (file *ObjectOpenFile, headers Headers, err error) { | |
1835 | return c.objectOpen(ctx, container, objectName, checkHash, h, nil) | |
1809 | 1836 | } |
1810 | 1837 | |
1811 | 1838 | // ObjectGet gets the object into the io.Writer contents. |
1817 | 1844 | // server. If it is wrong then it will return ObjectCorrupted. |
1818 | 1845 | // |
1819 | 1846 | // headers["Content-Type"] will give the content type if desired. |
1820 | func (c *Connection) ObjectGet(container string, objectName string, contents io.Writer, checkHash bool, h Headers) (headers Headers, err error) { | |
1821 | file, headers, err := c.ObjectOpen(container, objectName, checkHash, h) | |
1847 | func (c *Connection) ObjectGet(ctx context.Context, container string, objectName string, contents io.Writer, checkHash bool, h Headers) (headers Headers, err error) { | |
1848 | file, headers, err := c.ObjectOpen(ctx, container, objectName, checkHash, h) | |
1822 | 1849 | if err != nil { |
1823 | 1850 | return |
1824 | 1851 | } |
1830 | 1857 | // ObjectGetBytes returns an object as a []byte. |
1831 | 1858 | // |
1832 | 1859 | // This is a simplified interface which checks the MD5 |
1833 | func (c *Connection) ObjectGetBytes(container string, objectName string) (contents []byte, err error) { | |
1860 | func (c *Connection) ObjectGetBytes(ctx context.Context, container string, objectName string) (contents []byte, err error) { | |
1834 | 1861 | var buf bytes.Buffer |
1835 | _, err = c.ObjectGet(container, objectName, &buf, true, nil) | |
1862 | _, err = c.ObjectGet(ctx, container, objectName, &buf, true, nil) | |
1836 | 1863 | contents = buf.Bytes() |
1837 | 1864 | return |
1838 | 1865 | } |
1840 | 1867 | // ObjectGetString returns an object as a string. |
1841 | 1868 | // |
1842 | 1869 | // This is a simplified interface which checks the MD5 |
1843 | func (c *Connection) ObjectGetString(container string, objectName string) (contents string, err error) { | |
1870 | func (c *Connection) ObjectGetString(ctx context.Context, container string, objectName string) (contents string, err error) { | |
1844 | 1871 | var buf bytes.Buffer |
1845 | _, err = c.ObjectGet(container, objectName, &buf, true, nil) | |
1872 | _, err = c.ObjectGet(ctx, container, objectName, &buf, true, nil) | |
1846 | 1873 | contents = buf.String() |
1847 | 1874 | return |
1848 | 1875 | } |
1850 | 1877 | // ObjectDelete deletes the object. |
1851 | 1878 | // |
1852 | 1879 | // May return ObjectNotFound if the object isn't found |
1853 | func (c *Connection) ObjectDelete(container string, objectName string) error { | |
1854 | _, _, err := c.storage(RequestOpts{ | |
1880 | func (c *Connection) ObjectDelete(ctx context.Context, container string, objectName string) error { | |
1881 | _, _, err := c.storage(ctx, RequestOpts{ | |
1855 | 1882 | Container: container, |
1856 | 1883 | ObjectName: objectName, |
1857 | 1884 | Operation: "DELETE", |
1862 | 1889 | |
1863 | 1890 | // ObjectTempUrl returns a temporary URL for an object |
1864 | 1891 | func (c *Connection) ObjectTempUrl(container string, objectName string, secretKey string, method string, expires time.Time) string { |
1892 | c.authLock.Lock() | |
1893 | storageUrl := c.StorageUrl | |
1894 | c.authLock.Unlock() | |
1895 | if storageUrl == "" { | |
1896 | return "" // Cannot do better without changing the interface | |
1897 | } | |
1898 | ||
1865 | 1899 | mac := hmac.New(sha1.New, []byte(secretKey)) |
1866 | prefix, _ := url.Parse(c.StorageUrl) | |
1900 | prefix, _ := url.Parse(storageUrl) | |
1867 | 1901 | body := fmt.Sprintf("%s\n%d\n%s/%s/%s", method, expires.Unix(), prefix.Path, container, objectName) |
1868 | 1902 | mac.Write([]byte(body)) |
1869 | 1903 | sig := hex.EncodeToString(mac.Sum(nil)) |
1908 | 1942 | Headers Headers // Response HTTP headers. |
1909 | 1943 | } |
1910 | 1944 | |
1911 | func (c *Connection) doBulkDelete(objects []string, h Headers) (result BulkDeleteResult, err error) { | |
1945 | func (c *Connection) doBulkDelete(ctx context.Context, objects []string, h Headers) (result BulkDeleteResult, err error) { | |
1912 | 1946 | var buffer bytes.Buffer |
1913 | 1947 | for _, s := range objects { |
1914 | 1948 | u := url.URL{Path: s} |
1922 | 1956 | for key, value := range h { |
1923 | 1957 | extraHeaders[key] = value |
1924 | 1958 | } |
1925 | resp, headers, err := c.storage(RequestOpts{ | |
1959 | resp, headers, err := c.storage(ctx, RequestOpts{ | |
1926 | 1960 | Operation: "DELETE", |
1927 | 1961 | Parameters: url.Values{"bulk-delete": []string{"1"}}, |
1928 | 1962 | Headers: extraHeaders, |
1966 | 2000 | // See also: |
1967 | 2001 | // * http://docs.openstack.org/trunk/openstack-object-storage/admin/content/object-storage-bulk-delete.html |
1968 | 2002 | // * http://docs.rackspace.com/files/api/v1/cf-devguide/content/Bulk_Delete-d1e2338.html |
1969 | func (c *Connection) BulkDelete(container string, objectNames []string) (result BulkDeleteResult, err error) { | |
1970 | return c.BulkDeleteHeaders(container, objectNames, nil) | |
2003 | func (c *Connection) BulkDelete(ctx context.Context, container string, objectNames []string) (result BulkDeleteResult, err error) { | |
2004 | return c.BulkDeleteHeaders(ctx, container, objectNames, nil) | |
1971 | 2005 | } |
1972 | 2006 | |
1973 | 2007 | // BulkDeleteHeaders deletes multiple objectNames from container in one operation. |
1978 | 2012 | // See also: |
1979 | 2013 | // * http://docs.openstack.org/trunk/openstack-object-storage/admin/content/object-storage-bulk-delete.html |
1980 | 2014 | // * http://docs.rackspace.com/files/api/v1/cf-devguide/content/Bulk_Delete-d1e2338.html |
1981 | func (c *Connection) BulkDeleteHeaders(container string, objectNames []string, h Headers) (result BulkDeleteResult, err error) { | |
2015 | func (c *Connection) BulkDeleteHeaders(ctx context.Context, container string, objectNames []string, h Headers) (result BulkDeleteResult, err error) { | |
1982 | 2016 | if len(objectNames) == 0 { |
1983 | 2017 | result.Errors = make(map[string]error) |
1984 | 2018 | return |
1987 | 2021 | for i, name := range objectNames { |
1988 | 2022 | fullPaths[i] = fmt.Sprintf("/%s/%s", container, name) |
1989 | 2023 | } |
1990 | return c.doBulkDelete(fullPaths, h) | |
2024 | return c.doBulkDelete(ctx, fullPaths, h) | |
1991 | 2025 | } |
1992 | 2026 | |
1993 | 2027 | // BulkUploadResult stores results of BulkUpload(). |
2020 | 2054 | // See also: |
2021 | 2055 | // * http://docs.openstack.org/trunk/openstack-object-storage/admin/content/object-storage-extract-archive.html |
2022 | 2056 | // * http://docs.rackspace.com/files/api/v1/cf-devguide/content/Extract_Archive-d1e2338.html |
2023 | func (c *Connection) BulkUpload(uploadPath string, dataStream io.Reader, format string, h Headers) (result BulkUploadResult, err error) { | |
2057 | func (c *Connection) BulkUpload(ctx context.Context, uploadPath string, dataStream io.Reader, format string, h Headers) (result BulkUploadResult, err error) { | |
2024 | 2058 | extraHeaders := Headers{"Accept": "application/json"} |
2025 | 2059 | for key, value := range h { |
2026 | 2060 | extraHeaders[key] = value |
2027 | 2061 | } |
2028 | 2062 | // The following code abuses Container parameter intentionally. |
2029 | 2063 | // The best fix might be to rename Container to UploadPath. |
2030 | resp, headers, err := c.storage(RequestOpts{ | |
2064 | resp, headers, err := c.storage(ctx, RequestOpts{ | |
2031 | 2065 | Container: uploadPath, |
2032 | 2066 | Operation: "PUT", |
2033 | 2067 | Parameters: url.Values{"extract-archive": []string{format}}, |
2072 | 2106 | // May return ObjectNotFound. |
2073 | 2107 | // |
2074 | 2108 | // Use headers.ObjectMetadata() to read the metadata in the Headers. |
2075 | func (c *Connection) Object(container string, objectName string) (info Object, headers Headers, err error) { | |
2109 | func (c *Connection) Object(ctx context.Context, container string, objectName string) (info Object, headers Headers, err error) { | |
2076 | 2110 | err = withLORetry(0, func() (Headers, int64, error) { |
2077 | info, headers, err = c.objectBase(container, objectName) | |
2111 | info, headers, err = c.objectBase(ctx, container, objectName) | |
2078 | 2112 | if err != nil { |
2079 | 2113 | return headers, 0, err |
2080 | 2114 | } |
2083 | 2117 | return |
2084 | 2118 | } |
2085 | 2119 | |
2086 | func (c *Connection) objectBase(container string, objectName string) (info Object, headers Headers, err error) { | |
2120 | func (c *Connection) objectBase(ctx context.Context, container string, objectName string) (info Object, headers Headers, err error) { | |
2087 | 2121 | var resp *http.Response |
2088 | resp, headers, err = c.storage(RequestOpts{ | |
2122 | resp, headers, err = c.storage(ctx, RequestOpts{ | |
2089 | 2123 | Container: container, |
2090 | 2124 | ObjectName: objectName, |
2091 | 2125 | Operation: "HEAD", |
2123 | 2157 | } |
2124 | 2158 | } |
2125 | 2159 | |
2126 | info.Hash = resp.Header.Get("Etag") | |
2160 | // ETag header may be double quoted if following RFC 7232 | |
2161 | // https://github.com/openstack/swift/blob/2.24.0/CHANGELOG#L9 | |
2162 | info.Hash = strings.Trim(resp.Header.Get("Etag"), "\"") | |
2127 | 2163 | if resp.Header.Get("X-Object-Manifest") != "" { |
2128 | 2164 | info.ObjectType = DynamicLargeObjectType |
2129 | 2165 | } else if resp.Header.Get("X-Static-Large-Object") != "" { |
2155 | 2191 | // other headers such as Content-Type or CORS headers. |
2156 | 2192 | // |
2157 | 2193 | // May return ObjectNotFound. |
2158 | func (c *Connection) ObjectUpdate(container string, objectName string, h Headers) error { | |
2159 | _, _, err := c.storage(RequestOpts{ | |
2194 | func (c *Connection) ObjectUpdate(ctx context.Context, container string, objectName string, h Headers) error { | |
2195 | _, _, err := c.storage(ctx, RequestOpts{ | |
2160 | 2196 | Container: container, |
2161 | 2197 | ObjectName: objectName, |
2162 | 2198 | Operation: "POST", |
2185 | 2221 | // |
2186 | 2222 | // You can use this to copy an object to itself - this is the only way |
2187 | 2223 | // to update the content type of an object. |
2188 | func (c *Connection) ObjectCopy(srcContainer string, srcObjectName string, dstContainer string, dstObjectName string, h Headers) (headers Headers, err error) { | |
2224 | func (c *Connection) ObjectCopy(ctx context.Context, srcContainer string, srcObjectName string, dstContainer string, dstObjectName string, h Headers) (headers Headers, err error) { | |
2189 | 2225 | // Meta stuff |
2190 | 2226 | extraHeaders := map[string]string{ |
2191 | 2227 | "Destination": urlPathEscape(dstContainer + "/" + dstObjectName), |
2193 | 2229 | for key, value := range h { |
2194 | 2230 | extraHeaders[key] = value |
2195 | 2231 | } |
2196 | _, headers, err = c.storage(RequestOpts{ | |
2232 | _, headers, err = c.storage(ctx, RequestOpts{ | |
2197 | 2233 | Container: srcContainer, |
2198 | 2234 | ObjectName: srcObjectName, |
2199 | 2235 | Operation: "COPY", |
2211 | 2247 | // All metadata is preserved. |
2212 | 2248 | // |
2213 | 2249 | // The destination container must exist before the copy. |
2214 | func (c *Connection) ObjectMove(srcContainer string, srcObjectName string, dstContainer string, dstObjectName string) (err error) { | |
2215 | _, err = c.ObjectCopy(srcContainer, srcObjectName, dstContainer, dstObjectName, nil) | |
2250 | func (c *Connection) ObjectMove(ctx context.Context, srcContainer string, srcObjectName string, dstContainer string, dstObjectName string) (err error) { | |
2251 | _, err = c.ObjectCopy(ctx, srcContainer, srcObjectName, dstContainer, dstObjectName, nil) | |
2216 | 2252 | if err != nil { |
2217 | 2253 | return |
2218 | 2254 | } |
2219 | return c.ObjectDelete(srcContainer, srcObjectName) | |
2255 | return c.ObjectDelete(ctx, srcContainer, srcObjectName) | |
2220 | 2256 | } |
2221 | 2257 | |
2222 | 2258 | // ObjectUpdateContentType updates the content type of an object |
2224 | 2260 | // This is a convenience method which calls ObjectCopy |
2225 | 2261 | // |
2226 | 2262 | // All other metadata is preserved. |
2227 | func (c *Connection) ObjectUpdateContentType(container string, objectName string, contentType string) (err error) { | |
2263 | func (c *Connection) ObjectUpdateContentType(ctx context.Context, container string, objectName string, contentType string) (err error) { | |
2228 | 2264 | h := Headers{"Content-Type": contentType} |
2229 | _, err = c.ObjectCopy(container, objectName, container, objectName, h) | |
2265 | _, err = c.ObjectCopy(ctx, container, objectName, container, objectName, h) | |
2230 | 2266 | return |
2231 | 2267 | } |
2232 | 2268 | |
2238 | 2274 | // |
2239 | 2275 | // If the server doesn't support versioning then it will return |
2240 | 2276 | // Forbidden however it will have created both the containers at that point. |
2241 | func (c *Connection) VersionContainerCreate(current, version string) error { | |
2242 | if err := c.ContainerCreate(version, nil); err != nil { | |
2277 | func (c *Connection) VersionContainerCreate(ctx context.Context, current, version string) error { | |
2278 | if err := c.ContainerCreate(ctx, version, nil); err != nil { | |
2243 | 2279 | return err |
2244 | 2280 | } |
2245 | if err := c.ContainerCreate(current, nil); err != nil { | |
2281 | if err := c.ContainerCreate(ctx, current, nil); err != nil { | |
2246 | 2282 | return err |
2247 | 2283 | } |
2248 | if err := c.VersionEnable(current, version); err != nil { | |
2284 | if err := c.VersionEnable(ctx, current, version); err != nil { | |
2249 | 2285 | return err |
2250 | 2286 | } |
2251 | 2287 | return nil |
2254 | 2290 | // VersionEnable enables versioning on the current container with version as the tracking container. |
2255 | 2291 | // |
2256 | 2292 | // May return Forbidden if this isn't supported by the server |
2257 | func (c *Connection) VersionEnable(current, version string) error { | |
2293 | func (c *Connection) VersionEnable(ctx context.Context, current, version string) error { | |
2258 | 2294 | h := Headers{"X-Versions-Location": version} |
2259 | if err := c.ContainerUpdate(current, h); err != nil { | |
2295 | if err := c.ContainerUpdate(ctx, current, h); err != nil { | |
2260 | 2296 | return err |
2261 | 2297 | } |
2262 | 2298 | // Check to see if the header was set properly |
2263 | _, headers, err := c.Container(current) | |
2299 | _, headers, err := c.Container(ctx, current) | |
2264 | 2300 | if err != nil { |
2265 | 2301 | return err |
2266 | 2302 | } |
2272 | 2308 | } |
2273 | 2309 | |
2274 | 2310 | // VersionDisable disables versioning on the current container. |
2275 | func (c *Connection) VersionDisable(current string) error { | |
2311 | func (c *Connection) VersionDisable(ctx context.Context, current string) error { | |
2276 | 2312 | h := Headers{"X-Versions-Location": ""} |
2277 | if err := c.ContainerUpdate(current, h); err != nil { | |
2313 | if err := c.ContainerUpdate(ctx, current, h); err != nil { | |
2278 | 2314 | return err |
2279 | 2315 | } |
2280 | 2316 | return nil |
2283 | 2319 | // VersionObjectList returns a list of older versions of the object. |
2284 | 2320 | // |
2285 | 2321 | // Objects are returned in the format <length><object_name>/<timestamp> |
2286 | func (c *Connection) VersionObjectList(version, object string) ([]string, error) { | |
2322 | func (c *Connection) VersionObjectList(ctx context.Context, version, object string) ([]string, error) { | |
2287 | 2323 | opts := &ObjectsOpts{ |
2288 | 2324 | // <3-character zero-padded hexadecimal character length><object name>/ |
2289 | 2325 | Prefix: fmt.Sprintf("%03x", len(object)) + object + "/", |
2290 | 2326 | } |
2291 | return c.ObjectNames(version, opts) | |
2292 | } | |
2327 | return c.ObjectNames(ctx, version, opts) | |
2328 | } | |
2329 | ||
2330 | // GetStorageUrl returns Swift storage URL. | |
2331 | func (c *Connection) GetStorageUrl(ctx context.Context) (string, error) { | |
2332 | c.authLock.Lock() | |
2333 | defer c.authLock.Unlock() | |
2334 | ||
2335 | // Return cached URL even if authentication has expired | |
2336 | if c.StorageUrl == "" { | |
2337 | err := c.authenticate(ctx) | |
2338 | if err != nil { | |
2339 | return "", err | |
2340 | } | |
2341 | } | |
2342 | return c.StorageUrl, nil | |
2343 | } |
5 | 5 | package swift |
6 | 6 | |
7 | 7 | import ( |
8 | "context" | |
8 | 9 | "fmt" |
9 | 10 | "io" |
10 | 11 | "net" |
148 | 149 | func NewSwiftServer() *SwiftServer { |
149 | 150 | server := &SwiftServer{} |
150 | 151 | http.HandleFunc("/", handle) |
151 | go http.ListenAndServe(TEST_ADDRESS, nil) | |
152 | go func() { | |
153 | _ = http.ListenAndServe(TEST_ADDRESS, nil) | |
154 | }() | |
152 | 155 | fmt.Print("Waiting for server to start ") |
153 | 156 | for { |
154 | 157 | fmt.Print(".") |
155 | 158 | conn, err := net.Dial("tcp", TEST_ADDRESS) |
156 | 159 | if err == nil { |
157 | conn.Close() | |
160 | _ = conn.Close() | |
158 | 161 | fmt.Println(" Started") |
159 | 162 | break |
160 | 163 | } |
315 | 318 | }).Url("/v1.0") |
316 | 319 | defer server.Finished() |
317 | 320 | |
318 | err := c.Authenticate() | |
321 | ctx := context.Background() | |
322 | err := c.Authenticate(ctx) | |
319 | 323 | if err != nil { |
320 | 324 | t.Fatal(err) |
321 | 325 | } |
335 | 339 | server.AddCheck(t).Error(401, "DENIED") |
336 | 340 | defer server.Finished() |
337 | 341 | c.UnAuthenticate() |
338 | err := c.Authenticate() | |
342 | err := c.Authenticate(context.Background()) | |
339 | 343 | if err != AuthorizationFailed { |
340 | 344 | t.Fatal("Expecting AuthorizationFailed", err) |
341 | 345 | } |
350 | 354 | "X-Storage-Url": PROXY_URL, |
351 | 355 | }) |
352 | 356 | defer server.Finished() |
353 | err := c.Authenticate() | |
357 | ctx := context.Background() | |
358 | err := c.Authenticate(ctx) | |
354 | 359 | checkError(t, err, 0, "Response didn't have storage url and auth token") |
355 | 360 | if c.Authenticated() { |
356 | 361 | t.Fatal("Expecting not authenticated") |
359 | 364 | server.AddCheck(t).Out(Headers{ |
360 | 365 | "X-Auth-Token": AUTH_TOKEN, |
361 | 366 | }) |
362 | err = c.Authenticate() | |
367 | err = c.Authenticate(ctx) | |
363 | 368 | checkError(t, err, 0, "Response didn't have storage url and auth token") |
364 | 369 | if c.Authenticated() { |
365 | 370 | t.Fatal("Expecting not authenticated") |
366 | 371 | } |
367 | 372 | |
368 | 373 | server.AddCheck(t) |
369 | err = c.Authenticate() | |
374 | err = c.Authenticate(ctx) | |
370 | 375 | checkError(t, err, 0, "Response didn't have storage url and auth token") |
371 | 376 | if c.Authenticated() { |
372 | 377 | t.Fatal("Expecting not authenticated") |
376 | 381 | "X-Storage-Url": PROXY_URL, |
377 | 382 | "X-Auth-Token": AUTH_TOKEN, |
378 | 383 | }) |
379 | err = c.Authenticate() | |
384 | err = c.Authenticate(ctx) | |
380 | 385 | if err != nil { |
381 | 386 | t.Fatal(err) |
382 | 387 | } |
390 | 395 | "User-Agent": DefaultUserAgent, |
391 | 396 | "X-Auth-Token": AUTH_TOKEN, |
392 | 397 | }).Tx(rx).Url("/proxy") |
393 | containers, err := c.ContainerNames(nil) | |
398 | containers, err := c.ContainerNames(context.Background(), nil) | |
394 | 399 | if err != nil { |
395 | 400 | t.Fatal(err) |
396 | 401 | } |
419 | 424 | "Content-Type": "text/plain", |
420 | 425 | }).Rx("12345") |
421 | 426 | defer server.Finished() |
422 | c.ObjectPutBytes("container", "object", []byte{'1', '2', '3', '4', '5'}, "text/plain") | |
427 | err := c.ObjectPutBytes(context.Background(), "container", "object", []byte{'1', '2', '3', '4', '5'}, "text/plain") | |
428 | if err != nil { | |
429 | t.Fatal("ObjectPutBytes", err) | |
430 | } | |
423 | 431 | } |
424 | 432 | |
425 | 433 | func TestInternalObjectPutString(t *testing.T) { |
430 | 438 | "Content-Type": "text/plain", |
431 | 439 | }).Rx("12345") |
432 | 440 | defer server.Finished() |
433 | c.ObjectPutString("container", "object", "12345", "text/plain") | |
441 | err := c.ObjectPutString(context.Background(), "container", "object", "12345", "text/plain") | |
442 | if err != nil { | |
443 | t.Fatal(err) | |
444 | } | |
434 | 445 | } |
435 | 446 | |
436 | 447 | func TestSetFromEnv(t *testing.T) { |
437 | 448 | // String |
438 | 449 | s := "" |
439 | 450 | |
440 | os.Setenv("POTATO", "") | |
451 | _ = os.Setenv("POTATO", "") | |
441 | 452 | err := setFromEnv(&s, "POTATO") |
442 | 453 | if err != nil { |
443 | 454 | t.Fatal(err) |
444 | 455 | } |
445 | 456 | |
446 | os.Setenv("POTATO", "this is a test") | |
457 | _ = os.Setenv("POTATO", "this is a test") | |
447 | 458 | err = setFromEnv(&s, "POTATO") |
448 | 459 | if err != nil { |
449 | 460 | t.Fatal(err) |
452 | 463 | t.Fatal("incorrect", s) |
453 | 464 | } |
454 | 465 | |
455 | os.Setenv("POTATO", "new") | |
466 | _ = os.Setenv("POTATO", "new") | |
456 | 467 | err = setFromEnv(&s, "POTATO") |
457 | 468 | if err != nil { |
458 | 469 | t.Fatal(err) |
464 | 475 | // Integer |
465 | 476 | i := 0 |
466 | 477 | |
467 | os.Setenv("POTATO", "42") | |
478 | _ = os.Setenv("POTATO", "42") | |
468 | 479 | err = setFromEnv(&i, "POTATO") |
469 | 480 | if err != nil { |
470 | 481 | t.Fatal(err) |
473 | 484 | t.Fatal("incorrect", i) |
474 | 485 | } |
475 | 486 | |
476 | os.Setenv("POTATO", "43") | |
487 | _ = os.Setenv("POTATO", "43") | |
477 | 488 | err = setFromEnv(&i, "POTATO") |
478 | 489 | if err != nil { |
479 | 490 | t.Fatal(err) |
483 | 494 | } |
484 | 495 | |
485 | 496 | i = 0 |
486 | os.Setenv("POTATO", "not a number") | |
497 | _ = os.Setenv("POTATO", "not a number") | |
487 | 498 | err = setFromEnv(&i, "POTATO") |
488 | 499 | if err == nil { |
489 | 500 | t.Fatal("expecting error but didn't get one") |
491 | 502 | |
492 | 503 | // bool |
493 | 504 | var b bool |
494 | os.Setenv("POTATO", "1") | |
505 | _ = os.Setenv("POTATO", "1") | |
495 | 506 | err = setFromEnv(&b, "POTATO") |
496 | 507 | if err != nil { |
497 | 508 | t.Fatal(err) |
502 | 513 | |
503 | 514 | // time.Duration |
504 | 515 | var dt time.Duration |
505 | os.Setenv("POTATO", "5s") | |
516 | _ = os.Setenv("POTATO", "5s") | |
506 | 517 | err = setFromEnv(&dt, "POTATO") |
507 | 518 | if err != nil { |
508 | 519 | t.Fatal(err) |
513 | 524 | |
514 | 525 | // EndpointType |
515 | 526 | var e EndpointType |
516 | os.Setenv("POTATO", "internal") | |
527 | _ = os.Setenv("POTATO", "internal") | |
517 | 528 | err = setFromEnv(&e, "POTATO") |
518 | 529 | if err != nil { |
519 | 530 | t.Fatal(err) |
529 | 540 | t.Fatal("expecting error") |
530 | 541 | } |
531 | 542 | |
532 | os.Setenv("POTATO", "") | |
543 | _ = os.Setenv("POTATO", "") | |
533 | 544 | } |
534 | 545 | |
535 | 546 | func TestApplyEnvironment(t *testing.T) { |
536 | 547 | // We've tested all the setting logic above, so just do a quick test here |
537 | 548 | c := new(Connection) |
538 | os.Setenv("GOSWIFT_CONNECT_TIMEOUT", "100s") | |
549 | _ = os.Setenv("GOSWIFT_CONNECT_TIMEOUT", "100s") | |
539 | 550 | err := c.ApplyEnvironment() |
540 | 551 | if err != nil { |
541 | 552 | t.Fatal(err) |
545 | 556 | } |
546 | 557 | |
547 | 558 | c.ConnectTimeout = 0 |
548 | os.Setenv("GOSWIFT_CONNECT_TIMEOUT", "parse error") | |
559 | _ = os.Setenv("GOSWIFT_CONNECT_TIMEOUT", "parse error") | |
549 | 560 | err = c.ApplyEnvironment() |
550 | 561 | if err == nil { |
551 | 562 | t.Fatal("expecting error") |
554 | 565 | t.Fatal("timeout incorrect", c.ConnectTimeout) |
555 | 566 | } |
556 | 567 | |
557 | os.Setenv("GOSWIFT_CONNECT_TIMEOUT", "") | |
568 | _ = os.Setenv("GOSWIFT_CONNECT_TIMEOUT", "") | |
558 | 569 | } |
559 | 570 | |
560 | 571 | func TestApplyEnvironmentAll(t *testing.T) { |
604 | 615 | item := &items[i] |
605 | 616 | if item.phase == phase { |
606 | 617 | item.oldValue = os.Getenv(item.name) // save old value |
607 | os.Setenv(item.name, item.value) // set new value | |
618 | _ = os.Setenv(item.name, item.value) // set new value | |
608 | 619 | } |
609 | 620 | } |
610 | 621 | |
620 | 631 | if !reflect.DeepEqual(item.want, got) { |
621 | 632 | t.Errorf("%s: %v != %v", item.name, item.want, got) |
622 | 633 | } |
623 | os.Setenv(item.name, item.oldValue) // restore old value | |
634 | _ = os.Setenv(item.name, item.oldValue) // restore old value | |
624 | 635 | } |
625 | 636 | } |
626 | 637 | } |
627 | 638 | |
628 | 639 | } |
640 | ||
641 | func TestIsLastPage(t *testing.T) { | |
642 | conn := &Connection{} | |
643 | testPaging(t, conn, []pagingTest{ | |
644 | { | |
645 | length: 0, | |
646 | limit: 1000, | |
647 | expected: true, | |
648 | }, | |
649 | { | |
650 | length: 850, | |
651 | limit: 1000, | |
652 | expected: true, | |
653 | }, | |
654 | { | |
655 | length: 950, | |
656 | limit: 1000, | |
657 | expected: true, | |
658 | }, | |
659 | { | |
660 | length: 1000, | |
661 | limit: 1000, | |
662 | expected: false, | |
663 | }, | |
664 | // For completeness | |
665 | { | |
666 | length: 0, | |
667 | limit: 0, | |
668 | expected: false, | |
669 | }, | |
670 | { | |
671 | length: 2000, | |
672 | limit: 1000, | |
673 | expected: false, | |
674 | }, | |
675 | }) | |
676 | ||
677 | } | |
678 | ||
679 | func TestFetchUntilEmptyPageWorkaround(t *testing.T) { | |
680 | conn := &Connection{ | |
681 | FetchUntilEmptyPage: true, | |
682 | } | |
683 | testPaging(t, conn, []pagingTest{ | |
684 | { | |
685 | length: 0, | |
686 | limit: 1000, | |
687 | expected: true, | |
688 | }, | |
689 | { | |
690 | length: 850, | |
691 | limit: 1000, | |
692 | expected: false, | |
693 | }, | |
694 | { | |
695 | length: 950, | |
696 | limit: 1000, | |
697 | expected: false, | |
698 | }, | |
699 | { | |
700 | length: 1000, | |
701 | limit: 1000, | |
702 | expected: false, | |
703 | }, | |
704 | // For completeness | |
705 | { | |
706 | length: 0, | |
707 | limit: 0, | |
708 | expected: false, | |
709 | }, | |
710 | { | |
711 | length: 2000, | |
712 | limit: 1000, | |
713 | expected: false, | |
714 | }, | |
715 | }) | |
716 | } | |
717 | ||
718 | func TestPartialPageFetchThreshold(t *testing.T) { | |
719 | conn := &Connection{ | |
720 | PartialPageFetchThreshold: 90, | |
721 | } | |
722 | testPaging(t, conn, []pagingTest{ | |
723 | { | |
724 | length: 0, | |
725 | limit: 1000, | |
726 | expected: true, | |
727 | }, | |
728 | { | |
729 | length: 850, | |
730 | limit: 1000, | |
731 | expected: true, | |
732 | }, | |
733 | { | |
734 | length: 950, | |
735 | limit: 1000, | |
736 | expected: false, | |
737 | }, | |
738 | { | |
739 | length: 1000, | |
740 | limit: 1000, | |
741 | expected: false, | |
742 | }, | |
743 | // For completeness | |
744 | { | |
745 | length: 0, | |
746 | limit: 0, | |
747 | expected: false, | |
748 | }, | |
749 | { | |
750 | length: 2000, | |
751 | limit: 1000, | |
752 | expected: false, | |
753 | }, | |
754 | }) | |
755 | ||
756 | } | |
757 | ||
758 | type pagingTest struct { | |
759 | length int | |
760 | limit int | |
761 | expected bool | |
762 | } | |
763 | ||
764 | func testPaging(t *testing.T, conn *Connection, testCases []pagingTest) { | |
765 | for _, tCase := range testCases { | |
766 | if actual := conn.isLastPage(tCase.length, tCase.limit); actual != tCase.expected { | |
767 | t.Fatalf("isLastPage(%d, %d) returned %t, expected %t", tCase.length, tCase.limit, actual, tCase.expected) | |
768 | } | |
769 | } | |
770 | } |
14 | 14 | import ( |
15 | 15 | "archive/tar" |
16 | 16 | "bytes" |
17 | "context" | |
17 | 18 | "crypto/md5" |
18 | 19 | "crypto/rand" |
19 | 20 | "crypto/tls" |
32 | 33 | "testing" |
33 | 34 | "time" |
34 | 35 | |
35 | "github.com/ncw/swift" | |
36 | "github.com/ncw/swift/swifttest" | |
36 | "github.com/ncw/swift/v2" | |
37 | "github.com/ncw/swift/v2/swifttest" | |
37 | 38 | ) |
38 | 39 | |
39 | 40 | var ( |
143 | 144 | } |
144 | 145 | |
145 | 146 | func makeConnectionAuth(t *testing.T) (*swift.Connection, func()) { |
147 | ctx := context.Background() | |
146 | 148 | c, rollback := makeConnection(t) |
147 | err := c.Authenticate() | |
149 | err := c.Authenticate(ctx) | |
148 | 150 | if err != nil { |
149 | 151 | t.Fatal("Auth failed", err) |
150 | 152 | } |
152 | 154 | } |
153 | 155 | |
154 | 156 | func makeConnectionWithContainer(t *testing.T) (*swift.Connection, func()) { |
157 | ctx := context.Background() | |
155 | 158 | c, rollback := makeConnectionAuth(t) |
156 | err := c.ContainerCreate(CONTAINER, m1.ContainerHeaders()) | |
159 | err := c.ContainerCreate(ctx, CONTAINER, m1.ContainerHeaders()) | |
157 | 160 | if err != nil { |
158 | 161 | t.Fatal(err) |
159 | 162 | } |
160 | 163 | return c, func() { |
161 | c.ContainerDelete(CONTAINER) | |
164 | _ = c.ContainerDelete(ctx, CONTAINER) | |
162 | 165 | rollback() |
163 | 166 | } |
164 | 167 | } |
165 | 168 | |
166 | 169 | func makeConnectionWithObject(t *testing.T) (*swift.Connection, func()) { |
170 | ctx := context.Background() | |
167 | 171 | c, rollback := makeConnectionWithContainer(t) |
168 | err := c.ObjectPutString(CONTAINER, OBJECT, CONTENTS, "") | |
172 | err := c.ObjectPutString(ctx, CONTAINER, OBJECT, CONTENTS, "") | |
169 | 173 | if err != nil { |
170 | 174 | t.Fatal(err) |
171 | 175 | } |
172 | 176 | return c, func() { |
173 | c.ObjectDelete(CONTAINER, OBJECT) | |
177 | _ = c.ObjectDelete(ctx, CONTAINER, OBJECT) | |
174 | 178 | rollback() |
175 | 179 | } |
176 | 180 | } |
177 | 181 | |
178 | 182 | func makeConnectionWithObjectHeaders(t *testing.T) (*swift.Connection, func()) { |
183 | ctx := context.Background() | |
179 | 184 | c, rollback := makeConnectionWithObject(t) |
180 | err := c.ObjectUpdate(CONTAINER, OBJECT, m1.ObjectHeaders()) | |
185 | err := c.ObjectUpdate(ctx, CONTAINER, OBJECT, m1.ObjectHeaders()) | |
181 | 186 | if err != nil { |
182 | 187 | t.Fatal(err) |
183 | 188 | } |
185 | 190 | } |
186 | 191 | |
187 | 192 | func makeConnectionWithVersionsContainer(t *testing.T) (*swift.Connection, func()) { |
193 | ctx := context.Background() | |
188 | 194 | c, rollback := makeConnectionAuth(t) |
189 | err := c.VersionContainerCreate(CURRENT_CONTAINER, VERSIONS_CONTAINER) | |
195 | err := c.VersionContainerCreate(ctx, CURRENT_CONTAINER, VERSIONS_CONTAINER) | |
190 | 196 | newRollback := func() { |
191 | c.ContainerDelete(CURRENT_CONTAINER) | |
192 | c.ContainerDelete(VERSIONS_CONTAINER) | |
197 | _ = c.ContainerDelete(ctx, CURRENT_CONTAINER) | |
198 | _ = c.ContainerDelete(ctx, VERSIONS_CONTAINER) | |
193 | 199 | rollback() |
194 | 200 | } |
195 | 201 | if err != nil { |
203 | 209 | } |
204 | 210 | |
205 | 211 | func makeConnectionWithVersionsObject(t *testing.T) (*swift.Connection, func()) { |
212 | ctx := context.Background() | |
206 | 213 | c, rollback := makeConnectionWithVersionsContainer(t) |
207 | if err := c.ObjectPutString(CURRENT_CONTAINER, OBJECT, CONTENTS, ""); err != nil { | |
214 | if err := c.ObjectPutString(ctx, CURRENT_CONTAINER, OBJECT, CONTENTS, ""); err != nil { | |
208 | 215 | t.Fatal(err) |
209 | 216 | } |
210 | 217 | // Version 2 |
211 | if err := c.ObjectPutString(CURRENT_CONTAINER, OBJECT, CONTENTS2, ""); err != nil { | |
218 | if err := c.ObjectPutString(ctx, CURRENT_CONTAINER, OBJECT, CONTENTS2, ""); err != nil { | |
212 | 219 | t.Fatal(err) |
213 | 220 | } |
214 | 221 | // Version 3 |
215 | if err := c.ObjectPutString(CURRENT_CONTAINER, OBJECT, CONTENTS2, ""); err != nil { | |
222 | if err := c.ObjectPutString(ctx, CURRENT_CONTAINER, OBJECT, CONTENTS2, ""); err != nil { | |
216 | 223 | t.Fatal(err) |
217 | 224 | } |
218 | 225 | return c, func() { |
219 | 226 | for i := 0; i < 3; i++ { |
220 | c.ObjectDelete(CURRENT_CONTAINER, OBJECT) | |
227 | _ = c.ObjectDelete(ctx, CURRENT_CONTAINER, OBJECT) | |
221 | 228 | } |
222 | 229 | rollback() |
223 | 230 | } |
224 | 231 | } |
225 | 232 | |
226 | 233 | func makeConnectionWithSegmentsContainer(t *testing.T) (*swift.Connection, func()) { |
234 | ctx := context.Background() | |
227 | 235 | c, rollback := makeConnectionWithContainer(t) |
228 | err := c.ContainerCreate(SEGMENTS_CONTAINER, swift.Headers{}) | |
236 | err := c.ContainerCreate(ctx, SEGMENTS_CONTAINER, swift.Headers{}) | |
229 | 237 | if err != nil { |
230 | 238 | t.Fatal(err) |
231 | 239 | } |
232 | 240 | return c, func() { |
233 | err = c.ContainerDelete(SEGMENTS_CONTAINER) | |
241 | err = c.ContainerDelete(ctx, SEGMENTS_CONTAINER) | |
234 | 242 | if err != nil { |
235 | 243 | t.Fatal(err) |
236 | 244 | } |
239 | 247 | } |
240 | 248 | |
241 | 249 | func makeConnectionWithDLO(t *testing.T) (*swift.Connection, func()) { |
250 | ctx := context.Background() | |
242 | 251 | c, rollback := makeConnectionWithSegmentsContainer(t) |
243 | 252 | opts := swift.LargeObjectOpts{ |
244 | 253 | Container: CONTAINER, |
245 | 254 | ObjectName: OBJECT, |
246 | 255 | ContentType: "image/jpeg", |
247 | 256 | } |
248 | out, err := c.DynamicLargeObjectCreate(&opts) | |
257 | out, err := c.DynamicLargeObjectCreate(ctx, &opts) | |
249 | 258 | if err != nil { |
250 | 259 | t.Fatal(err) |
251 | 260 | } |
255 | 264 | t.Fatal(err) |
256 | 265 | } |
257 | 266 | } |
258 | err = out.Close() | |
267 | err = out.CloseWithContext(ctx) | |
259 | 268 | if err != nil { |
260 | 269 | t.Error(err) |
261 | 270 | } |
262 | 271 | return c, func() { |
263 | c.DynamicLargeObjectDelete(CONTAINER, OBJECT) | |
272 | _ = c.DynamicLargeObjectDelete(ctx, CONTAINER, OBJECT) | |
264 | 273 | rollback() |
265 | 274 | } |
266 | 275 | } |
267 | 276 | |
268 | 277 | func makeConnectionWithSLO(t *testing.T) (*swift.Connection, func()) { |
278 | ctx := context.Background() | |
269 | 279 | c, rollback := makeConnectionWithSegmentsContainer(t) |
270 | 280 | opts := swift.LargeObjectOpts{ |
271 | 281 | Container: CONTAINER, |
272 | 282 | ObjectName: OBJECT, |
273 | 283 | ContentType: "image/jpeg", |
274 | 284 | } |
275 | out, err := c.StaticLargeObjectCreate(&opts) | |
285 | out, err := c.StaticLargeObjectCreate(ctx, &opts) | |
276 | 286 | if err != nil { |
277 | 287 | if err == swift.SLONotSupported { |
278 | 288 | t.Skip("SLO not supported") |
286 | 296 | t.Fatal(err) |
287 | 297 | } |
288 | 298 | } |
289 | err = out.Close() | |
299 | err = out.CloseWithContext(ctx) | |
290 | 300 | if err != nil { |
291 | 301 | t.Error(err) |
292 | 302 | } |
293 | 303 | return c, func() { |
294 | c.StaticLargeObjectDelete(CONTAINER, OBJECT) | |
304 | _ = c.StaticLargeObjectDelete(ctx, CONTAINER, OBJECT) | |
295 | 305 | rollback() |
296 | 306 | } |
297 | 307 | } |
302 | 312 | } |
303 | 313 | |
304 | 314 | func getSwinftInfo(t *testing.T) (info swift.SwiftInfo, err error) { |
315 | ctx := context.Background() | |
305 | 316 | c, rollback := makeConnectionAuth(t) |
306 | 317 | defer rollback() |
307 | return c.QueryInfo() | |
318 | return c.QueryInfo(ctx) | |
308 | 319 | } |
309 | 320 | |
310 | 321 | func TestTransport(t *testing.T) { |
322 | ctx := context.Background() | |
311 | 323 | c, rollback := makeConnection(t) |
312 | 324 | defer rollback() |
313 | 325 | |
325 | 337 | |
326 | 338 | c.Transport = tr |
327 | 339 | |
328 | err := c.Authenticate() | |
340 | err := c.Authenticate(ctx) | |
329 | 341 | if err != nil { |
330 | 342 | t.Fatal("Auth failed", err) |
331 | 343 | } |
336 | 348 | |
337 | 349 | // The following Test functions are run in order - this one must come before the others! |
338 | 350 | func TestV1V2Authenticate(t *testing.T) { |
351 | ctx := context.Background() | |
339 | 352 | if isV3Api() { |
340 | 353 | return |
341 | 354 | } |
342 | 355 | c, rollback := makeConnection(t) |
343 | 356 | defer rollback() |
344 | 357 | |
345 | err := c.Authenticate() | |
358 | err := c.Authenticate(ctx) | |
346 | 359 | if err != nil { |
347 | 360 | t.Fatal("Auth failed", err) |
348 | 361 | } |
352 | 365 | } |
353 | 366 | |
354 | 367 | func TestV3AuthenticateWithDomainNameAndTenantId(t *testing.T) { |
368 | ctx := context.Background() | |
355 | 369 | if !isV3Api() { |
356 | 370 | return |
357 | 371 | } |
364 | 378 | c.TenantId = os.Getenv("SWIFT_TENANT_ID") |
365 | 379 | c.DomainId = "" |
366 | 380 | |
367 | err := c.Authenticate() | |
381 | err := c.Authenticate(ctx) | |
368 | 382 | if err != nil { |
369 | 383 | t.Fatal("Auth failed", err) |
370 | 384 | } |
374 | 388 | } |
375 | 389 | |
376 | 390 | func TestV3TrustWithTrustId(t *testing.T) { |
391 | ctx := context.Background() | |
377 | 392 | if !isV3Api() { |
378 | 393 | return |
379 | 394 | } |
383 | 398 | |
384 | 399 | c.TrustId = os.Getenv("SWIFT_TRUST_ID") |
385 | 400 | |
386 | err := c.Authenticate() | |
401 | err := c.Authenticate(ctx) | |
387 | 402 | if err != nil { |
388 | 403 | t.Fatal("Auth failed", err) |
389 | 404 | } |
393 | 408 | } |
394 | 409 | |
395 | 410 | func TestV3AuthenticateWithDomainIdAndTenantId(t *testing.T) { |
411 | ctx := context.Background() | |
396 | 412 | if !isV3Api() { |
397 | 413 | return |
398 | 414 | } |
405 | 421 | c.TenantId = os.Getenv("SWIFT_TENANT_ID") |
406 | 422 | c.DomainId = os.Getenv("SWIFT_API_DOMAIN_ID") |
407 | 423 | |
408 | err := c.Authenticate() | |
424 | err := c.Authenticate(ctx) | |
409 | 425 | if err != nil { |
410 | 426 | t.Fatal("Auth failed", err) |
411 | 427 | } |
415 | 431 | } |
416 | 432 | |
417 | 433 | func TestV3AuthenticateWithDomainNameAndTenantName(t *testing.T) { |
434 | ctx := context.Background() | |
418 | 435 | if !isV3Api() { |
419 | 436 | return |
420 | 437 | } |
427 | 444 | c.TenantId = "" |
428 | 445 | c.DomainId = "" |
429 | 446 | |
430 | err := c.Authenticate() | |
447 | err := c.Authenticate(ctx) | |
431 | 448 | if err != nil { |
432 | 449 | t.Fatal("Auth failed", err) |
433 | 450 | } |
437 | 454 | } |
438 | 455 | |
439 | 456 | func TestV3AuthenticateWithDomainIdAndTenantName(t *testing.T) { |
457 | ctx := context.Background() | |
440 | 458 | if !isV3Api() { |
441 | 459 | return |
442 | 460 | } |
449 | 467 | c.TenantId = "" |
450 | 468 | c.DomainId = os.Getenv("SWIFT_API_DOMAIN_ID") |
451 | 469 | |
452 | err := c.Authenticate() | |
470 | err := c.Authenticate(ctx) | |
453 | 471 | if err != nil { |
454 | 472 | t.Fatal("Auth failed", err) |
455 | 473 | } |
462 | 480 | // |
463 | 481 | // Run with -race to test |
464 | 482 | func TestAuthenticateRace(t *testing.T) { |
483 | ctx := context.Background() | |
465 | 484 | c, rollback := makeConnection(t) |
466 | 485 | defer rollback() |
467 | 486 | var wg sync.WaitGroup |
469 | 488 | wg.Add(1) |
470 | 489 | go func() { |
471 | 490 | defer wg.Done() |
472 | err := c.Authenticate() | |
491 | err := c.Authenticate(ctx) | |
473 | 492 | if err != nil { |
474 | t.Fatal("Auth failed", err) | |
493 | t.Error("Auth failed", err) | |
475 | 494 | } |
476 | 495 | if !c.Authenticated() { |
477 | t.Fatal("Not authenticated") | |
496 | t.Error("Not authenticated") | |
478 | 497 | } |
479 | 498 | }() |
480 | 499 | } |
483 | 502 | |
484 | 503 | // Test a connection can be serialized and unserialized with JSON |
485 | 504 | func TestSerializeConnectionJson(t *testing.T) { |
505 | ctx := context.Background() | |
486 | 506 | c, rollback := makeConnectionAuth(t) |
487 | 507 | defer rollback() |
488 | 508 | serializedConnection, err := json.Marshal(c) |
497 | 517 | if !c2.Authenticated() { |
498 | 518 | t.Fatal("Should be authenticated") |
499 | 519 | } |
500 | _, _, err = c2.Account() | |
520 | _, _, err = c2.Account(ctx) | |
501 | 521 | if err != nil { |
502 | 522 | t.Fatalf("Failed to use unserialized connection: %v", err) |
503 | 523 | } |
505 | 525 | |
506 | 526 | // Test a connection can be serialized and unserialized with XML |
507 | 527 | func TestSerializeConnectionXml(t *testing.T) { |
528 | ctx := context.Background() | |
508 | 529 | c, rollback := makeConnectionAuth(t) |
509 | 530 | defer rollback() |
510 | 531 | serializedConnection, err := xml.Marshal(c) |
519 | 540 | if !c2.Authenticated() { |
520 | 541 | t.Fatal("Should be authenticated") |
521 | 542 | } |
522 | _, _, err = c2.Account() | |
543 | _, _, err = c2.Account(ctx) | |
523 | 544 | if err != nil { |
524 | 545 | t.Fatalf("Failed to use unserialized connection: %v", err) |
525 | 546 | } |
527 | 548 | |
528 | 549 | // Test the reauthentication logic |
529 | 550 | func TestOnReAuth(t *testing.T) { |
551 | ctx := context.Background() | |
530 | 552 | c, rollback := makeConnectionAuth(t) |
531 | 553 | defer rollback() |
532 | 554 | c.UnAuthenticate() |
533 | _, _, err := c.Account() | |
555 | _, _, err := c.Account(ctx) | |
534 | 556 | if err != nil { |
535 | 557 | t.Fatalf("Failed to reauthenticate: %v", err) |
536 | 558 | } |
537 | 559 | } |
538 | 560 | |
539 | 561 | func TestAccount(t *testing.T) { |
562 | ctx := context.Background() | |
540 | 563 | c, rollback := makeConnectionAuth(t) |
541 | 564 | defer rollback() |
542 | info, headers, err := c.Account() | |
565 | info, headers, err := c.Account(ctx) | |
543 | 566 | if err != nil { |
544 | 567 | t.Fatal(err) |
545 | 568 | } |
571 | 594 | } |
572 | 595 | |
573 | 596 | func TestAccountUpdate(t *testing.T) { |
597 | ctx := context.Background() | |
574 | 598 | c, rollback := makeConnectionAuth(t) |
575 | 599 | defer rollback() |
576 | err := c.AccountUpdate(m1.AccountHeaders()) | |
577 | if err != nil { | |
578 | t.Fatal(err) | |
579 | } | |
580 | ||
581 | _, headers, err := c.Account() | |
600 | err := c.AccountUpdate(ctx, m1.AccountHeaders()) | |
601 | if err != nil { | |
602 | t.Fatal(err) | |
603 | } | |
604 | ||
605 | _, headers, err := c.Account(ctx) | |
582 | 606 | if err != nil { |
583 | 607 | t.Fatal(err) |
584 | 608 | } |
586 | 610 | delete(m, "temp-url-key") // remove X-Account-Meta-Temp-URL-Key if set |
587 | 611 | compareMaps(t, m, map[string]string{"hello": "1", "potato-salad": "2"}) |
588 | 612 | |
589 | err = c.AccountUpdate(m2.AccountHeaders()) | |
590 | if err != nil { | |
591 | t.Fatal(err) | |
592 | } | |
593 | ||
594 | _, headers, err = c.Account() | |
613 | err = c.AccountUpdate(ctx, m2.AccountHeaders()) | |
614 | if err != nil { | |
615 | t.Fatal(err) | |
616 | } | |
617 | ||
618 | _, headers, err = c.Account(ctx) | |
595 | 619 | if err != nil { |
596 | 620 | t.Fatal(err) |
597 | 621 | } |
601 | 625 | } |
602 | 626 | |
603 | 627 | func TestContainerCreate(t *testing.T) { |
628 | ctx := context.Background() | |
604 | 629 | c, rollback := makeConnectionAuth(t) |
605 | 630 | defer rollback() |
606 | err := c.ContainerCreate(CONTAINER, m1.ContainerHeaders()) | |
607 | if err != nil { | |
608 | t.Fatal(err) | |
609 | } | |
610 | err = c.ContainerDelete(CONTAINER) | |
631 | err := c.ContainerCreate(ctx, CONTAINER, m1.ContainerHeaders()) | |
632 | if err != nil { | |
633 | t.Fatal(err) | |
634 | } | |
635 | err = c.ContainerDelete(ctx, CONTAINER) | |
611 | 636 | if err != nil { |
612 | 637 | t.Fatal(err) |
613 | 638 | } |
614 | 639 | } |
615 | 640 | |
616 | 641 | func TestContainer(t *testing.T) { |
642 | ctx := context.Background() | |
617 | 643 | c, rollback := makeConnectionWithContainer(t) |
618 | 644 | defer rollback() |
619 | info, headers, err := c.Container(CONTAINER) | |
645 | info, headers, err := c.Container(ctx, CONTAINER) | |
620 | 646 | if err != nil { |
621 | 647 | t.Fatal(err) |
622 | 648 | } |
633 | 659 | } |
634 | 660 | |
635 | 661 | func TestContainersAll(t *testing.T) { |
662 | ctx := context.Background() | |
636 | 663 | c, rollback := makeConnectionWithContainer(t) |
637 | 664 | defer rollback() |
638 | containers1, err := c.ContainersAll(nil) | |
639 | if err != nil { | |
640 | t.Fatal(err) | |
641 | } | |
642 | containers2, err := c.Containers(nil) | |
665 | containers1, err := c.ContainersAll(ctx, nil) | |
666 | if err != nil { | |
667 | t.Fatal(err) | |
668 | } | |
669 | containers2, err := c.Containers(ctx, nil) | |
643 | 670 | if err != nil { |
644 | 671 | t.Fatal(err) |
645 | 672 | } |
654 | 681 | } |
655 | 682 | |
656 | 683 | func TestContainersAllWithLimit(t *testing.T) { |
684 | ctx := context.Background() | |
657 | 685 | c, rollback := makeConnectionWithContainer(t) |
658 | 686 | defer rollback() |
659 | containers1, err := c.ContainersAll(&swift.ContainersOpts{Limit: 1}) | |
660 | if err != nil { | |
661 | t.Fatal(err) | |
662 | } | |
663 | containers2, err := c.Containers(nil) | |
687 | containers1, err := c.ContainersAll(ctx, &swift.ContainersOpts{Limit: 1}) | |
688 | if err != nil { | |
689 | t.Fatal(err) | |
690 | } | |
691 | containers2, err := c.Containers(ctx, nil) | |
664 | 692 | if err != nil { |
665 | 693 | t.Fatal(err) |
666 | 694 | } |
675 | 703 | } |
676 | 704 | |
677 | 705 | func TestContainerUpdate(t *testing.T) { |
706 | ctx := context.Background() | |
678 | 707 | c, rollback := makeConnectionWithContainer(t) |
679 | 708 | defer rollback() |
680 | err := c.ContainerUpdate(CONTAINER, m2.ContainerHeaders()) | |
681 | if err != nil { | |
682 | t.Fatal(err) | |
683 | } | |
684 | _, headers, err := c.Container(CONTAINER) | |
709 | err := c.ContainerUpdate(ctx, CONTAINER, m2.ContainerHeaders()) | |
710 | if err != nil { | |
711 | t.Fatal(err) | |
712 | } | |
713 | _, headers, err := c.Container(ctx, CONTAINER) | |
685 | 714 | if err != nil { |
686 | 715 | t.Fatal(err) |
687 | 716 | } |
689 | 718 | } |
690 | 719 | |
691 | 720 | func TestContainerNames(t *testing.T) { |
721 | ctx := context.Background() | |
692 | 722 | c, rollback := makeConnectionWithContainer(t) |
693 | 723 | defer rollback() |
694 | containers, err := c.ContainerNames(nil) | |
724 | containers, err := c.ContainerNames(ctx, nil) | |
695 | 725 | if err != nil { |
696 | 726 | t.Fatal(err) |
697 | 727 | } |
708 | 738 | } |
709 | 739 | |
710 | 740 | func TestContainerNamesAll(t *testing.T) { |
741 | ctx := context.Background() | |
711 | 742 | c, rollback := makeConnectionWithContainer(t) |
712 | 743 | defer rollback() |
713 | containers1, err := c.ContainerNamesAll(nil) | |
714 | if err != nil { | |
715 | t.Fatal(err) | |
716 | } | |
717 | containers2, err := c.ContainerNames(nil) | |
744 | containers1, err := c.ContainerNamesAll(ctx, nil) | |
745 | if err != nil { | |
746 | t.Fatal(err) | |
747 | } | |
748 | containers2, err := c.ContainerNames(ctx, nil) | |
718 | 749 | if err != nil { |
719 | 750 | t.Fatal(err) |
720 | 751 | } |
729 | 760 | } |
730 | 761 | |
731 | 762 | func TestContainerNamesAllWithLimit(t *testing.T) { |
763 | ctx := context.Background() | |
732 | 764 | c, rollback := makeConnectionWithContainer(t) |
733 | 765 | defer rollback() |
734 | containers1, err := c.ContainerNamesAll(&swift.ContainersOpts{Limit: 1}) | |
735 | if err != nil { | |
736 | t.Fatal(err) | |
737 | } | |
738 | containers2, err := c.ContainerNames(nil) | |
766 | containers1, err := c.ContainerNamesAll(ctx, &swift.ContainersOpts{Limit: 1}) | |
767 | if err != nil { | |
768 | t.Fatal(err) | |
769 | } | |
770 | containers2, err := c.ContainerNames(ctx, nil) | |
739 | 771 | if err != nil { |
740 | 772 | t.Fatal(err) |
741 | 773 | } |
750 | 782 | } |
751 | 783 | |
752 | 784 | func TestObjectPutString(t *testing.T) { |
785 | ctx := context.Background() | |
753 | 786 | c, rollback := makeConnectionWithContainer(t) |
754 | 787 | defer rollback() |
755 | err := c.ObjectPutString(CONTAINER, OBJECT, CONTENTS, "") | |
788 | err := c.ObjectPutString(ctx, CONTAINER, OBJECT, CONTENTS, "") | |
756 | 789 | if err != nil { |
757 | 790 | t.Fatal(err) |
758 | 791 | } |
759 | 792 | defer func() { |
760 | err = c.ObjectDelete(CONTAINER, OBJECT) | |
793 | err = c.ObjectDelete(ctx, CONTAINER, OBJECT) | |
761 | 794 | if err != nil { |
762 | 795 | t.Fatal(err) |
763 | 796 | } |
764 | 797 | }() |
765 | 798 | |
766 | info, _, err := c.Object(CONTAINER, OBJECT) | |
799 | info, _, err := c.Object(ctx, CONTAINER, OBJECT) | |
767 | 800 | if err != nil { |
768 | 801 | t.Error(err) |
769 | 802 | } |
779 | 812 | } |
780 | 813 | |
781 | 814 | func TestObjectPut(t *testing.T) { |
815 | ctx := context.Background() | |
782 | 816 | c, rollback := makeConnectionWithContainer(t) |
783 | 817 | defer rollback() |
784 | 818 | |
787 | 821 | // Set content size incorrectly - should produce an error |
788 | 822 | headers["Content-Length"] = strconv.FormatInt(CONTENT_SIZE-1, 10) |
789 | 823 | contents := bytes.NewBufferString(CONTENTS) |
790 | h, err := c.ObjectPut(CONTAINER, OBJECT, contents, true, CONTENT_MD5, "text/plain", headers) | |
824 | _, err := c.ObjectPut(ctx, CONTAINER, OBJECT, contents, true, CONTENT_MD5, "text/plain", headers) | |
791 | 825 | if err == nil { |
792 | 826 | t.Fatal("Expecting error but didn't get one") |
793 | 827 | } |
795 | 829 | // Now set content size correctly |
796 | 830 | contents = bytes.NewBufferString(CONTENTS) |
797 | 831 | headers["Content-Length"] = strconv.FormatInt(CONTENT_SIZE, 10) |
798 | h, err = c.ObjectPut(CONTAINER, OBJECT, contents, true, CONTENT_MD5, "text/plain", headers) | |
832 | h, err := c.ObjectPut(ctx, CONTAINER, OBJECT, contents, true, CONTENT_MD5, "text/plain", headers) | |
799 | 833 | if err != nil { |
800 | 834 | t.Fatal(err) |
801 | 835 | } |
802 | 836 | defer func() { |
803 | err = c.ObjectDelete(CONTAINER, OBJECT) | |
837 | err = c.ObjectDelete(ctx, CONTAINER, OBJECT) | |
804 | 838 | if err != nil { |
805 | 839 | t.Fatal(err) |
806 | 840 | } |
811 | 845 | } |
812 | 846 | |
813 | 847 | // Fetch object info and compare |
814 | info, _, err := c.Object(CONTAINER, OBJECT) | |
848 | info, _, err := c.Object(ctx, CONTAINER, OBJECT) | |
815 | 849 | if err != nil { |
816 | 850 | t.Error(err) |
817 | 851 | } |
827 | 861 | } |
828 | 862 | |
829 | 863 | func TestObjectPutWithReauth(t *testing.T) { |
864 | ctx := context.Background() | |
830 | 865 | if !swift.IS_AT_LEAST_GO_16 { |
831 | 866 | return |
832 | 867 | } |
837 | 872 | c.AuthToken = "expiredtoken" |
838 | 873 | |
839 | 874 | r := strings.NewReader(CONTENTS) |
840 | _, err := c.ObjectPut(CONTAINER, OBJECT, r, true, "", "text/plain", nil) | |
841 | if err != nil { | |
842 | t.Fatal(err) | |
843 | } | |
844 | ||
845 | info, _, err := c.Object(CONTAINER, OBJECT) | |
875 | _, err := c.ObjectPut(ctx, CONTAINER, OBJECT, r, true, "", "text/plain", nil) | |
876 | if err != nil { | |
877 | t.Fatal(err) | |
878 | } | |
879 | ||
880 | info, _, err := c.Object(ctx, CONTAINER, OBJECT) | |
846 | 881 | if err != nil { |
847 | 882 | t.Error(err) |
848 | 883 | } |
858 | 893 | } |
859 | 894 | |
860 | 895 | func TestObjectPutStringWithReauth(t *testing.T) { |
896 | ctx := context.Background() | |
861 | 897 | if !swift.IS_AT_LEAST_GO_16 { |
862 | 898 | return |
863 | 899 | } |
867 | 903 | // Simulate that our auth token expired |
868 | 904 | c.AuthToken = "expiredtoken" |
869 | 905 | |
870 | err := c.ObjectPutString(CONTAINER, OBJECT, CONTENTS, "") | |
871 | if err != nil { | |
872 | t.Fatal(err) | |
873 | } | |
874 | ||
875 | info, _, err := c.Object(CONTAINER, OBJECT) | |
906 | err := c.ObjectPutString(ctx, CONTAINER, OBJECT, CONTENTS, "") | |
907 | if err != nil { | |
908 | t.Fatal(err) | |
909 | } | |
910 | ||
911 | info, _, err := c.Object(ctx, CONTAINER, OBJECT) | |
876 | 912 | if err != nil { |
877 | 913 | t.Error(err) |
878 | 914 | } |
888 | 924 | } |
889 | 925 | |
890 | 926 | func TestObjectEmpty(t *testing.T) { |
927 | ctx := context.Background() | |
891 | 928 | c, rollback := makeConnectionWithContainer(t) |
892 | 929 | defer rollback() |
893 | err := c.ObjectPutString(CONTAINER, EMPTYOBJECT, "", "") | |
930 | err := c.ObjectPutString(ctx, CONTAINER, EMPTYOBJECT, "", "") | |
894 | 931 | if err != nil { |
895 | 932 | t.Fatal(err) |
896 | 933 | } |
897 | 934 | defer func() { |
898 | err = c.ObjectDelete(CONTAINER, EMPTYOBJECT) | |
935 | err = c.ObjectDelete(ctx, CONTAINER, EMPTYOBJECT) | |
899 | 936 | if err != nil { |
900 | 937 | t.Error(err) |
901 | 938 | } |
902 | 939 | }() |
903 | 940 | |
904 | info, _, err := c.Object(CONTAINER, EMPTYOBJECT) | |
941 | info, _, err := c.Object(ctx, CONTAINER, EMPTYOBJECT) | |
905 | 942 | if err != nil { |
906 | 943 | t.Error(err) |
907 | 944 | } |
917 | 954 | } |
918 | 955 | |
919 | 956 | func TestSymlinkObject(t *testing.T) { |
957 | ctx := context.Background() | |
920 | 958 | info, err := getSwinftInfo(t) |
921 | 959 | if err != nil { |
922 | 960 | t.Fatal(err) |
930 | 968 | defer rollback() |
931 | 969 | |
932 | 970 | // write target objects |
933 | err = c.ObjectPutBytes(CONTAINER, OBJECT, []byte(CONTENTS), "text/potato") | |
971 | err = c.ObjectPutBytes(ctx, CONTAINER, OBJECT, []byte(CONTENTS), "text/potato") | |
934 | 972 | if err != nil { |
935 | 973 | t.Fatal(err) |
936 | 974 | } |
937 | 975 | defer func() { |
938 | err = c.ObjectDelete(CONTAINER, OBJECT) | |
976 | err = c.ObjectDelete(ctx, CONTAINER, OBJECT) | |
939 | 977 | if err != nil { |
940 | 978 | t.Error(err) |
941 | 979 | } |
942 | 980 | }() |
943 | 981 | |
944 | 982 | // test dynamic link |
945 | _, err = c.ObjectSymlinkCreate(CONTAINER, SYMLINK_OBJECT, "", CONTAINER, OBJECT, "") | |
983 | _, err = c.ObjectSymlinkCreate(ctx, CONTAINER, SYMLINK_OBJECT, "", CONTAINER, OBJECT, "") | |
946 | 984 | if err != nil { |
947 | 985 | t.Fatal(err) |
948 | 986 | } |
949 | 987 | defer func() { |
950 | err = c.ObjectDelete(CONTAINER, SYMLINK_OBJECT) | |
988 | err = c.ObjectDelete(ctx, CONTAINER, SYMLINK_OBJECT) | |
951 | 989 | if err != nil { |
952 | 990 | t.Error(err) |
953 | 991 | } |
954 | 992 | }() |
955 | 993 | |
956 | md, _, err := c.Object(CONTAINER, SYMLINK_OBJECT) | |
994 | md, _, err := c.Object(ctx, CONTAINER, SYMLINK_OBJECT) | |
957 | 995 | if err != nil { |
958 | 996 | t.Error(err) |
959 | 997 | } |
970 | 1008 | } |
971 | 1009 | |
972 | 1010 | func TestStaticSymlinkObject(t *testing.T) { |
1011 | ctx := context.Background() | |
973 | 1012 | info, err := getSwinftInfo(t) |
974 | 1013 | if err != nil { |
975 | 1014 | t.Fatal(err) |
988 | 1027 | defer rollback() |
989 | 1028 | |
990 | 1029 | // write target objects |
991 | err = c.ObjectPutBytes(CONTAINER, OBJECT2, []byte(CONTENTS2), "text/tomato") | |
1030 | err = c.ObjectPutBytes(ctx, CONTAINER, OBJECT2, []byte(CONTENTS2), "text/tomato") | |
992 | 1031 | if err != nil { |
993 | 1032 | t.Fatal(err) |
994 | 1033 | } |
995 | 1034 | defer func() { |
996 | err = c.ObjectDelete(CONTAINER, OBJECT2) | |
1035 | err = c.ObjectDelete(ctx, CONTAINER, OBJECT2) | |
997 | 1036 | if err != nil { |
998 | 1037 | t.Error(err) |
999 | 1038 | } |
1001 | 1040 | |
1002 | 1041 | // test static link |
1003 | 1042 | // first with the wrong target etag |
1004 | _, err = c.ObjectSymlinkCreate(CONTAINER, SYMLINK_OBJECT2, "", CONTAINER, OBJECT2, CONTENT_MD5) | |
1043 | _, err = c.ObjectSymlinkCreate(ctx, CONTAINER, SYMLINK_OBJECT2, "", CONTAINER, OBJECT2, CONTENT_MD5) | |
1005 | 1044 | if err == nil { |
1006 | 1045 | t.Error("Symlink with wrong target etag should have failed") |
1007 | 1046 | } |
1008 | 1047 | |
1009 | _, err = c.ObjectSymlinkCreate(CONTAINER, SYMLINK_OBJECT2, "", CONTAINER, OBJECT2, CONTENT2_MD5) | |
1048 | _, err = c.ObjectSymlinkCreate(ctx, CONTAINER, SYMLINK_OBJECT2, "", CONTAINER, OBJECT2, CONTENT2_MD5) | |
1010 | 1049 | if err != nil { |
1011 | 1050 | t.Fatal(err) |
1012 | 1051 | } |
1013 | 1052 | defer func() { |
1014 | err = c.ObjectDelete(CONTAINER, SYMLINK_OBJECT2) | |
1053 | err = c.ObjectDelete(ctx, CONTAINER, SYMLINK_OBJECT2) | |
1015 | 1054 | if err != nil { |
1016 | 1055 | t.Error(err) |
1017 | 1056 | } |
1018 | 1057 | }() |
1019 | 1058 | |
1020 | md, _, err := c.Object(CONTAINER, SYMLINK_OBJECT2) | |
1059 | md, _, err := c.Object(ctx, CONTAINER, SYMLINK_OBJECT2) | |
1021 | 1060 | if err != nil { |
1022 | 1061 | t.Error(err) |
1023 | 1062 | } |
1033 | 1072 | } |
1034 | 1073 | |
1035 | 1074 | func TestObjectPutBytes(t *testing.T) { |
1075 | ctx := context.Background() | |
1036 | 1076 | c, rollback := makeConnectionWithContainer(t) |
1037 | 1077 | defer rollback() |
1038 | err := c.ObjectPutBytes(CONTAINER, OBJECT, []byte(CONTENTS), "") | |
1078 | err := c.ObjectPutBytes(ctx, CONTAINER, OBJECT, []byte(CONTENTS), "") | |
1039 | 1079 | if err != nil { |
1040 | 1080 | t.Fatal(err) |
1041 | 1081 | } |
1042 | 1082 | defer func() { |
1043 | err = c.ObjectDelete(CONTAINER, OBJECT) | |
1083 | err = c.ObjectDelete(ctx, CONTAINER, OBJECT) | |
1044 | 1084 | if err != nil { |
1045 | 1085 | t.Error(err) |
1046 | 1086 | } |
1047 | 1087 | }() |
1048 | 1088 | |
1049 | info, _, err := c.Object(CONTAINER, OBJECT) | |
1089 | info, _, err := c.Object(ctx, CONTAINER, OBJECT) | |
1050 | 1090 | if err != nil { |
1051 | 1091 | t.Error(err) |
1052 | 1092 | } |
1062 | 1102 | } |
1063 | 1103 | |
1064 | 1104 | func TestObjectPutMimeType(t *testing.T) { |
1105 | ctx := context.Background() | |
1065 | 1106 | c, rollback := makeConnectionWithContainer(t) |
1066 | 1107 | defer rollback() |
1067 | err := c.ObjectPutString(CONTAINER, "test.jpg", CONTENTS, "") | |
1108 | err := c.ObjectPutString(ctx, CONTAINER, "test.jpg", CONTENTS, "") | |
1068 | 1109 | if err != nil { |
1069 | 1110 | t.Fatal(err) |
1070 | 1111 | } |
1071 | 1112 | defer func() { |
1072 | err = c.ObjectDelete(CONTAINER, "test.jpg") | |
1113 | err = c.ObjectDelete(ctx, CONTAINER, "test.jpg") | |
1073 | 1114 | if err != nil { |
1074 | 1115 | t.Error(err) |
1075 | 1116 | } |
1076 | 1117 | }() |
1077 | 1118 | |
1078 | info, _, err := c.Object(CONTAINER, "test.jpg") | |
1119 | info, _, err := c.Object(ctx, CONTAINER, "test.jpg") | |
1079 | 1120 | if err != nil { |
1080 | 1121 | t.Error(err) |
1081 | 1122 | } |
1085 | 1126 | } |
1086 | 1127 | |
1087 | 1128 | func TestObjectCreate(t *testing.T) { |
1129 | ctx := context.Background() | |
1088 | 1130 | c, rollback := makeConnectionWithContainer(t) |
1089 | 1131 | defer rollback() |
1090 | out, err := c.ObjectCreate(CONTAINER, OBJECT2, true, "", "", nil) | |
1132 | out, err := c.ObjectCreate(ctx, CONTAINER, OBJECT2, true, "", "", nil) | |
1091 | 1133 | if err != nil { |
1092 | 1134 | t.Fatal(err) |
1093 | 1135 | } |
1094 | 1136 | defer func() { |
1095 | err = c.ObjectDelete(CONTAINER, OBJECT2) | |
1137 | err = c.ObjectDelete(ctx, CONTAINER, OBJECT2) | |
1096 | 1138 | if err != nil { |
1097 | 1139 | t.Error(err) |
1098 | 1140 | } |
1101 | 1143 | hash := md5.New() |
1102 | 1144 | out2 := io.MultiWriter(out, buf, hash) |
1103 | 1145 | for i := 0; i < 100; i++ { |
1104 | fmt.Fprintf(out2, "%d %s\n", i, CONTENTS) | |
1146 | _, _ = fmt.Fprintf(out2, "%d %s\n", i, CONTENTS) | |
1105 | 1147 | } |
1106 | 1148 | // Ensure Headers fails if called prematurely |
1107 | 1149 | _, err = out.Headers() |
1113 | 1155 | t.Error(err) |
1114 | 1156 | } |
1115 | 1157 | expected := buf.String() |
1116 | contents, err := c.ObjectGetString(CONTAINER, OBJECT2) | |
1158 | contents, err := c.ObjectGetString(ctx, CONTAINER, OBJECT2) | |
1117 | 1159 | if err != nil { |
1118 | 1160 | t.Error(err) |
1119 | 1161 | } |
1137 | 1179 | } |
1138 | 1180 | |
1139 | 1181 | // Now with hash instead |
1140 | out, err = c.ObjectCreate(CONTAINER, OBJECT2, false, fmt.Sprintf("%x", hash.Sum(nil)), "", nil) | |
1182 | out, err = c.ObjectCreate(ctx, CONTAINER, OBJECT2, false, fmt.Sprintf("%x", hash.Sum(nil)), "", nil) | |
1141 | 1183 | if err != nil { |
1142 | 1184 | t.Fatal(err) |
1143 | 1185 | } |
1149 | 1191 | if err != nil { |
1150 | 1192 | t.Error(err) |
1151 | 1193 | } |
1152 | contents, err = c.ObjectGetString(CONTAINER, OBJECT2) | |
1194 | contents, err = c.ObjectGetString(ctx, CONTAINER, OBJECT2) | |
1153 | 1195 | if err != nil { |
1154 | 1196 | t.Error(err) |
1155 | 1197 | } |
1158 | 1200 | } |
1159 | 1201 | |
1160 | 1202 | // Now with bad hash |
1161 | out, err = c.ObjectCreate(CONTAINER, OBJECT2, false, CONTENT_MD5, "", nil) | |
1203 | out, err = c.ObjectCreate(ctx, CONTAINER, OBJECT2, false, CONTENT_MD5, "", nil) | |
1162 | 1204 | if err != nil { |
1163 | 1205 | t.Fatal(err) |
1164 | 1206 | } |
1165 | 1207 | // FIXME: work around bug which produces 503 not 422 for empty corrupted files |
1166 | fmt.Fprintf(out, "Sausage") | |
1208 | _, _ = fmt.Fprintf(out, "Sausage") | |
1167 | 1209 | err = out.Close() |
1168 | 1210 | if err != swift.ObjectCorrupted { |
1169 | 1211 | t.Error("Expecting object corrupted not", err) |
1171 | 1213 | } |
1172 | 1214 | |
1173 | 1215 | func TestObjectCreateAbort(t *testing.T) { |
1216 | ctx := context.Background() | |
1174 | 1217 | c, rollback := makeConnectionWithContainer(t) |
1175 | 1218 | defer rollback() |
1176 | 1219 | |
1177 | out, err := c.ObjectCreate(CONTAINER, OBJECT2, true, "", "", nil) | |
1220 | out, err := c.ObjectCreate(ctx, CONTAINER, OBJECT2, true, "", "", nil) | |
1178 | 1221 | if err != nil { |
1179 | 1222 | t.Fatal(err) |
1180 | 1223 | } |
1181 | 1224 | defer func() { |
1182 | _ = c.ObjectDelete(CONTAINER, OBJECT2) // Ignore error | |
1225 | _ = c.ObjectDelete(ctx, CONTAINER, OBJECT2) // Ignore error | |
1183 | 1226 | }() |
1184 | 1227 | |
1185 | 1228 | expectedContents := "foo" |
1194 | 1237 | t.Errorf("Unexpected error %#v", err) |
1195 | 1238 | } |
1196 | 1239 | |
1197 | _, err = c.ObjectGetString(CONTAINER, OBJECT2) | |
1240 | _, err = c.ObjectGetString(ctx, CONTAINER, OBJECT2) | |
1198 | 1241 | if err != swift.ObjectNotFound { |
1199 | 1242 | t.Errorf("Unexpected error: %#v", err) |
1200 | 1243 | } |
1201 | 1244 | } |
1202 | 1245 | |
1203 | 1246 | func TestObjectGetString(t *testing.T) { |
1247 | ctx := context.Background() | |
1204 | 1248 | c, rollback := makeConnectionWithObject(t) |
1205 | 1249 | defer rollback() |
1206 | contents, err := c.ObjectGetString(CONTAINER, OBJECT) | |
1250 | contents, err := c.ObjectGetString(ctx, CONTAINER, OBJECT) | |
1207 | 1251 | if err != nil { |
1208 | 1252 | t.Fatal(err) |
1209 | 1253 | } |
1213 | 1257 | } |
1214 | 1258 | |
1215 | 1259 | func TestObjectGetBytes(t *testing.T) { |
1260 | ctx := context.Background() | |
1216 | 1261 | c, rollback := makeConnectionWithObject(t) |
1217 | 1262 | defer rollback() |
1218 | contents, err := c.ObjectGetBytes(CONTAINER, OBJECT) | |
1263 | contents, err := c.ObjectGetBytes(ctx, CONTAINER, OBJECT) | |
1219 | 1264 | if err != nil { |
1220 | 1265 | t.Fatal(err) |
1221 | 1266 | } |
1225 | 1270 | } |
1226 | 1271 | |
1227 | 1272 | func TestObjectOpen(t *testing.T) { |
1273 | ctx := context.Background() | |
1228 | 1274 | c, rollback := makeConnectionWithObject(t) |
1229 | 1275 | defer rollback() |
1230 | file, _, err := c.ObjectOpen(CONTAINER, OBJECT, true, nil) | |
1276 | file, _, err := c.ObjectOpen(ctx, CONTAINER, OBJECT, true, nil) | |
1231 | 1277 | if err != nil { |
1232 | 1278 | t.Fatal(err) |
1233 | 1279 | } |
1249 | 1295 | } |
1250 | 1296 | |
1251 | 1297 | func TestObjectOpenPartial(t *testing.T) { |
1298 | ctx := context.Background() | |
1252 | 1299 | c, rollback := makeConnectionWithObject(t) |
1253 | 1300 | defer rollback() |
1254 | file, _, err := c.ObjectOpen(CONTAINER, OBJECT, true, nil) | |
1301 | file, _, err := c.ObjectOpen(ctx, CONTAINER, OBJECT, true, nil) | |
1255 | 1302 | if err != nil { |
1256 | 1303 | t.Fatal(err) |
1257 | 1304 | } |
1273 | 1320 | } |
1274 | 1321 | |
1275 | 1322 | func TestObjectOpenLength(t *testing.T) { |
1323 | ctx := context.Background() | |
1276 | 1324 | c, rollback := makeConnectionWithObject(t) |
1277 | 1325 | defer rollback() |
1278 | file, _, err := c.ObjectOpen(CONTAINER, OBJECT, true, nil) | |
1326 | file, _, err := c.ObjectOpen(ctx, CONTAINER, OBJECT, true, nil) | |
1279 | 1327 | if err != nil { |
1280 | 1328 | t.Fatal(err) |
1281 | 1329 | } |
1282 | 1330 | // FIXME ideally this would check both branches of the Length() code |
1283 | n, err := file.Length() | |
1331 | n, err := file.Length(ctx) | |
1284 | 1332 | if err != nil { |
1285 | 1333 | t.Fatal(err) |
1286 | 1334 | } |
1294 | 1342 | } |
1295 | 1343 | |
1296 | 1344 | func TestObjectOpenNotModified(t *testing.T) { |
1345 | ctx := context.Background() | |
1297 | 1346 | c, rollback := makeConnectionWithObject(t) |
1298 | 1347 | defer rollback() |
1299 | _, _, err := c.ObjectOpen(CONTAINER, OBJECT, true, swift.Headers{ | |
1348 | _, _, err := c.ObjectOpen(ctx, CONTAINER, OBJECT, true, swift.Headers{ | |
1300 | 1349 | "If-None-Match": CONTENT_MD5, |
1301 | 1350 | }) |
1302 | 1351 | if err != swift.NotModified { |
1305 | 1354 | } |
1306 | 1355 | |
1307 | 1356 | func TestObjectOpenSeek(t *testing.T) { |
1357 | ctx := context.Background() | |
1308 | 1358 | c, rollback := makeConnectionWithObject(t) |
1309 | 1359 | defer rollback() |
1310 | 1360 | |
1330 | 1380 | {2, -4, 1}, |
1331 | 1381 | } |
1332 | 1382 | |
1333 | file, _, err := c.ObjectOpen(CONTAINER, OBJECT, true, nil) | |
1383 | file, _, err := c.ObjectOpen(ctx, CONTAINER, OBJECT, true, nil) | |
1334 | 1384 | if err != nil { |
1335 | 1385 | t.Fatal(err) |
1336 | 1386 | } |
1338 | 1388 | for _, p := range plan { |
1339 | 1389 | if p.whence >= 0 { |
1340 | 1390 | var result int64 |
1341 | result, err = file.Seek(p.offset, p.whence) | |
1391 | result, err = file.Seek(ctx, p.offset, p.whence) | |
1342 | 1392 | if err != nil { |
1343 | 1393 | t.Fatal(err, p) |
1344 | 1394 | } |
1371 | 1421 | |
1372 | 1422 | // Test seeking to the end to find the file size |
1373 | 1423 | func TestObjectOpenSeekEnd(t *testing.T) { |
1424 | ctx := context.Background() | |
1374 | 1425 | c, rollback := makeConnectionWithObject(t) |
1375 | 1426 | defer rollback() |
1376 | file, _, err := c.ObjectOpen(CONTAINER, OBJECT, true, nil) | |
1377 | if err != nil { | |
1378 | t.Fatal(err) | |
1379 | } | |
1380 | n, err := file.Seek(0, 2) // seek to end | |
1427 | file, _, err := c.ObjectOpen(ctx, CONTAINER, OBJECT, true, nil) | |
1428 | if err != nil { | |
1429 | t.Fatal(err) | |
1430 | } | |
1431 | n, err := file.Seek(ctx, 0, 2) // seek to end | |
1381 | 1432 | if err != nil { |
1382 | 1433 | t.Fatal(err) |
1383 | 1434 | } |
1396 | 1447 | } |
1397 | 1448 | |
1398 | 1449 | // Now seek back to start and check we can read the file |
1399 | n, err = file.Seek(0, 0) // seek to start | |
1450 | n, err = file.Seek(ctx, 0, 0) // seek to start | |
1400 | 1451 | if err != nil { |
1401 | 1452 | t.Fatal(err) |
1402 | 1453 | } |
1415 | 1466 | } |
1416 | 1467 | |
1417 | 1468 | func TestObjectUpdate(t *testing.T) { |
1469 | ctx := context.Background() | |
1418 | 1470 | c, rollback := makeConnectionWithObject(t) |
1419 | 1471 | defer rollback() |
1420 | err := c.ObjectUpdate(CONTAINER, OBJECT, m1.ObjectHeaders()) | |
1472 | err := c.ObjectUpdate(ctx, CONTAINER, OBJECT, m1.ObjectHeaders()) | |
1421 | 1473 | if err != nil { |
1422 | 1474 | t.Fatal(err) |
1423 | 1475 | } |
1431 | 1483 | } |
1432 | 1484 | |
1433 | 1485 | func TestObject(t *testing.T) { |
1486 | ctx := context.Background() | |
1434 | 1487 | c, rollback := makeConnectionWithObjectHeaders(t) |
1435 | 1488 | defer rollback() |
1436 | object, headers, err := c.Object(CONTAINER, OBJECT) | |
1489 | object, headers, err := c.Object(ctx, CONTAINER, OBJECT) | |
1437 | 1490 | if err != nil { |
1438 | 1491 | t.Fatal(err) |
1439 | 1492 | } |
1445 | 1498 | } |
1446 | 1499 | |
1447 | 1500 | func TestObjectUpdate2(t *testing.T) { |
1501 | ctx := context.Background() | |
1448 | 1502 | c, rollback := makeConnectionWithObjectHeaders(t) |
1449 | 1503 | defer rollback() |
1450 | err := c.ObjectUpdate(CONTAINER, OBJECT, m2.ObjectHeaders()) | |
1451 | if err != nil { | |
1452 | t.Fatal(err) | |
1453 | } | |
1454 | _, headers, err := c.Object(CONTAINER, OBJECT) | |
1504 | err := c.ObjectUpdate(ctx, CONTAINER, OBJECT, m2.ObjectHeaders()) | |
1505 | if err != nil { | |
1506 | t.Fatal(err) | |
1507 | } | |
1508 | _, headers, err := c.Object(ctx, CONTAINER, OBJECT) | |
1455 | 1509 | if err != nil { |
1456 | 1510 | t.Fatal(err) |
1457 | 1511 | } |
1459 | 1513 | } |
1460 | 1514 | |
1461 | 1515 | func TestContainers(t *testing.T) { |
1516 | ctx := context.Background() | |
1462 | 1517 | c, rollback := makeConnectionWithObjectHeaders(t) |
1463 | 1518 | defer rollback() |
1464 | containers, err := c.Containers(nil) | |
1519 | containers, err := c.Containers(ctx, nil) | |
1465 | 1520 | if err != nil { |
1466 | 1521 | t.Fatal(err) |
1467 | 1522 | } |
1487 | 1542 | } |
1488 | 1543 | |
1489 | 1544 | func TestObjectNames(t *testing.T) { |
1545 | ctx := context.Background() | |
1490 | 1546 | c, rollback := makeConnectionWithObjectHeaders(t) |
1491 | 1547 | defer rollback() |
1492 | objects, err := c.ObjectNames(CONTAINER, nil) | |
1548 | objects, err := c.ObjectNames(ctx, CONTAINER, nil) | |
1493 | 1549 | if err != nil { |
1494 | 1550 | t.Fatal(err) |
1495 | 1551 | } |
1499 | 1555 | } |
1500 | 1556 | |
1501 | 1557 | func TestObjectNamesAll(t *testing.T) { |
1558 | ctx := context.Background() | |
1502 | 1559 | c, rollback := makeConnectionWithObjectHeaders(t) |
1503 | 1560 | defer rollback() |
1504 | objects, err := c.ObjectNamesAll(CONTAINER, nil) | |
1561 | objects, err := c.ObjectNamesAll(ctx, CONTAINER, nil) | |
1505 | 1562 | if err != nil { |
1506 | 1563 | t.Fatal(err) |
1507 | 1564 | } |
1511 | 1568 | } |
1512 | 1569 | |
1513 | 1570 | func TestObjectNamesAllWithLimit(t *testing.T) { |
1571 | ctx := context.Background() | |
1514 | 1572 | c, rollback := makeConnectionWithObjectHeaders(t) |
1515 | 1573 | defer rollback() |
1516 | objects, err := c.ObjectNamesAll(CONTAINER, &swift.ObjectsOpts{Limit: 1}) | |
1574 | objects, err := c.ObjectNamesAll(ctx, CONTAINER, &swift.ObjectsOpts{Limit: 1}) | |
1517 | 1575 | if err != nil { |
1518 | 1576 | t.Fatal(err) |
1519 | 1577 | } |
1523 | 1581 | } |
1524 | 1582 | |
1525 | 1583 | func TestObjectsWalk(t *testing.T) { |
1584 | ctx := context.Background() | |
1526 | 1585 | c, rollback := makeConnectionWithObjectHeaders(t) |
1527 | 1586 | defer rollback() |
1528 | 1587 | objects := make([]string, 0) |
1529 | err := c.ObjectsWalk(container, nil, func(opts *swift.ObjectsOpts) (interface{}, error) { | |
1530 | newObjects, err := c.ObjectNames(CONTAINER, opts) | |
1588 | err := c.ObjectsWalk(ctx, container, nil, func(ctx context.Context, opts *swift.ObjectsOpts) (interface{}, error) { | |
1589 | newObjects, err := c.ObjectNames(ctx, CONTAINER, opts) | |
1531 | 1590 | if err == nil { |
1532 | 1591 | objects = append(objects, newObjects...) |
1533 | 1592 | } |
1542 | 1601 | } |
1543 | 1602 | |
1544 | 1603 | func TestObjects(t *testing.T) { |
1604 | ctx := context.Background() | |
1545 | 1605 | c, rollback := makeConnectionWithObjectHeaders(t) |
1546 | 1606 | defer rollback() |
1547 | objects, err := c.Objects(CONTAINER, &swift.ObjectsOpts{Delimiter: '/'}) | |
1607 | objects, err := c.Objects(ctx, CONTAINER, &swift.ObjectsOpts{Delimiter: '/'}) | |
1548 | 1608 | if err != nil { |
1549 | 1609 | t.Fatal(err) |
1550 | 1610 | } |
1559 | 1619 | } |
1560 | 1620 | |
1561 | 1621 | func TestObjectsDirectory(t *testing.T) { |
1622 | ctx := context.Background() | |
1562 | 1623 | c, rollback := makeConnectionWithObjectHeaders(t) |
1563 | 1624 | defer rollback() |
1564 | err := c.ObjectPutString(CONTAINER, "directory", "", "application/directory") | |
1565 | if err != nil { | |
1566 | t.Fatal(err) | |
1567 | } | |
1568 | defer c.ObjectDelete(CONTAINER, "directory") | |
1625 | err := c.ObjectPutString(ctx, CONTAINER, "directory", "", "application/directory") | |
1626 | if err != nil { | |
1627 | t.Fatal(err) | |
1628 | } | |
1629 | defer func() { | |
1630 | _ = c.ObjectDelete(ctx, CONTAINER, "directory") | |
1631 | }() | |
1569 | 1632 | |
1570 | 1633 | // Look for the directory object and check we aren't confusing |
1571 | 1634 | // it with a pseudo directory object |
1572 | objects, err := c.Objects(CONTAINER, &swift.ObjectsOpts{Delimiter: '/'}) | |
1635 | objects, err := c.Objects(ctx, CONTAINER, &swift.ObjectsOpts{Delimiter: '/'}) | |
1573 | 1636 | if err != nil { |
1574 | 1637 | t.Fatal(err) |
1575 | 1638 | } |
1593 | 1656 | } |
1594 | 1657 | |
1595 | 1658 | func TestObjectsPseudoDirectory(t *testing.T) { |
1659 | ctx := context.Background() | |
1596 | 1660 | c, rollback := makeConnectionWithObjectHeaders(t) |
1597 | 1661 | defer rollback() |
1598 | err := c.ObjectPutString(CONTAINER, "directory/puppy.jpg", "cute puppy", "") | |
1599 | if err != nil { | |
1600 | t.Fatal(err) | |
1601 | } | |
1602 | defer c.ObjectDelete(CONTAINER, "directory/puppy.jpg") | |
1662 | err := c.ObjectPutString(ctx, CONTAINER, "directory/puppy.jpg", "cute puppy", "") | |
1663 | if err != nil { | |
1664 | t.Fatal(err) | |
1665 | } | |
1666 | defer func() { | |
1667 | _ = c.ObjectDelete(ctx, CONTAINER, "directory/puppy.jpg") | |
1668 | }() | |
1603 | 1669 | |
1604 | 1670 | // Look for the pseudo directory |
1605 | objects, err := c.Objects(CONTAINER, &swift.ObjectsOpts{Delimiter: '/'}) | |
1671 | objects, err := c.Objects(ctx, CONTAINER, &swift.ObjectsOpts{Delimiter: '/'}) | |
1606 | 1672 | if err != nil { |
1607 | 1673 | t.Fatal(err) |
1608 | 1674 | } |
1624 | 1690 | } |
1625 | 1691 | |
1626 | 1692 | // Look in the pseudo directory now |
1627 | objects, err = c.Objects(CONTAINER, &swift.ObjectsOpts{Delimiter: '/', Path: "directory/"}) | |
1693 | objects, err = c.Objects(ctx, CONTAINER, &swift.ObjectsOpts{Delimiter: '/', Path: "directory/"}) | |
1628 | 1694 | if err != nil { |
1629 | 1695 | t.Fatal(err) |
1630 | 1696 | } |
1639 | 1705 | } |
1640 | 1706 | |
1641 | 1707 | func TestObjectsAll(t *testing.T) { |
1708 | ctx := context.Background() | |
1642 | 1709 | c, rollback := makeConnectionWithObjectHeaders(t) |
1643 | 1710 | defer rollback() |
1644 | objects, err := c.ObjectsAll(CONTAINER, nil) | |
1711 | objects, err := c.ObjectsAll(ctx, CONTAINER, nil) | |
1645 | 1712 | if err != nil { |
1646 | 1713 | t.Fatal(err) |
1647 | 1714 | } |
1651 | 1718 | } |
1652 | 1719 | |
1653 | 1720 | func TestObjectsAllWithLimit(t *testing.T) { |
1721 | ctx := context.Background() | |
1654 | 1722 | c, rollback := makeConnectionWithObjectHeaders(t) |
1655 | 1723 | defer rollback() |
1656 | objects, err := c.ObjectsAll(CONTAINER, &swift.ObjectsOpts{Limit: 1}) | |
1724 | objects, err := c.ObjectsAll(ctx, CONTAINER, &swift.ObjectsOpts{Limit: 1}) | |
1657 | 1725 | if err != nil { |
1658 | 1726 | t.Fatal(err) |
1659 | 1727 | } |
1663 | 1731 | } |
1664 | 1732 | |
1665 | 1733 | func TestObjectNamesWithPath(t *testing.T) { |
1734 | ctx := context.Background() | |
1666 | 1735 | c, rollback := makeConnectionWithObjectHeaders(t) |
1667 | 1736 | defer rollback() |
1668 | objects, err := c.ObjectNames(CONTAINER, &swift.ObjectsOpts{Delimiter: '/', Path: ""}) | |
1737 | objects, err := c.ObjectNames(ctx, CONTAINER, &swift.ObjectsOpts{Delimiter: '/', Path: ""}) | |
1669 | 1738 | if err != nil { |
1670 | 1739 | t.Fatal(err) |
1671 | 1740 | } |
1673 | 1742 | t.Error("Bad listing with path", objects) |
1674 | 1743 | } |
1675 | 1744 | // fmt.Println(objects) |
1676 | objects, err = c.ObjectNames(CONTAINER, &swift.ObjectsOpts{Delimiter: '/', Path: "Downloads/"}) | |
1745 | objects, err = c.ObjectNames(ctx, CONTAINER, &swift.ObjectsOpts{Delimiter: '/', Path: "Downloads/"}) | |
1677 | 1746 | if err != nil { |
1678 | 1747 | t.Fatal(err) |
1679 | 1748 | } |
1683 | 1752 | } |
1684 | 1753 | |
1685 | 1754 | func TestObjectCopy(t *testing.T) { |
1755 | ctx := context.Background() | |
1686 | 1756 | c, rollback := makeConnectionWithObjectHeaders(t) |
1687 | 1757 | defer rollback() |
1688 | _, err := c.ObjectCopy(CONTAINER, OBJECT, CONTAINER, OBJECT2, nil) | |
1689 | if err != nil { | |
1690 | t.Fatal(err) | |
1691 | } | |
1692 | err = c.ObjectDelete(CONTAINER, OBJECT2) | |
1758 | _, err := c.ObjectCopy(ctx, CONTAINER, OBJECT, CONTAINER, OBJECT2, nil) | |
1759 | if err != nil { | |
1760 | t.Fatal(err) | |
1761 | } | |
1762 | err = c.ObjectDelete(ctx, CONTAINER, OBJECT2) | |
1693 | 1763 | if err != nil { |
1694 | 1764 | t.Fatal(err) |
1695 | 1765 | } |
1696 | 1766 | } |
1697 | 1767 | |
1698 | 1768 | func TestObjectCopyDifficultName(t *testing.T) { |
1769 | ctx := context.Background() | |
1699 | 1770 | c, rollback := makeConnectionWithObjectHeaders(t) |
1700 | 1771 | defer rollback() |
1701 | 1772 | const dest = OBJECT + "?param %30%31%32 £100" |
1702 | _, err := c.ObjectCopy(CONTAINER, OBJECT, CONTAINER, dest, nil) | |
1703 | if err != nil { | |
1704 | t.Fatal(err) | |
1705 | } | |
1706 | err = c.ObjectDelete(CONTAINER, dest) | |
1773 | _, err := c.ObjectCopy(ctx, CONTAINER, OBJECT, CONTAINER, dest, nil) | |
1774 | if err != nil { | |
1775 | t.Fatal(err) | |
1776 | } | |
1777 | err = c.ObjectDelete(ctx, CONTAINER, dest) | |
1707 | 1778 | if err != nil { |
1708 | 1779 | t.Fatal(err) |
1709 | 1780 | } |
1710 | 1781 | } |
1711 | 1782 | |
1712 | 1783 | func TestObjectCopyWithMetadata(t *testing.T) { |
1784 | ctx := context.Background() | |
1713 | 1785 | c, rollback := makeConnectionWithObjectHeaders(t) |
1714 | 1786 | defer rollback() |
1715 | 1787 | m := swift.Metadata{} |
1717 | 1789 | m["hello"] = "9" |
1718 | 1790 | h := m.ObjectHeaders() |
1719 | 1791 | h["Content-Type"] = "image/jpeg" |
1720 | _, err := c.ObjectCopy(CONTAINER, OBJECT, CONTAINER, OBJECT2, h) | |
1792 | _, err := c.ObjectCopy(ctx, CONTAINER, OBJECT, CONTAINER, OBJECT2, h) | |
1721 | 1793 | if err != nil { |
1722 | 1794 | t.Fatal(err) |
1723 | 1795 | } |
1724 | 1796 | defer func() { |
1725 | err = c.ObjectDelete(CONTAINER, OBJECT2) | |
1797 | err = c.ObjectDelete(ctx, CONTAINER, OBJECT2) | |
1726 | 1798 | if err != nil { |
1727 | 1799 | t.Fatal(err) |
1728 | 1800 | } |
1729 | 1801 | }() |
1730 | 1802 | // Re-read the metadata to see if it is correct |
1731 | _, headers, err := c.Object(CONTAINER, OBJECT2) | |
1803 | _, headers, err := c.Object(ctx, CONTAINER, OBJECT2) | |
1732 | 1804 | if err != nil { |
1733 | 1805 | t.Fatal(err) |
1734 | 1806 | } |
1739 | 1811 | } |
1740 | 1812 | |
1741 | 1813 | func TestObjectMove(t *testing.T) { |
1814 | ctx := context.Background() | |
1742 | 1815 | c, rollback := makeConnectionWithObjectHeaders(t) |
1743 | 1816 | defer rollback() |
1744 | err := c.ObjectMove(CONTAINER, OBJECT, CONTAINER, OBJECT2) | |
1817 | err := c.ObjectMove(ctx, CONTAINER, OBJECT, CONTAINER, OBJECT2) | |
1745 | 1818 | if err != nil { |
1746 | 1819 | t.Fatal(err) |
1747 | 1820 | } |
1748 | 1821 | testExistenceAfterDelete(t, c, CONTAINER, OBJECT) |
1749 | _, _, err = c.Object(CONTAINER, OBJECT2) | |
1750 | if err != nil { | |
1751 | t.Fatal(err) | |
1752 | } | |
1753 | ||
1754 | err = c.ObjectMove(CONTAINER, OBJECT2, CONTAINER, OBJECT) | |
1822 | _, _, err = c.Object(ctx, CONTAINER, OBJECT2) | |
1823 | if err != nil { | |
1824 | t.Fatal(err) | |
1825 | } | |
1826 | ||
1827 | err = c.ObjectMove(ctx, CONTAINER, OBJECT2, CONTAINER, OBJECT) | |
1755 | 1828 | if err != nil { |
1756 | 1829 | t.Fatal(err) |
1757 | 1830 | } |
1758 | 1831 | testExistenceAfterDelete(t, c, CONTAINER, OBJECT2) |
1759 | _, headers, err := c.Object(CONTAINER, OBJECT) | |
1832 | _, headers, err := c.Object(ctx, CONTAINER, OBJECT) | |
1760 | 1833 | if err != nil { |
1761 | 1834 | t.Fatal(err) |
1762 | 1835 | } |
1764 | 1837 | } |
1765 | 1838 | |
1766 | 1839 | func TestObjectUpdateContentType(t *testing.T) { |
1840 | ctx := context.Background() | |
1767 | 1841 | c, rollback := makeConnectionWithObjectHeaders(t) |
1768 | 1842 | defer rollback() |
1769 | err := c.ObjectUpdateContentType(CONTAINER, OBJECT, "text/potato") | |
1843 | err := c.ObjectUpdateContentType(ctx, CONTAINER, OBJECT, "text/potato") | |
1770 | 1844 | if err != nil { |
1771 | 1845 | t.Fatal(err) |
1772 | 1846 | } |
1773 | 1847 | // Re-read the metadata to see if it is correct |
1774 | _, headers, err := c.Object(CONTAINER, OBJECT) | |
1848 | _, headers, err := c.Object(ctx, CONTAINER, OBJECT) | |
1775 | 1849 | if err != nil { |
1776 | 1850 | t.Fatal(err) |
1777 | 1851 | } |
1782 | 1856 | } |
1783 | 1857 | |
1784 | 1858 | func TestVersionContainerCreate(t *testing.T) { |
1859 | ctx := context.Background() | |
1785 | 1860 | c, rollback := makeConnectionAuth(t) |
1786 | 1861 | defer rollback() |
1787 | err := c.VersionContainerCreate(CURRENT_CONTAINER, VERSIONS_CONTAINER) | |
1862 | err := c.VersionContainerCreate(ctx, CURRENT_CONTAINER, VERSIONS_CONTAINER) | |
1788 | 1863 | defer func() { |
1789 | c.ContainerDelete(CURRENT_CONTAINER) | |
1790 | c.ContainerDelete(VERSIONS_CONTAINER) | |
1864 | _ = c.ContainerDelete(ctx, CURRENT_CONTAINER) | |
1865 | _ = c.ContainerDelete(ctx, VERSIONS_CONTAINER) | |
1791 | 1866 | }() |
1792 | 1867 | if err != nil { |
1793 | 1868 | if err == swift.Forbidden { |
1799 | 1874 | } |
1800 | 1875 | |
1801 | 1876 | func TestVersionObjectAdd(t *testing.T) { |
1877 | ctx := context.Background() | |
1802 | 1878 | c, rollback := makeConnectionWithVersionsContainer(t) |
1803 | 1879 | defer rollback() |
1804 | 1880 | if skipVersionTests { |
1806 | 1882 | return |
1807 | 1883 | } |
1808 | 1884 | // Version 1 |
1809 | if err := c.ObjectPutString(CURRENT_CONTAINER, OBJECT, CONTENTS, ""); err != nil { | |
1885 | if err := c.ObjectPutString(ctx, CURRENT_CONTAINER, OBJECT, CONTENTS, ""); err != nil { | |
1810 | 1886 | t.Fatal(err) |
1811 | 1887 | } |
1812 | 1888 | defer func() { |
1813 | err := c.ObjectDelete(CURRENT_CONTAINER, OBJECT) | |
1889 | err := c.ObjectDelete(ctx, CURRENT_CONTAINER, OBJECT) | |
1814 | 1890 | if err != nil { |
1815 | 1891 | t.Fatal(err) |
1816 | 1892 | } |
1817 | 1893 | }() |
1818 | if contents, err := c.ObjectGetString(CURRENT_CONTAINER, OBJECT); err != nil { | |
1894 | if contents, err := c.ObjectGetString(ctx, CURRENT_CONTAINER, OBJECT); err != nil { | |
1819 | 1895 | t.Fatal(err) |
1820 | 1896 | } else if contents != CONTENTS { |
1821 | 1897 | t.Error("Contents wrong") |
1822 | 1898 | } |
1823 | 1899 | |
1824 | 1900 | // Version 2 |
1825 | if err := c.ObjectPutString(CURRENT_CONTAINER, OBJECT, CONTENTS2, ""); err != nil { | |
1901 | if err := c.ObjectPutString(ctx, CURRENT_CONTAINER, OBJECT, CONTENTS2, ""); err != nil { | |
1826 | 1902 | t.Fatal(err) |
1827 | 1903 | } |
1828 | 1904 | defer func() { |
1829 | err := c.ObjectDelete(CURRENT_CONTAINER, OBJECT) | |
1905 | err := c.ObjectDelete(ctx, CURRENT_CONTAINER, OBJECT) | |
1830 | 1906 | if err != nil { |
1831 | 1907 | t.Fatal(err) |
1832 | 1908 | } |
1833 | 1909 | }() |
1834 | if contents, err := c.ObjectGetString(CURRENT_CONTAINER, OBJECT); err != nil { | |
1910 | if contents, err := c.ObjectGetString(ctx, CURRENT_CONTAINER, OBJECT); err != nil { | |
1835 | 1911 | t.Fatal(err) |
1836 | 1912 | } else if contents != CONTENTS2 { |
1837 | 1913 | t.Error("Contents wrong") |
1838 | 1914 | } |
1839 | 1915 | |
1840 | 1916 | // Version 3 |
1841 | if err := c.ObjectPutString(CURRENT_CONTAINER, OBJECT, CONTENTS2, ""); err != nil { | |
1917 | if err := c.ObjectPutString(ctx, CURRENT_CONTAINER, OBJECT, CONTENTS2, ""); err != nil { | |
1842 | 1918 | t.Fatal(err) |
1843 | 1919 | } |
1844 | 1920 | defer func() { |
1845 | err := c.ObjectDelete(CURRENT_CONTAINER, OBJECT) | |
1921 | err := c.ObjectDelete(ctx, CURRENT_CONTAINER, OBJECT) | |
1846 | 1922 | if err != nil { |
1847 | 1923 | t.Fatal(err) |
1848 | 1924 | } |
1850 | 1926 | } |
1851 | 1927 | |
1852 | 1928 | func TestVersionObjectList(t *testing.T) { |
1929 | ctx := context.Background() | |
1853 | 1930 | c, rollback := makeConnectionWithVersionsObject(t) |
1854 | 1931 | defer rollback() |
1855 | 1932 | if skipVersionTests { |
1856 | 1933 | t.Log("Server doesn't support Versions - skipping test") |
1857 | 1934 | return |
1858 | 1935 | } |
1859 | list, err := c.VersionObjectList(VERSIONS_CONTAINER, OBJECT) | |
1936 | list, err := c.VersionObjectList(ctx, VERSIONS_CONTAINER, OBJECT) | |
1860 | 1937 | if err != nil { |
1861 | 1938 | t.Fatal(err) |
1862 | 1939 | } |
1867 | 1944 | } |
1868 | 1945 | |
1869 | 1946 | func TestVersionObjectDelete(t *testing.T) { |
1947 | ctx := context.Background() | |
1870 | 1948 | c, rollback := makeConnectionWithVersionsObject(t) |
1871 | 1949 | defer rollback() |
1872 | 1950 | if skipVersionTests { |
1874 | 1952 | return |
1875 | 1953 | } |
1876 | 1954 | // Delete Version 3 |
1877 | if err := c.ObjectDelete(CURRENT_CONTAINER, OBJECT); err != nil { | |
1955 | if err := c.ObjectDelete(ctx, CURRENT_CONTAINER, OBJECT); err != nil { | |
1878 | 1956 | t.Fatal(err) |
1879 | 1957 | } |
1880 | 1958 | |
1881 | 1959 | // Delete Version 2 |
1882 | if err := c.ObjectDelete(CURRENT_CONTAINER, OBJECT); err != nil { | |
1960 | if err := c.ObjectDelete(ctx, CURRENT_CONTAINER, OBJECT); err != nil { | |
1883 | 1961 | t.Fatal(err) |
1884 | 1962 | } |
1885 | 1963 | |
1886 | 1964 | // Contents should be reverted to Version 1 |
1887 | if contents, err := c.ObjectGetString(CURRENT_CONTAINER, OBJECT); err != nil { | |
1965 | if contents, err := c.ObjectGetString(ctx, CURRENT_CONTAINER, OBJECT); err != nil { | |
1888 | 1966 | t.Fatal(err) |
1889 | 1967 | } else if contents != CONTENTS { |
1890 | 1968 | t.Error("Contents wrong") |
1892 | 1970 | } |
1893 | 1971 | |
1894 | 1972 | func TestVersionDeleteContent(t *testing.T) { |
1973 | ctx := context.Background() | |
1895 | 1974 | c, rollback := makeConnectionWithVersionsObject(t) |
1896 | 1975 | defer rollback() |
1897 | 1976 | if skipVersionTests { |
1899 | 1978 | return |
1900 | 1979 | } |
1901 | 1980 | // Delete Version 3 |
1902 | if err := c.ObjectDelete(CURRENT_CONTAINER, OBJECT); err != nil { | |
1981 | if err := c.ObjectDelete(ctx, CURRENT_CONTAINER, OBJECT); err != nil { | |
1903 | 1982 | t.Fatal(err) |
1904 | 1983 | } |
1905 | 1984 | // Delete Version 2 |
1906 | if err := c.ObjectDelete(CURRENT_CONTAINER, OBJECT); err != nil { | |
1985 | if err := c.ObjectDelete(ctx, CURRENT_CONTAINER, OBJECT); err != nil { | |
1907 | 1986 | t.Fatal(err) |
1908 | 1987 | } |
1909 | 1988 | // Delete Version 1 |
1910 | if err := c.ObjectDelete(CURRENT_CONTAINER, OBJECT); err != nil { | |
1911 | t.Fatal(err) | |
1912 | } | |
1913 | if err := c.ObjectDelete(CURRENT_CONTAINER, OBJECT); err != swift.ObjectNotFound { | |
1989 | if err := c.ObjectDelete(ctx, CURRENT_CONTAINER, OBJECT); err != nil { | |
1990 | t.Fatal(err) | |
1991 | } | |
1992 | if err := c.ObjectDelete(ctx, CURRENT_CONTAINER, OBJECT); err != swift.ObjectNotFound { | |
1914 | 1993 | t.Fatalf("Expecting Object not found error, got: %v", err) |
1915 | 1994 | } |
1916 | 1995 | } |
1918 | 1997 | // Check for non existence after delete |
1919 | 1998 | // May have to do it a few times to wait for swift to be consistent. |
1920 | 1999 | func testExistenceAfterDelete(t *testing.T, c *swift.Connection, container, object string) { |
2000 | ctx := context.Background() | |
1921 | 2001 | for i := 10; i <= 0; i-- { |
1922 | _, _, err := c.Object(container, object) | |
2002 | _, _, err := c.Object(ctx, container, object) | |
1923 | 2003 | if err == swift.ObjectNotFound { |
1924 | 2004 | break |
1925 | 2005 | } |
1931 | 2011 | } |
1932 | 2012 | |
1933 | 2013 | func TestObjectDelete(t *testing.T) { |
2014 | ctx := context.Background() | |
1934 | 2015 | c, rollback := makeConnectionWithObject(t) |
1935 | 2016 | defer rollback() |
1936 | err := c.ObjectDelete(CONTAINER, OBJECT) | |
2017 | err := c.ObjectDelete(ctx, CONTAINER, OBJECT) | |
1937 | 2018 | if err != nil { |
1938 | 2019 | t.Fatal(err) |
1939 | 2020 | } |
1940 | 2021 | testExistenceAfterDelete(t, c, CONTAINER, OBJECT) |
1941 | err = c.ObjectDelete(CONTAINER, OBJECT) | |
2022 | err = c.ObjectDelete(ctx, CONTAINER, OBJECT) | |
1942 | 2023 | if err != swift.ObjectNotFound { |
1943 | 2024 | t.Fatal("Expecting Object not found", err) |
1944 | 2025 | } |
1945 | 2026 | } |
1946 | 2027 | |
1947 | 2028 | func TestBulkDelete(t *testing.T) { |
2029 | ctx := context.Background() | |
1948 | 2030 | c, rollback := makeConnectionWithContainer(t) |
1949 | 2031 | defer rollback() |
1950 | result, err := c.BulkDelete(CONTAINER, []string{OBJECT}) | |
2032 | result, err := c.BulkDelete(ctx, CONTAINER, []string{OBJECT}) | |
1951 | 2033 | if err == swift.Forbidden { |
1952 | 2034 | t.Log("Server doesn't support BulkDelete - skipping test") |
1953 | 2035 | return |
1961 | 2043 | if result.NumberDeleted != 0 { |
1962 | 2044 | t.Error("Expected 0, actual:", result.NumberDeleted) |
1963 | 2045 | } |
1964 | err = c.ObjectPutString(CONTAINER, OBJECT, CONTENTS, "") | |
1965 | if err != nil { | |
1966 | t.Fatal(err) | |
1967 | } | |
1968 | result, err = c.BulkDelete(CONTAINER, []string{OBJECT2, OBJECT}) | |
2046 | err = c.ObjectPutString(ctx, CONTAINER, OBJECT, CONTENTS, "") | |
2047 | if err != nil { | |
2048 | t.Fatal(err) | |
2049 | } | |
2050 | result, err = c.BulkDelete(ctx, CONTAINER, []string{OBJECT2, OBJECT}) | |
1969 | 2051 | if err != nil { |
1970 | 2052 | t.Fatal(err) |
1971 | 2053 | } |
1979 | 2061 | } |
1980 | 2062 | |
1981 | 2063 | func TestBulkUpload(t *testing.T) { |
2064 | ctx := context.Background() | |
1982 | 2065 | c, rollback := makeConnectionWithContainer(t) |
1983 | 2066 | defer rollback() |
1984 | 2067 | buffer := new(bytes.Buffer) |
2003 | 2086 | t.Fatal(err) |
2004 | 2087 | } |
2005 | 2088 | |
2006 | result, err := c.BulkUpload(CONTAINER, buffer, swift.UploadTar, nil) | |
2089 | result, err := c.BulkUpload(ctx, CONTAINER, buffer, swift.UploadTar, nil) | |
2007 | 2090 | if err == swift.Forbidden { |
2008 | 2091 | t.Log("Server doesn't support BulkUpload - skipping test") |
2009 | 2092 | return |
2012 | 2095 | t.Fatal(err) |
2013 | 2096 | } |
2014 | 2097 | defer func() { |
2015 | err = c.ObjectDelete(CONTAINER, OBJECT) | |
2016 | if err != nil { | |
2017 | t.Fatal(err) | |
2018 | } | |
2019 | err = c.ObjectDelete(CONTAINER, OBJECT2) | |
2098 | err = c.ObjectDelete(ctx, CONTAINER, OBJECT) | |
2099 | if err != nil { | |
2100 | t.Fatal(err) | |
2101 | } | |
2102 | err = c.ObjectDelete(ctx, CONTAINER, OBJECT2) | |
2020 | 2103 | if err != nil { |
2021 | 2104 | t.Fatal(err) |
2022 | 2105 | } |
2026 | 2109 | } |
2027 | 2110 | t.Log("Errors:", result.Errors) |
2028 | 2111 | |
2029 | _, _, err = c.Object(CONTAINER, OBJECT) | |
2112 | _, _, err = c.Object(ctx, CONTAINER, OBJECT) | |
2030 | 2113 | if err != nil { |
2031 | 2114 | t.Error("Expecting object to be found") |
2032 | 2115 | } |
2033 | _, _, err = c.Object(CONTAINER, OBJECT2) | |
2116 | _, _, err = c.Object(ctx, CONTAINER, OBJECT2) | |
2034 | 2117 | if err != nil { |
2035 | 2118 | t.Error("Expecting object to be found") |
2036 | 2119 | } |
2037 | 2120 | } |
2038 | 2121 | |
2039 | 2122 | func TestObjectDifficultName(t *testing.T) { |
2123 | ctx := context.Background() | |
2040 | 2124 | c, rollback := makeConnectionWithContainer(t) |
2041 | 2125 | defer rollback() |
2042 | 2126 | const name = `hello? sausage/êé/Hello, 世界/ " ' @ < > & ?/` |
2043 | err := c.ObjectPutString(CONTAINER, name, CONTENTS, "") | |
2127 | err := c.ObjectPutString(ctx, CONTAINER, name, CONTENTS, "") | |
2044 | 2128 | if err != nil { |
2045 | 2129 | t.Fatal(err) |
2046 | 2130 | } |
2047 | 2131 | defer func() { |
2048 | err = c.ObjectDelete(CONTAINER, name) | |
2132 | err = c.ObjectDelete(ctx, CONTAINER, name) | |
2049 | 2133 | if err != nil { |
2050 | 2134 | t.Fatal(err) |
2051 | 2135 | } |
2052 | 2136 | }() |
2053 | objects, err := c.ObjectNamesAll(CONTAINER, nil) | |
2137 | objects, err := c.ObjectNamesAll(ctx, CONTAINER, nil) | |
2054 | 2138 | if err != nil { |
2055 | 2139 | t.Error(err) |
2056 | 2140 | } |
2067 | 2151 | } |
2068 | 2152 | |
2069 | 2153 | func TestTempUrl(t *testing.T) { |
2154 | ctx := context.Background() | |
2070 | 2155 | c, rollback := makeConnectionWithContainer(t) |
2071 | 2156 | defer rollback() |
2072 | err := c.ObjectPutBytes(CONTAINER, OBJECT, []byte(CONTENTS), "") | |
2157 | err := c.ObjectPutBytes(ctx, CONTAINER, OBJECT, []byte(CONTENTS), "") | |
2073 | 2158 | if err != nil { |
2074 | 2159 | t.Fatal(err) |
2075 | 2160 | } |
2076 | 2161 | defer func() { |
2077 | err = c.ObjectDelete(CONTAINER, OBJECT) | |
2162 | err = c.ObjectDelete(ctx, CONTAINER, OBJECT) | |
2078 | 2163 | if err != nil { |
2079 | 2164 | t.Fatal(err) |
2080 | 2165 | } |
2082 | 2167 | |
2083 | 2168 | m := swift.Metadata{} |
2084 | 2169 | m["temp-url-key"] = SECRET_KEY |
2085 | err = c.AccountUpdate(m.AccountHeaders()) | |
2170 | err = c.AccountUpdate(ctx, m.AccountHeaders()) | |
2086 | 2171 | if err != nil { |
2087 | 2172 | t.Fatal(err) |
2088 | 2173 | } |
2093 | 2178 | if err != nil { |
2094 | 2179 | t.Fatal("Failed to retrieve file from temporary url") |
2095 | 2180 | } |
2096 | defer resp.Body.Close() | |
2181 | defer func() { | |
2182 | err := resp.Body.Close() | |
2183 | if err != nil { | |
2184 | t.Error("Close failed", err) | |
2185 | } | |
2186 | }() | |
2097 | 2187 | if resp.StatusCode == 401 { |
2098 | 2188 | t.Log("Server doesn't support tempurl") |
2099 | 2189 | } else if resp.StatusCode != 200 { |
2108 | 2198 | if err != nil { |
2109 | 2199 | t.Fatal("Failed to retrieve file from temporary url") |
2110 | 2200 | } |
2111 | defer resp.Body.Close() | |
2201 | defer func() { | |
2202 | err := resp.Body.Close() | |
2203 | if err != nil { | |
2204 | t.Error("Close failed", err) | |
2205 | } | |
2206 | }() | |
2112 | 2207 | if resp.StatusCode != 401 { |
2113 | 2208 | t.Fatal("Expecting server to forbid access to object") |
2114 | 2209 | } |
2116 | 2211 | } |
2117 | 2212 | |
2118 | 2213 | func TestQueryInfo(t *testing.T) { |
2214 | ctx := context.Background() | |
2119 | 2215 | c, rollback := makeConnectionAuth(t) |
2120 | 2216 | defer rollback() |
2121 | infos, err := c.QueryInfo() | |
2217 | infos, err := c.QueryInfo(ctx) | |
2122 | 2218 | if err != nil { |
2123 | 2219 | t.Log("Server doesn't support querying info") |
2124 | 2220 | return |
2129 | 2225 | } |
2130 | 2226 | |
2131 | 2227 | func TestDLOCreate(t *testing.T) { |
2228 | ctx := context.Background() | |
2132 | 2229 | c, rollback := makeConnectionWithSegmentsContainer(t) |
2133 | 2230 | defer rollback() |
2134 | 2231 | |
2137 | 2234 | ObjectName: OBJECT, |
2138 | 2235 | ContentType: "image/jpeg", |
2139 | 2236 | } |
2140 | out, err := c.DynamicLargeObjectCreate(&opts) | |
2237 | out, err := c.DynamicLargeObjectCreate(ctx, &opts) | |
2141 | 2238 | if err != nil { |
2142 | 2239 | t.Fatal(err) |
2143 | 2240 | } |
2144 | 2241 | defer func() { |
2145 | err = c.DynamicLargeObjectDelete(CONTAINER, OBJECT) | |
2242 | err = c.DynamicLargeObjectDelete(ctx, CONTAINER, OBJECT) | |
2146 | 2243 | if err != nil { |
2147 | 2244 | t.Fatal(err) |
2148 | 2245 | } |
2156 | 2253 | t.Fatal(err) |
2157 | 2254 | } |
2158 | 2255 | } |
2159 | err = out.Close() | |
2256 | err = out.CloseWithContext(ctx) | |
2160 | 2257 | if err != nil { |
2161 | 2258 | t.Error(err) |
2162 | 2259 | } |
2163 | 2260 | expected := buf.String() |
2164 | contents, err := c.ObjectGetString(CONTAINER, OBJECT) | |
2261 | contents, err := c.ObjectGetString(ctx, CONTAINER, OBJECT) | |
2165 | 2262 | if err != nil { |
2166 | 2263 | t.Error(err) |
2167 | 2264 | } |
2168 | 2265 | if contents != expected { |
2169 | 2266 | t.Errorf("Contents wrong, expected %q, got: %q", expected, contents) |
2170 | 2267 | } |
2171 | info, _, err := c.Object(CONTAINER, OBJECT) | |
2268 | info, _, err := c.Object(ctx, CONTAINER, OBJECT) | |
2172 | 2269 | if err != nil { |
2173 | 2270 | t.Fatal(err) |
2174 | 2271 | } |
2181 | 2278 | } |
2182 | 2279 | |
2183 | 2280 | func TestDLOInsert(t *testing.T) { |
2281 | ctx := context.Background() | |
2184 | 2282 | c, rollback := makeConnectionWithDLO(t) |
2185 | 2283 | defer rollback() |
2186 | 2284 | opts := swift.LargeObjectOpts{ |
2189 | 2287 | CheckHash: true, |
2190 | 2288 | ContentType: "image/jpeg", |
2191 | 2289 | } |
2192 | out, err := c.DynamicLargeObjectCreateFile(&opts) | |
2290 | out, err := c.DynamicLargeObjectCreateFile(ctx, &opts) | |
2193 | 2291 | if err != nil { |
2194 | 2292 | t.Fatal(err) |
2195 | 2293 | } |
2200 | 2298 | if err != nil { |
2201 | 2299 | t.Fatal(err) |
2202 | 2300 | } |
2203 | fmt.Fprintf(buf, "\n%d %s\n", 1, CONTENTS) | |
2204 | err = out.Close() | |
2301 | _, _ = fmt.Fprintf(buf, "\n%d %s\n", 1, CONTENTS) | |
2302 | err = out.CloseWithContext(ctx) | |
2205 | 2303 | if err != nil { |
2206 | 2304 | t.Error(err) |
2207 | 2305 | } |
2208 | 2306 | expected := buf.String() |
2209 | contents, err := c.ObjectGetString(CONTAINER, OBJECT) | |
2307 | contents, err := c.ObjectGetString(ctx, CONTAINER, OBJECT) | |
2210 | 2308 | if err != nil { |
2211 | 2309 | t.Error(err) |
2212 | 2310 | } |
2216 | 2314 | } |
2217 | 2315 | |
2218 | 2316 | func TestDLOAppend(t *testing.T) { |
2317 | ctx := context.Background() | |
2219 | 2318 | c, rollback := makeConnectionWithDLO(t) |
2220 | 2319 | defer rollback() |
2221 | 2320 | opts := swift.LargeObjectOpts{ |
2225 | 2324 | CheckHash: true, |
2226 | 2325 | ContentType: "image/jpeg", |
2227 | 2326 | } |
2228 | out, err := c.DynamicLargeObjectCreateFile(&opts) | |
2229 | if err != nil { | |
2230 | t.Fatal(err) | |
2231 | } | |
2232 | ||
2233 | contents, err := c.ObjectGetString(CONTAINER, OBJECT) | |
2327 | out, err := c.DynamicLargeObjectCreateFile(ctx, &opts) | |
2328 | if err != nil { | |
2329 | t.Fatal(err) | |
2330 | } | |
2331 | ||
2332 | contents, err := c.ObjectGetString(ctx, CONTAINER, OBJECT) | |
2333 | if err != nil { | |
2334 | t.Fatal(err) | |
2335 | } | |
2234 | 2336 | buf := bytes.NewBuffer([]byte(contents)) |
2235 | 2337 | multi := io.MultiWriter(buf, out) |
2236 | 2338 | for i := 0; i < 2; i++ { |
2239 | 2341 | t.Fatal(err) |
2240 | 2342 | } |
2241 | 2343 | } |
2242 | err = out.Close() | |
2344 | err = out.CloseWithContext(ctx) | |
2243 | 2345 | if err != nil { |
2244 | 2346 | t.Error(err) |
2245 | 2347 | } |
2246 | 2348 | expected := buf.String() |
2247 | contents, err = c.ObjectGetString(CONTAINER, OBJECT) | |
2349 | contents, err = c.ObjectGetString(ctx, CONTAINER, OBJECT) | |
2248 | 2350 | if err != nil { |
2249 | 2351 | t.Error(err) |
2250 | 2352 | } |
2254 | 2356 | } |
2255 | 2357 | |
2256 | 2358 | func TestDLOTruncate(t *testing.T) { |
2359 | ctx := context.Background() | |
2257 | 2360 | c, rollback := makeConnectionWithDLO(t) |
2258 | 2361 | defer rollback() |
2259 | 2362 | opts := swift.LargeObjectOpts{ |
2263 | 2366 | CheckHash: true, |
2264 | 2367 | ContentType: "image/jpeg", |
2265 | 2368 | } |
2266 | out, err := c.DynamicLargeObjectCreateFile(&opts) | |
2369 | out, err := c.DynamicLargeObjectCreateFile(ctx, &opts) | |
2267 | 2370 | if err != nil { |
2268 | 2371 | t.Fatal(err) |
2269 | 2372 | } |
2274 | 2377 | if err != nil { |
2275 | 2378 | t.Fatal(err) |
2276 | 2379 | } |
2277 | err = out.Close() | |
2380 | err = out.CloseWithContext(ctx) | |
2278 | 2381 | if err != nil { |
2279 | 2382 | t.Error(err) |
2280 | 2383 | } |
2281 | 2384 | expected := buf.String() |
2282 | contents, err := c.ObjectGetString(CONTAINER, OBJECT) | |
2385 | contents, err := c.ObjectGetString(ctx, CONTAINER, OBJECT) | |
2283 | 2386 | if err != nil { |
2284 | 2387 | t.Error(err) |
2285 | 2388 | } |
2289 | 2392 | } |
2290 | 2393 | |
2291 | 2394 | func TestDLOMove(t *testing.T) { |
2395 | ctx := context.Background() | |
2292 | 2396 | c, rollback := makeConnectionWithDLO(t) |
2293 | 2397 | defer rollback() |
2294 | contents, err := c.ObjectGetString(CONTAINER, OBJECT) | |
2295 | if err != nil { | |
2296 | t.Fatal(err) | |
2297 | } | |
2298 | ||
2299 | err = c.DynamicLargeObjectMove(CONTAINER, OBJECT, CONTAINER, OBJECT2) | |
2398 | contents, err := c.ObjectGetString(ctx, CONTAINER, OBJECT) | |
2399 | if err != nil { | |
2400 | t.Fatal(err) | |
2401 | } | |
2402 | ||
2403 | err = c.DynamicLargeObjectMove(ctx, CONTAINER, OBJECT, CONTAINER, OBJECT2) | |
2300 | 2404 | if err != nil { |
2301 | 2405 | t.Fatal(err) |
2302 | 2406 | } |
2303 | 2407 | defer func() { |
2304 | err = c.DynamicLargeObjectDelete(CONTAINER, OBJECT2) | |
2408 | err = c.DynamicLargeObjectDelete(ctx, CONTAINER, OBJECT2) | |
2305 | 2409 | if err != nil { |
2306 | 2410 | t.Fatal(err) |
2307 | 2411 | } |
2308 | 2412 | }() |
2309 | 2413 | |
2310 | contents2, err := c.ObjectGetString(CONTAINER, OBJECT2) | |
2414 | contents2, err := c.ObjectGetString(ctx, CONTAINER, OBJECT2) | |
2311 | 2415 | if err != nil { |
2312 | 2416 | t.Fatal(err) |
2313 | 2417 | } |
2318 | 2422 | } |
2319 | 2423 | |
2320 | 2424 | func TestDLONoSegmentContainer(t *testing.T) { |
2425 | ctx := context.Background() | |
2321 | 2426 | c, rollback := makeConnectionWithDLO(t) |
2322 | 2427 | defer rollback() |
2323 | 2428 | opts := swift.LargeObjectOpts{ |
2326 | 2431 | ContentType: "image/jpeg", |
2327 | 2432 | SegmentContainer: CONTAINER, |
2328 | 2433 | } |
2329 | out, err := c.DynamicLargeObjectCreate(&opts) | |
2434 | out, err := c.DynamicLargeObjectCreate(ctx, &opts) | |
2330 | 2435 | if err != nil { |
2331 | 2436 | t.Fatal(err) |
2332 | 2437 | } |
2339 | 2444 | t.Fatal(err) |
2340 | 2445 | } |
2341 | 2446 | } |
2342 | err = out.Close() | |
2447 | err = out.CloseWithContext(ctx) | |
2343 | 2448 | if err != nil { |
2344 | 2449 | t.Error(err) |
2345 | 2450 | } |
2346 | 2451 | expected := buf.String() |
2347 | contents, err := c.ObjectGetString(CONTAINER, OBJECT) | |
2452 | contents, err := c.ObjectGetString(ctx, CONTAINER, OBJECT) | |
2348 | 2453 | if err != nil { |
2349 | 2454 | t.Error(err) |
2350 | 2455 | } |
2354 | 2459 | } |
2355 | 2460 | |
2356 | 2461 | func TestDLOCreateMissingSegmentsInList(t *testing.T) { |
2462 | ctx := context.Background() | |
2357 | 2463 | c, rollback := makeConnectionWithContainer(t) |
2358 | 2464 | defer rollback() |
2359 | 2465 | |
2368 | 2474 | w.Header().Set(k, v[0]) |
2369 | 2475 | } |
2370 | 2476 | w.WriteHeader(recorder.Code) |
2371 | w.Write([]byte("null\n")) | |
2477 | _, _ = w.Write([]byte("null\n")) | |
2372 | 2478 | }) |
2373 | 2479 | defer srv.UnsetOverride(listURL) |
2374 | 2480 | |
2375 | 2481 | headers := swift.Headers{} |
2376 | err := c.ContainerCreate(SEGMENTS_CONTAINER, headers) | |
2482 | err := c.ContainerCreate(ctx, SEGMENTS_CONTAINER, headers) | |
2377 | 2483 | if err != nil { |
2378 | 2484 | t.Fatal(err) |
2379 | 2485 | } |
2380 | 2486 | defer func() { |
2381 | err = c.ContainerDelete(SEGMENTS_CONTAINER) | |
2487 | err = c.ContainerDelete(ctx, SEGMENTS_CONTAINER) | |
2382 | 2488 | if err != nil { |
2383 | 2489 | t.Fatal(err) |
2384 | 2490 | } |
2389 | 2495 | ObjectName: OBJECT, |
2390 | 2496 | ContentType: "image/jpeg", |
2391 | 2497 | } |
2392 | out, err := c.DynamicLargeObjectCreate(&opts) | |
2498 | out, err := c.DynamicLargeObjectCreate(ctx, &opts) | |
2393 | 2499 | if err != nil { |
2394 | 2500 | t.Fatal(err) |
2395 | 2501 | } |
2396 | 2502 | defer func() { |
2397 | err = c.DynamicLargeObjectDelete(CONTAINER, OBJECT) | |
2503 | err = c.DynamicLargeObjectDelete(ctx, CONTAINER, OBJECT) | |
2398 | 2504 | if err != nil { |
2399 | 2505 | t.Fatal(err) |
2400 | 2506 | } |
2408 | 2514 | t.Fatal(err) |
2409 | 2515 | } |
2410 | 2516 | } |
2411 | err = out.Close() | |
2517 | err = out.CloseWithContext(ctx) | |
2412 | 2518 | if err != nil { |
2413 | 2519 | t.Error(err) |
2414 | 2520 | } |
2415 | 2521 | expected := buf.String() |
2416 | contents, err := c.ObjectGetString(CONTAINER, OBJECT) | |
2522 | contents, err := c.ObjectGetString(ctx, CONTAINER, OBJECT) | |
2417 | 2523 | if err != nil { |
2418 | 2524 | t.Error(err) |
2419 | 2525 | } |
2423 | 2529 | } |
2424 | 2530 | |
2425 | 2531 | func TestDLOCreateIncorrectSize(t *testing.T) { |
2532 | ctx := context.Background() | |
2426 | 2533 | c, rollback := makeConnectionWithContainer(t) |
2427 | 2534 | defer rollback() |
2428 | 2535 | |
2445 | 2552 | } |
2446 | 2553 | } |
2447 | 2554 | w.WriteHeader(recorder.Code) |
2448 | w.Write(recorder.Body.Bytes()) | |
2555 | _, _ = w.Write(recorder.Body.Bytes()) | |
2449 | 2556 | }) |
2450 | 2557 | defer srv.UnsetOverride(listURL) |
2451 | 2558 | |
2452 | 2559 | headers := swift.Headers{} |
2453 | err := c.ContainerCreate(SEGMENTS_CONTAINER, headers) | |
2560 | err := c.ContainerCreate(ctx, SEGMENTS_CONTAINER, headers) | |
2454 | 2561 | if err != nil { |
2455 | 2562 | t.Fatal(err) |
2456 | 2563 | } |
2457 | 2564 | defer func() { |
2458 | err = c.ContainerDelete(SEGMENTS_CONTAINER) | |
2565 | err = c.ContainerDelete(ctx, SEGMENTS_CONTAINER) | |
2459 | 2566 | if err != nil { |
2460 | 2567 | t.Fatal(err) |
2461 | 2568 | } |
2466 | 2573 | ObjectName: OBJECT, |
2467 | 2574 | ContentType: "image/jpeg", |
2468 | 2575 | } |
2469 | out, err := c.DynamicLargeObjectCreate(&opts) | |
2576 | out, err := c.DynamicLargeObjectCreate(ctx, &opts) | |
2470 | 2577 | if err != nil { |
2471 | 2578 | t.Fatal(err) |
2472 | 2579 | } |
2473 | 2580 | defer func() { |
2474 | err = c.DynamicLargeObjectDelete(CONTAINER, OBJECT) | |
2581 | err = c.DynamicLargeObjectDelete(ctx, CONTAINER, OBJECT) | |
2475 | 2582 | if err != nil { |
2476 | 2583 | t.Fatal(err) |
2477 | 2584 | } |
2484 | 2591 | t.Fatal(err) |
2485 | 2592 | } |
2486 | 2593 | } |
2487 | err = out.Close() | |
2594 | err = out.CloseWithContext(ctx) | |
2488 | 2595 | if err != nil { |
2489 | 2596 | t.Error(err) |
2490 | 2597 | } |
2492 | 2599 | t.Errorf("Unexpected HEAD requests count, expected %d, got: %d", expectedHeadCount, headCount) |
2493 | 2600 | } |
2494 | 2601 | expected := buf.String() |
2495 | contents, err := c.ObjectGetString(CONTAINER, OBJECT) | |
2602 | contents, err := c.ObjectGetString(ctx, CONTAINER, OBJECT) | |
2496 | 2603 | if err != nil { |
2497 | 2604 | t.Error(err) |
2498 | 2605 | } |
2502 | 2609 | } |
2503 | 2610 | |
2504 | 2611 | func TestDLOConcurrentWrite(t *testing.T) { |
2612 | ctx := context.Background() | |
2505 | 2613 | c, rollback := makeConnectionWithSegmentsContainer(t) |
2506 | 2614 | defer rollback() |
2507 | 2615 | |
2516 | 2624 | ObjectName: objName, |
2517 | 2625 | ContentType: "image/jpeg", |
2518 | 2626 | } |
2519 | out, err := c.DynamicLargeObjectCreate(&opts) | |
2627 | out, err := c.DynamicLargeObjectCreate(ctx, &opts) | |
2520 | 2628 | if err != nil { |
2521 | 2629 | t.Fatal(err) |
2522 | 2630 | } |
2523 | 2631 | defer func() { |
2524 | err = c.DynamicLargeObjectDelete(CONTAINER, objName) | |
2632 | err = c.DynamicLargeObjectDelete(ctx, CONTAINER, objName) | |
2525 | 2633 | if err != nil { |
2526 | 2634 | t.Fatal(err) |
2527 | 2635 | } |
2543 | 2651 | t.Fatalf("expected to write %d, got: %d", chunkSize, n) |
2544 | 2652 | } |
2545 | 2653 | } |
2546 | err = out.Close() | |
2654 | err = out.CloseWithContext(ctx) | |
2547 | 2655 | if err != nil { |
2548 | 2656 | t.Error(err) |
2549 | 2657 | } |
2550 | 2658 | expected := buf.String() |
2551 | contents, err := c.ObjectGetString(CONTAINER, objName) | |
2659 | contents, err := c.ObjectGetString(ctx, CONTAINER, objName) | |
2552 | 2660 | if err != nil { |
2553 | 2661 | t.Error(err) |
2554 | 2662 | } |
2569 | 2677 | } |
2570 | 2678 | |
2571 | 2679 | func TestDLOSegmentation(t *testing.T) { |
2680 | ctx := context.Background() | |
2572 | 2681 | c, rollback := makeConnectionWithSegmentsContainer(t) |
2573 | 2682 | defer rollback() |
2574 | 2683 | |
2581 | 2690 | } |
2582 | 2691 | |
2583 | 2692 | testSegmentation(t, c, func() swift.LargeObjectFile { |
2584 | out, err := c.DynamicLargeObjectCreate(&opts) | |
2693 | out, err := c.DynamicLargeObjectCreate(ctx, &opts) | |
2585 | 2694 | if err != nil { |
2586 | 2695 | t.Fatal(err) |
2587 | 2696 | } |
2624 | 2733 | } |
2625 | 2734 | |
2626 | 2735 | func TestDLOSegmentationBuffered(t *testing.T) { |
2736 | ctx := context.Background() | |
2627 | 2737 | c, rollback := makeConnectionWithSegmentsContainer(t) |
2628 | 2738 | defer rollback() |
2629 | 2739 | |
2635 | 2745 | } |
2636 | 2746 | |
2637 | 2747 | testSegmentation(t, c, func() swift.LargeObjectFile { |
2638 | out, err := c.DynamicLargeObjectCreate(&opts) | |
2748 | out, err := c.DynamicLargeObjectCreate(ctx, &opts) | |
2639 | 2749 | if err != nil { |
2640 | 2750 | t.Fatal(err) |
2641 | 2751 | } |
2678 | 2788 | } |
2679 | 2789 | |
2680 | 2790 | func TestSLOCreate(t *testing.T) { |
2791 | ctx := context.Background() | |
2681 | 2792 | c, rollback := makeConnectionWithSegmentsContainer(t) |
2682 | 2793 | defer rollback() |
2683 | 2794 | |
2686 | 2797 | ObjectName: OBJECT, |
2687 | 2798 | ContentType: "image/jpeg", |
2688 | 2799 | } |
2689 | out, err := c.StaticLargeObjectCreate(&opts) | |
2800 | out, err := c.StaticLargeObjectCreate(ctx, &opts) | |
2690 | 2801 | if err != nil { |
2691 | 2802 | if err == swift.SLONotSupported { |
2692 | 2803 | t.Skip("SLO not supported") |
2695 | 2806 | t.Fatal(err) |
2696 | 2807 | } |
2697 | 2808 | defer func() { |
2698 | err = c.StaticLargeObjectDelete(CONTAINER, OBJECT) | |
2809 | err = c.StaticLargeObjectDelete(ctx, CONTAINER, OBJECT) | |
2699 | 2810 | if err != nil { |
2700 | 2811 | t.Fatal(err) |
2701 | 2812 | } |
2709 | 2820 | t.Fatal(err) |
2710 | 2821 | } |
2711 | 2822 | } |
2712 | err = out.Close() | |
2823 | err = out.CloseWithContext(ctx) | |
2713 | 2824 | if err != nil { |
2714 | 2825 | t.Error(err) |
2715 | 2826 | } |
2716 | 2827 | expected := buf.String() |
2717 | contents, err := c.ObjectGetString(CONTAINER, OBJECT) | |
2828 | contents, err := c.ObjectGetString(ctx, CONTAINER, OBJECT) | |
2718 | 2829 | if err != nil { |
2719 | 2830 | t.Error(err) |
2720 | 2831 | } |
2721 | 2832 | if contents != expected { |
2722 | 2833 | t.Errorf("Contents wrong, expected %q, got: %q", expected, contents) |
2723 | 2834 | } |
2724 | info, _, err := c.Object(CONTAINER, OBJECT) | |
2835 | info, _, err := c.Object(ctx, CONTAINER, OBJECT) | |
2725 | 2836 | if err != nil { |
2726 | 2837 | t.Fatal(err) |
2727 | 2838 | } |
2734 | 2845 | } |
2735 | 2846 | |
2736 | 2847 | func TestSLOInsert(t *testing.T) { |
2848 | ctx := context.Background() | |
2737 | 2849 | c, rollback := makeConnectionWithSLO(t) |
2738 | 2850 | defer rollback() |
2739 | 2851 | opts := swift.LargeObjectOpts{ |
2741 | 2853 | ObjectName: OBJECT, |
2742 | 2854 | ContentType: "image/jpeg", |
2743 | 2855 | } |
2744 | out, err := c.StaticLargeObjectCreateFile(&opts) | |
2856 | out, err := c.StaticLargeObjectCreateFile(ctx, &opts) | |
2745 | 2857 | if err != nil { |
2746 | 2858 | t.Fatal(err) |
2747 | 2859 | } |
2752 | 2864 | if err != nil { |
2753 | 2865 | t.Fatal(err) |
2754 | 2866 | } |
2755 | fmt.Fprintf(buf, "\n%d %s\n", 1, CONTENTS) | |
2756 | err = out.Close() | |
2867 | _, _ = fmt.Fprintf(buf, "\n%d %s\n", 1, CONTENTS) | |
2868 | err = out.CloseWithContext(ctx) | |
2757 | 2869 | if err != nil { |
2758 | 2870 | t.Error(err) |
2759 | 2871 | } |
2760 | 2872 | expected := buf.String() |
2761 | contents, err := c.ObjectGetString(CONTAINER, OBJECT) | |
2873 | contents, err := c.ObjectGetString(ctx, CONTAINER, OBJECT) | |
2762 | 2874 | if err != nil { |
2763 | 2875 | t.Error(err) |
2764 | 2876 | } |
2768 | 2880 | } |
2769 | 2881 | |
2770 | 2882 | func TestSLOAppend(t *testing.T) { |
2883 | ctx := context.Background() | |
2771 | 2884 | c, rollback := makeConnectionWithSLO(t) |
2772 | 2885 | defer rollback() |
2773 | 2886 | opts := swift.LargeObjectOpts{ |
2777 | 2890 | CheckHash: true, |
2778 | 2891 | ContentType: "image/jpeg", |
2779 | 2892 | } |
2780 | out, err := c.StaticLargeObjectCreateFile(&opts) | |
2781 | if err != nil { | |
2782 | t.Fatal(err) | |
2783 | } | |
2784 | ||
2785 | contents, err := c.ObjectGetString(CONTAINER, OBJECT) | |
2893 | out, err := c.StaticLargeObjectCreateFile(ctx, &opts) | |
2894 | if err != nil { | |
2895 | t.Fatal(err) | |
2896 | } | |
2897 | ||
2898 | contents, err := c.ObjectGetString(ctx, CONTAINER, OBJECT) | |
2899 | if err != nil { | |
2900 | t.Fatal(err) | |
2901 | } | |
2786 | 2902 | buf := bytes.NewBuffer([]byte(contents)) |
2787 | 2903 | multi := io.MultiWriter(buf, out) |
2788 | 2904 | for i := 0; i < 2; i++ { |
2791 | 2907 | t.Fatal(err) |
2792 | 2908 | } |
2793 | 2909 | } |
2794 | err = out.Close() | |
2910 | err = out.CloseWithContext(ctx) | |
2795 | 2911 | if err != nil { |
2796 | 2912 | t.Error(err) |
2797 | 2913 | } |
2798 | 2914 | expected := buf.String() |
2799 | contents, err = c.ObjectGetString(CONTAINER, OBJECT) | |
2915 | contents, err = c.ObjectGetString(ctx, CONTAINER, OBJECT) | |
2800 | 2916 | if err != nil { |
2801 | 2917 | t.Error(err) |
2802 | 2918 | } |
2806 | 2922 | } |
2807 | 2923 | |
2808 | 2924 | func TestSLOMove(t *testing.T) { |
2925 | ctx := context.Background() | |
2809 | 2926 | c, rollback := makeConnectionWithSLO(t) |
2810 | 2927 | defer rollback() |
2811 | contents, err := c.ObjectGetString(CONTAINER, OBJECT) | |
2812 | if err != nil { | |
2813 | t.Fatal(err) | |
2814 | } | |
2815 | ||
2816 | err = c.StaticLargeObjectMove(CONTAINER, OBJECT, CONTAINER, OBJECT2) | |
2928 | contents, err := c.ObjectGetString(ctx, CONTAINER, OBJECT) | |
2929 | if err != nil { | |
2930 | t.Fatal(err) | |
2931 | } | |
2932 | ||
2933 | err = c.StaticLargeObjectMove(ctx, CONTAINER, OBJECT, CONTAINER, OBJECT2) | |
2817 | 2934 | if err != nil { |
2818 | 2935 | t.Fatal(err) |
2819 | 2936 | } |
2820 | 2937 | defer func() { |
2821 | err = c.StaticLargeObjectDelete(CONTAINER, OBJECT2) | |
2938 | err = c.StaticLargeObjectDelete(ctx, CONTAINER, OBJECT2) | |
2822 | 2939 | if err != nil { |
2823 | 2940 | t.Fatal(err) |
2824 | 2941 | } |
2825 | 2942 | }() |
2826 | 2943 | |
2827 | contents2, err := c.ObjectGetString(CONTAINER, OBJECT2) | |
2944 | contents2, err := c.ObjectGetString(ctx, CONTAINER, OBJECT2) | |
2828 | 2945 | if err != nil { |
2829 | 2946 | t.Fatal(err) |
2830 | 2947 | } |
2835 | 2952 | } |
2836 | 2953 | |
2837 | 2954 | func TestSLONoSegmentContainer(t *testing.T) { |
2955 | ctx := context.Background() | |
2838 | 2956 | c, rollback := makeConnectionWithSLO(t) |
2839 | 2957 | defer rollback() |
2840 | 2958 | |
2844 | 2962 | ContentType: "image/jpeg", |
2845 | 2963 | SegmentContainer: CONTAINER, |
2846 | 2964 | } |
2847 | out, err := c.StaticLargeObjectCreate(&opts) | |
2965 | out, err := c.StaticLargeObjectCreate(ctx, &opts) | |
2848 | 2966 | if err != nil { |
2849 | 2967 | t.Fatal(err) |
2850 | 2968 | } |
2857 | 2975 | t.Fatal(err) |
2858 | 2976 | } |
2859 | 2977 | } |
2860 | err = out.Close() | |
2978 | err = out.CloseWithContext(ctx) | |
2861 | 2979 | if err != nil { |
2862 | 2980 | t.Error(err) |
2863 | 2981 | } |
2864 | 2982 | expected := buf.String() |
2865 | contents, err := c.ObjectGetString(CONTAINER, OBJECT) | |
2983 | contents, err := c.ObjectGetString(ctx, CONTAINER, OBJECT) | |
2866 | 2984 | if err != nil { |
2867 | 2985 | t.Error(err) |
2868 | 2986 | } |
2870 | 2988 | t.Errorf("Contents wrong, expected %q, got: %q", expected, contents) |
2871 | 2989 | } |
2872 | 2990 | |
2873 | err = c.StaticLargeObjectDelete(CONTAINER, OBJECT) | |
2991 | err = c.StaticLargeObjectDelete(ctx, CONTAINER, OBJECT) | |
2874 | 2992 | if err != nil { |
2875 | 2993 | t.Fatal(err) |
2876 | 2994 | } |
2877 | 2995 | } |
2878 | 2996 | |
2879 | 2997 | func TestSLOMinChunkSize(t *testing.T) { |
2998 | ctx := context.Background() | |
2880 | 2999 | c, rollback := makeConnectionWithSegmentsContainer(t) |
2881 | 3000 | defer rollback() |
2882 | 3001 | if srv == nil { |
2885 | 3004 | } |
2886 | 3005 | |
2887 | 3006 | srv.SetOverride("/info", func(w http.ResponseWriter, r *http.Request, recorder *httptest.ResponseRecorder) { |
2888 | w.Write([]byte(`{"slo": {"min_segment_size": 4}}`)) | |
3007 | _, _ = w.Write([]byte(`{"slo": {"min_segment_size": 4}}`)) | |
2889 | 3008 | }) |
2890 | 3009 | defer srv.UnsetOverride("/info") |
2891 | c.QueryInfo() | |
3010 | _, _ = c.QueryInfo(ctx) | |
2892 | 3011 | |
2893 | 3012 | opts := swift.LargeObjectOpts{ |
2894 | 3013 | Container: CONTAINER, |
2900 | 3019 | } |
2901 | 3020 | |
2902 | 3021 | testSLOSegmentation(t, c, func() swift.LargeObjectFile { |
2903 | out, err := c.StaticLargeObjectCreate(&opts) | |
3022 | out, err := c.StaticLargeObjectCreate(ctx, &opts) | |
2904 | 3023 | if err != nil { |
2905 | 3024 | t.Fatal(err) |
2906 | 3025 | } |
2909 | 3028 | } |
2910 | 3029 | |
2911 | 3030 | func TestSLOSegmentation(t *testing.T) { |
3031 | ctx := context.Background() | |
2912 | 3032 | c, rollback := makeConnectionWithSegmentsContainer(t) |
2913 | 3033 | defer rollback() |
2914 | 3034 | opts := swift.LargeObjectOpts{ |
2920 | 3040 | NoBuffer: true, |
2921 | 3041 | } |
2922 | 3042 | testSLOSegmentation(t, c, func() swift.LargeObjectFile { |
2923 | out, err := c.StaticLargeObjectCreate(&opts) | |
3043 | out, err := c.StaticLargeObjectCreate(ctx, &opts) | |
2924 | 3044 | if err != nil { |
2925 | 3045 | if err == swift.SLONotSupported { |
2926 | 3046 | t.Skip("SLO not supported") |
2932 | 3052 | } |
2933 | 3053 | |
2934 | 3054 | func TestSLOSegmentationBuffered(t *testing.T) { |
3055 | ctx := context.Background() | |
2935 | 3056 | c, rollback := makeConnectionWithSegmentsContainer(t) |
2936 | 3057 | defer rollback() |
2937 | 3058 | opts := swift.LargeObjectOpts{ |
2942 | 3063 | MinChunkSize: 4, |
2943 | 3064 | } |
2944 | 3065 | testSegmentation(t, c, func() swift.LargeObjectFile { |
2945 | out, err := c.StaticLargeObjectCreate(&opts) | |
3066 | out, err := c.StaticLargeObjectCreate(ctx, &opts) | |
2946 | 3067 | if err != nil { |
2947 | 3068 | if err == swift.SLONotSupported { |
2948 | 3069 | t.Skip("SLO not supported") |
3034 | 3155 | } |
3035 | 3156 | |
3036 | 3157 | func testSegmentation(t *testing.T, c *swift.Connection, createObj func() swift.LargeObjectFile, testCases []segmentTest) { |
3158 | ctx := context.Background() | |
3037 | 3159 | var err error |
3038 | 3160 | runTestCase := func(tCase segmentTest) { |
3039 | 3161 | out := createObj() |
3040 | 3162 | defer func() { |
3041 | err = c.LargeObjectDelete(CONTAINER, OBJECT) | |
3163 | err = c.LargeObjectDelete(ctx, CONTAINER, OBJECT) | |
3042 | 3164 | if err != nil { |
3043 | 3165 | t.Fatal(err) |
3044 | 3166 | } |
3055 | 3177 | } |
3056 | 3178 | } |
3057 | 3179 | } |
3058 | err = out.Close() | |
3180 | err = out.CloseWithContext(ctx) | |
3059 | 3181 | if err != nil { |
3060 | 3182 | t.Error(err) |
3061 | 3183 | } |
3062 | contents, err := c.ObjectGetString(CONTAINER, OBJECT) | |
3184 | contents, err := c.ObjectGetString(ctx, CONTAINER, OBJECT) | |
3063 | 3185 | if err != nil { |
3064 | 3186 | t.Error(err) |
3065 | 3187 | } |
3066 | 3188 | if contents != tCase.expectedValue { |
3067 | 3189 | t.Errorf("Contents wrong, expected %q, got: %q", tCase.expectedValue, contents) |
3068 | 3190 | } |
3069 | container, objects, err := c.LargeObjectGetSegments(CONTAINER, OBJECT) | |
3191 | container, objects, err := c.LargeObjectGetSegments(ctx, CONTAINER, OBJECT) | |
3070 | 3192 | if err != nil { |
3071 | 3193 | t.Error(err) |
3072 | 3194 | } |
3073 | 3195 | if container != SEGMENTS_CONTAINER { |
3074 | 3196 | t.Errorf("Segments container wrong, expected %q, got: %q", SEGMENTS_CONTAINER, container) |
3075 | 3197 | } |
3076 | _, headers, err := c.Object(CONTAINER, OBJECT) | |
3198 | _, headers, err := c.Object(ctx, CONTAINER, OBJECT) | |
3077 | 3199 | if err != nil { |
3078 | 3200 | t.Fatal(err) |
3079 | 3201 | } |
3080 | 3202 | if headers.IsLargeObjectSLO() { |
3081 | 3203 | var info swift.SwiftInfo |
3082 | info, err = c.QueryInfo() | |
3204 | info, err = c.QueryInfo(ctx) | |
3083 | 3205 | if err != nil { |
3084 | 3206 | t.Fatal(err) |
3085 | 3207 | } |
3091 | 3213 | var segContents []string |
3092 | 3214 | for _, obj := range objects { |
3093 | 3215 | var value string |
3094 | value, err = c.ObjectGetString(SEGMENTS_CONTAINER, obj.Name) | |
3216 | value, err = c.ObjectGetString(ctx, SEGMENTS_CONTAINER, obj.Name) | |
3095 | 3217 | if err != nil { |
3096 | 3218 | t.Error(err) |
3097 | 3219 | } |
3107 | 3229 | } |
3108 | 3230 | |
3109 | 3231 | func TestContainerDelete(t *testing.T) { |
3232 | ctx := context.Background() | |
3110 | 3233 | c, rollback := makeConnectionWithContainer(t) |
3111 | 3234 | defer rollback() |
3112 | err := c.ContainerDelete(CONTAINER) | |
3113 | if err != nil { | |
3114 | t.Fatal(err) | |
3115 | } | |
3116 | err = c.ContainerDelete(CONTAINER) | |
3235 | err := c.ContainerDelete(ctx, CONTAINER) | |
3236 | if err != nil { | |
3237 | t.Fatal(err) | |
3238 | } | |
3239 | err = c.ContainerDelete(ctx, CONTAINER) | |
3117 | 3240 | if err != swift.ContainerNotFound { |
3118 | 3241 | t.Fatal("Expecting container not found", err) |
3119 | 3242 | } |
3120 | _, _, err = c.Container(CONTAINER) | |
3243 | _, _, err = c.Container(ctx, CONTAINER) | |
3121 | 3244 | if err != swift.ContainerNotFound { |
3122 | 3245 | t.Fatal("Expecting container not found", err) |
3123 | 3246 | } |
3124 | 3247 | } |
3125 | 3248 | |
3126 | 3249 | func TestUnAuthenticate(t *testing.T) { |
3250 | ctx := context.Background() | |
3127 | 3251 | c, rollback := makeConnectionAuth(t) |
3128 | 3252 | defer rollback() |
3129 | 3253 | c.UnAuthenticate() |
3131 | 3255 | t.Fatal("Shouldn't be authenticated") |
3132 | 3256 | } |
3133 | 3257 | // Test re-authenticate |
3134 | err := c.Authenticate() | |
3258 | err := c.Authenticate(ctx) | |
3135 | 3259 | if err != nil { |
3136 | 3260 | t.Fatal("ReAuth failed", err) |
3137 | 3261 | } |
33 | 33 | "strings" |
34 | 34 | "sync" |
35 | 35 | "sync/atomic" |
36 | "testing" | |
37 | 36 | "time" |
38 | 37 | ) |
39 | 38 | |
50 | 49 | // See https://golang.org/pkg/sync/atomic/#pkg-note-BUG for more details. |
51 | 50 | reqId int64 |
52 | 51 | sync.RWMutex |
53 | t *testing.T | |
54 | mu sync.Mutex | |
55 | 52 | Listener net.Listener |
56 | 53 | AuthURL string |
57 | 54 | URL string |
144 | 141 | sync.RWMutex |
145 | 142 | metadata |
146 | 143 | name string |
147 | ctime time.Time | |
148 | 144 | objects map[string]*object |
149 | 145 | } |
150 | 146 | |
187 | 183 | container *container // non-nil if the container already exists. |
188 | 184 | } |
189 | 185 | |
190 | var responseParams = map[string]bool{ | |
191 | "content-type": true, | |
192 | "content-language": true, | |
193 | "expires": true, | |
194 | "cache-control": true, | |
195 | "content-disposition": true, | |
196 | "content-encoding": true, | |
197 | } | |
198 | ||
199 | 186 | func fatalf(code int, codeStr string, errf string, a ...interface{}) { |
200 | 187 | panic(&swiftError{ |
201 | 188 | statusCode: code, |
318 | 305 | } else { |
319 | 306 | for _, item := range objects { |
320 | 307 | if obj, ok := item.(*object); ok { |
321 | a.w.Write([]byte(obj.name + "\n")) | |
308 | _, err := a.w.Write([]byte(obj.name + "\n")) | |
309 | if err != nil { | |
310 | fatalf(500, "WriteFailed", "Write failed") | |
311 | } | |
322 | 312 | } else if subdir, ok := item.(Subdir); ok { |
323 | a.w.Write([]byte(subdir.Subdir + "\n")) | |
313 | _, err := a.w.Write([]byte(subdir.Subdir + "\n")) | |
314 | if err != nil { | |
315 | fatalf(500, "WriteFailed", "Write failed") | |
316 | } | |
324 | 317 | } |
325 | 318 | } |
326 | 319 | return nil |
397 | 390 | if err != nil { |
398 | 391 | fatalf(400, "TODO", "Invalid tar.gz") |
399 | 392 | } |
400 | defer gzr.Close() | |
393 | defer func() { | |
394 | err := gzr.Close() | |
395 | if err != nil { | |
396 | fatalf(400, "CloseFailed", "Close failed") | |
397 | } | |
398 | }() | |
401 | 399 | reader = tar.NewReader(gzr) |
402 | 400 | case "tar.bz2": |
403 | 401 | bzr := bzip2.NewReader(dataReader) |
659 | 657 | } else if value, ok := obj.meta["X-Static-Large-Object"]; ok && value[0] == "True" && a.req.URL.Query().Get("multipart-manifest") != "get" { |
660 | 658 | var segments []io.Reader |
661 | 659 | var segmentList []segment |
662 | json.Unmarshal(obj.data, &segmentList) | |
660 | err := json.Unmarshal(obj.data, &segmentList) | |
661 | if err != nil { | |
662 | fatalf(400, "BadParameters", "Unmarshal failed.") | |
663 | } | |
663 | 664 | cursor := 0 |
664 | 665 | size := 0 |
665 | 666 | sum := md5.New() |
768 | 769 | a.req.Header.Set("X-Static-Large-Object", "True") |
769 | 770 | |
770 | 771 | var segments []segment |
771 | json.Unmarshal(data, &segments) | |
772 | err := json.Unmarshal(data, &segments) | |
773 | if err != nil { | |
774 | fatalf(400, "BadParameters", "Unmarshal failed.") | |
775 | } | |
772 | 776 | for i := range segments { |
773 | 777 | segments[i].Name = "/" + segments[i].Path |
774 | 778 | segments[i].Path = "" |
900 | 904 | |
901 | 905 | func (s *SwiftServer) serveHTTP(w http.ResponseWriter, req *http.Request) { |
902 | 906 | // ignore error from ParseForm as it's usually spurious. |
903 | req.ParseForm() | |
907 | err := req.ParseForm() | |
908 | if err != nil { | |
909 | fatalf(400, "BadParameters", "Parse form failed.") | |
910 | } | |
904 | 911 | |
905 | 912 | if fn := s.override[req.URL.Path]; fn != nil { |
906 | 913 | originalRW := w |
953 | 960 | _, _ = rand.Read(r) |
954 | 961 | id := fmt.Sprintf("%X", r) |
955 | 962 | w.Header().Set("X-Storage-Url", s.URL+"/AUTH_"+username) |
956 | w.Header().Set("X-Auth-Token", "AUTH_tk"+string(id)) | |
957 | w.Header().Set("X-Storage-Token", "AUTH_tk"+string(id)) | |
963 | w.Header().Set("X-Auth-Token", "AUTH_tk"+id) | |
964 | w.Header().Set("X-Storage-Token", "AUTH_tk"+id) | |
958 | 965 | s.Sessions[id] = &session{ |
959 | 966 | username: username, |
960 | 967 | } |
1015 | 1022 | if !ok { |
1016 | 1023 | s.RUnlock() |
1017 | 1024 | panic(notAuthorized()) |
1018 | return | |
1019 | 1025 | } |
1020 | 1026 | |
1021 | 1027 | a.user = s.Accounts[session.username] |
1045 | 1051 | } else { |
1046 | 1052 | switch r := resp.(type) { |
1047 | 1053 | case string: |
1048 | w.Write([]byte(r)) | |
1054 | _, err := w.Write([]byte(r)) | |
1055 | if err != nil { | |
1056 | fatalf(500, "WriteFailed", "Write failed.") | |
1057 | } | |
1049 | 1058 | default: |
1050 | w.Write(resp.([]byte)) | |
1059 | _, err := w.Write(resp.([]byte)) | |
1060 | if err != nil { | |
1061 | fatalf(500, "WriteFailed", "Write failed.") | |
1062 | } | |
1051 | 1063 | } |
1052 | 1064 | } |
1053 | 1065 | } |
1194 | 1206 | Name: container.name, |
1195 | 1207 | }) |
1196 | 1208 | } else { |
1197 | a.w.Write([]byte(container.name + "\n")) | |
1209 | _, err := a.w.Write([]byte(container.name + "\n")) | |
1210 | if err != nil { | |
1211 | fatalf(500, "WriteFailed", "Write failed.") | |
1212 | } | |
1198 | 1213 | } |
1199 | 1214 | } |
1200 | 1215 | |
1274 | 1289 | } |
1275 | 1290 | |
1276 | 1291 | resp := fmt.Sprintf("Number Deleted: %d\nNumber Not Found: %d\nErrors: \nResponse Status: 200 OK\n", nb, notFound) |
1277 | a.w.Write([]byte(resp)) | |
1292 | _, err = a.w.Write([]byte(resp)) | |
1293 | if err != nil { | |
1294 | fatalf(500, "WriteFailed", "Write failed.") | |
1295 | } | |
1278 | 1296 | return nil |
1279 | 1297 | } |
1280 | 1298 | |
1309 | 1327 | Containers: make(map[string]*container), |
1310 | 1328 | } |
1311 | 1329 | |
1312 | go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | |
1313 | server.serveHTTP(w, req) | |
1314 | })) | |
1330 | go func() { | |
1331 | _ = http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | |
1332 | server.serveHTTP(w, req) | |
1333 | })) | |
1334 | }() | |
1315 | 1335 | |
1316 | 1336 | return server, nil |
1317 | 1337 | } |
1318 | 1338 | |
1319 | 1339 | func (srv *SwiftServer) Close() { |
1320 | srv.Listener.Close() | |
1321 | } | |
1340 | _ = srv.Listener.Close() | |
1341 | } |
46 | 46 | t.cancel() |
47 | 47 | return 0, TimeoutError |
48 | 48 | } |
49 | panic("unreachable") // for Go 1.0 | |
50 | 49 | } |
51 | 50 | |
52 | 51 | // Close the channel |
66 | 66 | if test.closed { |
67 | 67 | t.Fatal("Shouldn't be closed") |
68 | 68 | } |
69 | tr.Close() | |
69 | _ = tr.Close() | |
70 | 70 | if !test.closed { |
71 | 71 | t.Fatal("Should be closed") |
72 | 72 | } |
99 | 99 | if test.closed { |
100 | 100 | t.Fatal("Shouldn't be closed") |
101 | 101 | } |
102 | tr.Close() | |
102 | _ = tr.Close() | |
103 | 103 | if !test.closed { |
104 | 104 | t.Fatal("Should be closed") |
105 | 105 | } |