Merge branch '13973-child-priority' refs #13973
[arvados.git] / services / keep-web / cadaver_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package main
6
7 import (
8         "bytes"
9         "fmt"
10         "io"
11         "io/ioutil"
12         "net/url"
13         "os"
14         "os/exec"
15         "path/filepath"
16         "strings"
17         "time"
18
19         "git.curoverse.com/arvados.git/sdk/go/arvados"
20         "git.curoverse.com/arvados.git/sdk/go/arvadostest"
21         check "gopkg.in/check.v1"
22 )
23
24 func (s *IntegrationSuite) TestCadaverHTTPAuth(c *check.C) {
25         s.testCadaver(c, arvadostest.ActiveToken, func(newCollection arvados.Collection) (string, string, string) {
26                 r := "/c=" + arvadostest.FooAndBarFilesInDirUUID + "/"
27                 w := "/c=" + newCollection.UUID + "/"
28                 pdh := "/c=" + strings.Replace(arvadostest.FooAndBarFilesInDirPDH, "+", "-", -1) + "/"
29                 return r, w, pdh
30         }, nil)
31 }
32
33 func (s *IntegrationSuite) TestCadaverPathAuth(c *check.C) {
34         s.testCadaver(c, "", func(newCollection arvados.Collection) (string, string, string) {
35                 r := "/c=" + arvadostest.FooAndBarFilesInDirUUID + "/t=" + arvadostest.ActiveToken + "/"
36                 w := "/c=" + newCollection.UUID + "/t=" + arvadostest.ActiveToken + "/"
37                 pdh := "/c=" + strings.Replace(arvadostest.FooAndBarFilesInDirPDH, "+", "-", -1) + "/t=" + arvadostest.ActiveToken + "/"
38                 return r, w, pdh
39         }, nil)
40 }
41
42 func (s *IntegrationSuite) TestCadaverUserProject(c *check.C) {
43         rpath := "/users/active/foo_file_in_dir/"
44         s.testCadaver(c, arvadostest.ActiveToken, func(newCollection arvados.Collection) (string, string, string) {
45                 wpath := "/users/active/" + newCollection.Name
46                 pdh := "/c=" + strings.Replace(arvadostest.FooAndBarFilesInDirPDH, "+", "-", -1) + "/"
47                 return rpath, wpath, pdh
48         }, func(path string) bool {
49                 // Skip tests that rely on writes, because /users/
50                 // tree is read-only.
51                 return !strings.HasPrefix(path, rpath) || strings.HasPrefix(path, rpath+"_/")
52         })
53 }
54
55 func (s *IntegrationSuite) testCadaver(c *check.C, password string, pathFunc func(arvados.Collection) (string, string, string), skip func(string) bool) {
56         s.testServer.Config.AnonymousTokens = []string{arvadostest.AnonymousToken}
57
58         testdata := []byte("the human tragedy consists in the necessity of living with the consequences of actions performed under the pressure of compulsions we do not understand")
59
60         tempdir, err := ioutil.TempDir("", "keep-web-test-")
61         c.Assert(err, check.IsNil)
62         defer os.RemoveAll(tempdir)
63
64         localfile, err := ioutil.TempFile(tempdir, "localfile")
65         c.Assert(err, check.IsNil)
66         localfile.Write(testdata)
67
68         emptyfile, err := ioutil.TempFile(tempdir, "emptyfile")
69         c.Assert(err, check.IsNil)
70
71         checkfile, err := ioutil.TempFile(tempdir, "checkfile")
72         c.Assert(err, check.IsNil)
73
74         var newCollection arvados.Collection
75         arv := arvados.NewClientFromEnv()
76         arv.AuthToken = arvadostest.ActiveToken
77         err = arv.RequestAndDecode(&newCollection, "POST", "arvados/v1/collections", bytes.NewBufferString(url.Values{"collection": {"{}"}}.Encode()), nil)
78         c.Assert(err, check.IsNil)
79
80         readPath, writePath, pdhPath := pathFunc(newCollection)
81
82         matchToday := time.Now().Format("Jan +2")
83
84         type testcase struct {
85                 path  string
86                 cmd   string
87                 match string
88                 data  []byte
89         }
90         for _, trial := range []testcase{
91                 {
92                         path:  readPath,
93                         cmd:   "ls\n",
94                         match: `(?ms).*dir1 *0 .*`,
95                 },
96                 {
97                         path:  readPath,
98                         cmd:   "ls dir1\n",
99                         match: `(?ms).*bar *3.*foo *3 .*`,
100                 },
101                 {
102                         path:  readPath + "_/dir1",
103                         cmd:   "ls\n",
104                         match: `(?ms).*bar *3.*foo *3 .*`,
105                 },
106                 {
107                         path:  readPath + "dir1/",
108                         cmd:   "ls\n",
109                         match: `(?ms).*bar *3.*foo +3 +Feb +\d+ +2014.*`,
110                 },
111                 {
112                         path:  writePath,
113                         cmd:   "get emptyfile '" + checkfile.Name() + "'\n",
114                         match: `(?ms).*Not Found.*`,
115                 },
116                 {
117                         path:  writePath,
118                         cmd:   "put '" + emptyfile.Name() + "' emptyfile\n",
119                         match: `(?ms).*Uploading .* succeeded.*`,
120                 },
121                 {
122                         path:  writePath,
123                         cmd:   "get emptyfile '" + checkfile.Name() + "'\n",
124                         match: `(?ms).*Downloading .* succeeded.*`,
125                         data:  []byte{},
126                 },
127                 {
128                         path:  writePath,
129                         cmd:   "put '" + localfile.Name() + "' testfile\n",
130                         match: `(?ms).*Uploading .* succeeded.*`,
131                 },
132                 {
133                         path:  writePath,
134                         cmd:   "get testfile '" + checkfile.Name() + "'\n",
135                         match: `(?ms).*succeeded.*`,
136                         data:  testdata,
137                 },
138                 {
139                         path:  writePath,
140                         cmd:   "move testfile newdir0/\n",
141                         match: `(?ms).*Moving .* succeeded.*`,
142                 },
143                 {
144                         path:  writePath,
145                         cmd:   "move testfile newdir0/\n",
146                         match: `(?ms).*Moving .* failed.*`,
147                 },
148                 {
149                         path:  writePath,
150                         cmd:   "ls\n",
151                         match: `(?ms).*newdir0.* 0 +` + matchToday + ` \d+:\d+\n.*`,
152                 },
153                 {
154                         path:  writePath,
155                         cmd:   "move newdir0/testfile emptyfile/bogus/\n",
156                         match: `(?ms).*Moving .* failed.*`,
157                 },
158                 {
159                         path:  writePath,
160                         cmd:   "mkcol newdir1\n",
161                         match: `(?ms).*Creating .* succeeded.*`,
162                 },
163                 {
164                         path:  writePath,
165                         cmd:   "move newdir0/testfile newdir1/\n",
166                         match: `(?ms).*Moving .* succeeded.*`,
167                 },
168                 {
169                         path:  writePath,
170                         cmd:   "move newdir1 newdir1/\n",
171                         match: `(?ms).*Moving .* failed.*`,
172                 },
173                 {
174                         path:  writePath,
175                         cmd:   "get newdir1/testfile '" + checkfile.Name() + "'\n",
176                         match: `(?ms).*succeeded.*`,
177                         data:  testdata,
178                 },
179                 {
180                         path:  writePath,
181                         cmd:   "put '" + localfile.Name() + "' newdir1/testfile1\n",
182                         match: `(?ms).*Uploading .* succeeded.*`,
183                 },
184                 {
185                         path:  writePath,
186                         cmd:   "mkcol newdir2\n",
187                         match: `(?ms).*Creating .* succeeded.*`,
188                 },
189                 {
190                         path:  writePath,
191                         cmd:   "put '" + localfile.Name() + "' newdir2/testfile2\n",
192                         match: `(?ms).*Uploading .* succeeded.*`,
193                 },
194                 {
195                         path:  writePath,
196                         cmd:   "copy newdir2/testfile2 testfile3\n",
197                         match: `(?ms).*succeeded.*`,
198                 },
199                 {
200                         path:  writePath,
201                         cmd:   "get testfile3 '" + checkfile.Name() + "'\n",
202                         match: `(?ms).*succeeded.*`,
203                         data:  testdata,
204                 },
205                 {
206                         path:  writePath,
207                         cmd:   "get newdir2/testfile2 '" + checkfile.Name() + "'\n",
208                         match: `(?ms).*succeeded.*`,
209                         data:  testdata,
210                 },
211                 {
212                         path:  writePath,
213                         cmd:   "rmcol newdir2\n",
214                         match: `(?ms).*Deleting collection .* succeeded.*`,
215                 },
216                 {
217                         path:  writePath,
218                         cmd:   "get newdir2/testfile2 '" + checkfile.Name() + "'\n",
219                         match: `(?ms).*Downloading .* failed.*`,
220                 },
221                 {
222                         path:  "/c=" + arvadostest.UserAgreementCollection + "/t=" + arv.AuthToken + "/",
223                         cmd:   "put '" + localfile.Name() + "' foo\n",
224                         match: `(?ms).*Uploading .* failed:.*403 Forbidden.*`,
225                 },
226                 {
227                         path:  pdhPath,
228                         cmd:   "put '" + localfile.Name() + "' foo\n",
229                         match: `(?ms).*Uploading .* failed:.*405 Method Not Allowed.*`,
230                 },
231                 {
232                         path:  pdhPath,
233                         cmd:   "move foo bar\n",
234                         match: `(?ms).*Moving .* failed:.*405 Method Not Allowed.*`,
235                 },
236                 {
237                         path:  pdhPath,
238                         cmd:   "copy foo bar\n",
239                         match: `(?ms).*Copying .* failed:.*405 Method Not Allowed.*`,
240                 },
241                 {
242                         path:  pdhPath,
243                         cmd:   "delete foo\n",
244                         match: `(?ms).*Deleting .* failed:.*405 Method Not Allowed.*`,
245                 },
246         } {
247                 c.Logf("%s %+v", "http://"+s.testServer.Addr, trial)
248                 if skip != nil && skip(trial.path) {
249                         c.Log("(skip)")
250                         continue
251                 }
252
253                 os.Remove(checkfile.Name())
254
255                 stdout := s.runCadaver(c, password, trial.path, trial.cmd)
256                 c.Check(stdout, check.Matches, trial.match)
257
258                 if trial.data == nil {
259                         continue
260                 }
261                 checkfile, err = os.Open(checkfile.Name())
262                 c.Assert(err, check.IsNil)
263                 checkfile.Seek(0, os.SEEK_SET)
264                 got, err := ioutil.ReadAll(checkfile)
265                 c.Check(got, check.DeepEquals, trial.data)
266                 c.Check(err, check.IsNil)
267         }
268 }
269
270 func (s *IntegrationSuite) TestCadaverByID(c *check.C) {
271         for _, path := range []string{"/by_id", "/by_id/"} {
272                 stdout := s.runCadaver(c, arvadostest.ActiveToken, path, "ls")
273                 c.Check(stdout, check.Matches, `(?ms).*collection is empty.*`)
274         }
275         for _, path := range []string{
276                 "/by_id/" + arvadostest.FooPdh,
277                 "/by_id/" + arvadostest.FooPdh + "/",
278                 "/by_id/" + arvadostest.FooCollection,
279                 "/by_id/" + arvadostest.FooCollection + "/",
280         } {
281                 stdout := s.runCadaver(c, arvadostest.ActiveToken, path, "ls")
282                 c.Check(stdout, check.Matches, `(?ms).*\s+foo\s+3 .*`)
283         }
284 }
285
286 func (s *IntegrationSuite) TestCadaverUsersDir(c *check.C) {
287         for _, path := range []string{"/"} {
288                 stdout := s.runCadaver(c, arvadostest.ActiveToken, path, "ls")
289                 c.Check(stdout, check.Matches, `(?ms).*Coll:\s+by_id\s+0 .*`)
290                 c.Check(stdout, check.Matches, `(?ms).*Coll:\s+users\s+0 .*`)
291         }
292         for _, path := range []string{"/users", "/users/"} {
293                 stdout := s.runCadaver(c, arvadostest.ActiveToken, path, "ls")
294                 c.Check(stdout, check.Matches, `(?ms).*Coll:\s+active.*`)
295         }
296         for _, path := range []string{"/users/active", "/users/active/"} {
297                 stdout := s.runCadaver(c, arvadostest.ActiveToken, path, "ls")
298                 c.Check(stdout, check.Matches, `(?ms).*Coll:\s+A Project\s+0 .*`)
299                 c.Check(stdout, check.Matches, `(?ms).*Coll:\s+bar_file\s+0 .*`)
300         }
301         for _, path := range []string{"/users/admin", "/users/doesnotexist", "/users/doesnotexist/"} {
302                 stdout := s.runCadaver(c, arvadostest.ActiveToken, path, "ls")
303                 c.Check(stdout, check.Matches, `(?ms).*404 Not Found.*`)
304         }
305 }
306
307 func (s *IntegrationSuite) runCadaver(c *check.C, password, path, stdin string) string {
308         tempdir, err := ioutil.TempDir("", "keep-web-test-")
309         c.Assert(err, check.IsNil)
310         defer os.RemoveAll(tempdir)
311
312         cmd := exec.Command("cadaver", "http://"+s.testServer.Addr+path)
313         if password != "" {
314                 // cadaver won't try username/password authentication
315                 // unless the server responds 401 to an
316                 // unauthenticated request, which it only does in
317                 // AttachmentOnlyHost, TrustAllContent, and
318                 // per-collection vhost cases.
319                 s.testServer.Config.AttachmentOnlyHost = s.testServer.Addr
320
321                 cmd.Env = append(os.Environ(), "HOME="+tempdir)
322                 f, err := os.OpenFile(filepath.Join(tempdir, ".netrc"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
323                 c.Assert(err, check.IsNil)
324                 _, err = fmt.Fprintf(f, "default login none password %s\n", password)
325                 c.Assert(err, check.IsNil)
326                 c.Assert(f.Close(), check.IsNil)
327         }
328         cmd.Stdin = bytes.NewBufferString(stdin)
329         stdout, err := cmd.StdoutPipe()
330         c.Assert(err, check.Equals, nil)
331         cmd.Stderr = cmd.Stdout
332         go cmd.Start()
333
334         var buf bytes.Buffer
335         _, err = io.Copy(&buf, stdout)
336         c.Check(err, check.Equals, nil)
337         err = cmd.Wait()
338         c.Check(err, check.Equals, nil)
339         return buf.String()
340 }