1package storage
2
3// Copyright 2017 Microsoft Corporation
4//
5//  Licensed under the Apache License, Version 2.0 (the "License");
6//  you may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at
8//
9//      http://www.apache.org/licenses/LICENSE-2.0
10//
11//  Unless required by applicable law or agreed to in writing, software
12//  distributed under the License is distributed on an "AS IS" BASIS,
13//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14//  See the License for the specific language governing permissions and
15//  limitations under the License.
16
17import (
18	"bytes"
19	"crypto/md5"
20	"encoding/base64"
21	"io"
22
23	chk "gopkg.in/check.v1"
24)
25
26type StorageFileSuite struct{}
27
28var _ = chk.Suite(&StorageFileSuite{})
29
30func (s *StorageFileSuite) TestCreateFile(c *chk.C) {
31	cli := getFileClient(c)
32	cli.deleteAllShares()
33	rec := cli.client.appendRecorder(c)
34	defer rec.Stop()
35
36	// create share
37	share := cli.GetShareReference(shareName(c))
38	c.Assert(share.Create(nil), chk.IsNil)
39	defer share.Delete(nil)
40	root := share.GetRootDirectoryReference()
41
42	// create directory structure
43	dir1 := root.GetDirectoryReference("one")
44	c.Assert(dir1.Create(nil), chk.IsNil)
45	dir2 := dir1.GetDirectoryReference("two")
46	c.Assert(dir2.Create(nil), chk.IsNil)
47
48	// verify file doesn't exist
49	file := dir2.GetFileReference("some.file")
50	exists, err := file.Exists()
51	c.Assert(err, chk.IsNil)
52	c.Assert(exists, chk.Equals, false)
53
54	// create file
55	c.Assert(file.Create(1024, nil), chk.IsNil)
56
57	// delete file and verify
58	c.Assert(file.Delete(nil), chk.IsNil)
59	exists, err = file.Exists()
60	c.Assert(err, chk.IsNil)
61	c.Assert(exists, chk.Equals, false)
62}
63
64func (s *StorageFileSuite) TestGetFile(c *chk.C) {
65	cli := getFileClient(c)
66	rec := cli.client.appendRecorder(c)
67	defer rec.Stop()
68
69	// create share
70	share := cli.GetShareReference(shareName(c))
71	c.Assert(share.Create(nil), chk.IsNil)
72	defer share.Delete(nil)
73	root := share.GetRootDirectoryReference()
74
75	// create file
76	const size = uint64(1024)
77	byteStream, _ := newByteStream(size)
78	file := root.GetFileReference("some.file")
79	c.Assert(file.Create(size, nil), chk.IsNil)
80
81	// fill file with some data
82	c.Assert(file.WriteRange(byteStream, FileRange{End: size - 1}, nil), chk.IsNil)
83
84	// set some metadata
85	md := map[string]string{
86		"something": "somethingvalue",
87		"another":   "anothervalue",
88	}
89	file.Metadata = md
90	c.Assert(file.SetMetadata(nil), chk.IsNil)
91
92	options := GetFileOptions{
93		GetContentMD5: false,
94	}
95	// retrieve full file content and verify
96	stream, err := file.DownloadRangeToStream(FileRange{Start: 0, End: size - 1}, &options)
97	c.Assert(err, chk.IsNil)
98	defer stream.Body.Close()
99	var b1 [size]byte
100	count, _ := stream.Body.Read(b1[:])
101	c.Assert(count, chk.Equals, int(size))
102	var c1 [size]byte
103	bs, _ := newByteStream(size)
104	bs.Read(c1[:])
105	c.Assert(b1, chk.DeepEquals, c1)
106
107	// retrieve partial file content and verify
108	stream, err = file.DownloadRangeToStream(FileRange{Start: size / 2, End: size - 1}, &options)
109	c.Assert(err, chk.IsNil)
110	defer stream.Body.Close()
111	var b2 [size / 2]byte
112	count, _ = stream.Body.Read(b2[:])
113	c.Assert(count, chk.Equals, int(size)/2)
114	var c2 [size / 2]byte
115	bs, _ = newByteStream(size / 2)
116	bs.Read(c2[:])
117	c.Assert(b2, chk.DeepEquals, c2)
118}
119
120func (s *StorageFileSuite) TestFileRanges(c *chk.C) {
121	cli := getFileClient(c)
122	rec := cli.client.appendRecorder(c)
123	defer rec.Stop()
124
125	share := cli.GetShareReference(shareName(c))
126	c.Assert(share.Create(nil), chk.IsNil)
127	defer share.Delete(nil)
128	root := share.GetRootDirectoryReference()
129
130	fileSize := uint64(4096)
131	contentBytes := content(int(fileSize))
132
133	// --- File with no valid ranges
134	file1 := root.GetFileReference("file1.txt")
135	c.Assert(file1.Create(fileSize, nil), chk.IsNil)
136
137	ranges, err := file1.ListRanges(nil)
138	c.Assert(err, chk.IsNil)
139	c.Assert(ranges.ContentLength, chk.Equals, fileSize)
140	c.Assert(ranges.FileRanges, chk.IsNil)
141
142	// --- File after writing a range
143	file2 := root.GetFileReference("file2.txt")
144	c.Assert(file2.Create(fileSize, nil), chk.IsNil)
145	c.Assert(file2.WriteRange(bytes.NewReader(contentBytes), FileRange{End: fileSize - 1}, nil), chk.IsNil)
146
147	ranges, err = file2.ListRanges(nil)
148	c.Assert(err, chk.IsNil)
149	c.Assert(len(ranges.FileRanges), chk.Equals, 1)
150	c.Assert((ranges.FileRanges[0].End-ranges.FileRanges[0].Start)+1, chk.Equals, fileSize)
151
152	// --- File after writing and clearing
153	file3 := root.GetFileReference("file3.txt")
154	c.Assert(file3.Create(fileSize, nil), chk.IsNil)
155	c.Assert(file3.WriteRange(bytes.NewReader(contentBytes), FileRange{End: fileSize - 1}, nil), chk.IsNil)
156	c.Assert(file3.ClearRange(FileRange{End: fileSize - 1}, nil), chk.IsNil)
157
158	ranges, err = file3.ListRanges(nil)
159	c.Assert(err, chk.IsNil)
160	c.Assert(ranges.FileRanges, chk.IsNil)
161
162	// --- File with ranges and subranges
163	file4 := root.GetFileReference("file4.txt")
164	c.Assert(file4.Create(fileSize, nil), chk.IsNil)
165	putRanges := []FileRange{
166		{End: 511},
167		{Start: 1024, End: 1535},
168		{Start: 2048, End: 2559},
169		{Start: 3072, End: 3583},
170	}
171
172	for _, r := range putRanges {
173		err = file4.WriteRange(bytes.NewReader(contentBytes[:512]), r, nil)
174		c.Assert(err, chk.IsNil)
175	}
176
177	// validate all ranges
178	ranges, err = file4.ListRanges(nil)
179	c.Assert(err, chk.IsNil)
180	c.Assert(ranges.FileRanges, chk.DeepEquals, putRanges)
181
182	options := ListRangesOptions{
183		ListRange: &FileRange{
184			Start: 1000,
185			End:   3000,
186		},
187	}
188	// validate sub-ranges
189	ranges, err = file4.ListRanges(&options)
190	c.Assert(err, chk.IsNil)
191	c.Assert(ranges.FileRanges, chk.DeepEquals, putRanges[1:3])
192
193	// --- clear partial range and validate
194	file5 := root.GetFileReference("file5.txt")
195	c.Assert(file5.Create(fileSize, nil), chk.IsNil)
196	c.Assert(file5.WriteRange(bytes.NewReader(contentBytes), FileRange{End: fileSize - 1}, nil), chk.IsNil)
197	c.Assert(file5.ClearRange(putRanges[0], nil), chk.IsNil)
198	c.Assert(file5.ClearRange(putRanges[2], nil), chk.IsNil)
199
200	ranges, err = file5.ListRanges(nil)
201	c.Assert(err, chk.IsNil)
202	expectedtRanges := []FileRange{
203		{Start: 512, End: 2047},
204		{Start: 2560, End: 4095},
205	}
206	c.Assert(ranges.FileRanges, chk.HasLen, 2)
207	c.Assert(ranges.FileRanges[0], chk.DeepEquals, expectedtRanges[0])
208	c.Assert(ranges.FileRanges[1], chk.DeepEquals, expectedtRanges[1])
209}
210
211func (s *StorageFileSuite) TestFileProperties(c *chk.C) {
212	cli := getFileClient(c)
213	rec := cli.client.appendRecorder(c)
214	defer rec.Stop()
215
216	// create share
217	share := cli.GetShareReference(shareName(c))
218	c.Assert(share.Create(nil), chk.IsNil)
219	defer share.Delete(nil)
220	root := share.GetRootDirectoryReference()
221
222	fileSize := uint64(512)
223	file := root.GetFileReference("test.dat")
224	c.Assert(file.Create(fileSize, nil), chk.IsNil)
225
226	// get initial set of properties
227	c.Assert(file.Properties.Length, chk.Equals, fileSize)
228	c.Assert(file.Properties.Etag, chk.NotNil)
229
230	// set some file properties
231	cc := "cachecontrol"
232	ct := "mytype"
233	enc := "noencoding"
234	lang := "neutral"
235	disp := "friendly"
236	file.Properties.CacheControl = cc
237	file.Properties.Type = ct
238	file.Properties.Disposition = disp
239	file.Properties.Encoding = enc
240	file.Properties.Language = lang
241	c.Assert(file.SetProperties(nil), chk.IsNil)
242
243	// retrieve and verify
244	c.Assert(file.FetchAttributes(nil), chk.IsNil)
245	c.Assert(file.Properties.CacheControl, chk.Equals, cc)
246	c.Assert(file.Properties.Type, chk.Equals, ct)
247	c.Assert(file.Properties.Disposition, chk.Equals, disp)
248	c.Assert(file.Properties.Encoding, chk.Equals, enc)
249	c.Assert(file.Properties.Language, chk.Equals, lang)
250}
251
252func (s *StorageFileSuite) TestFileMetadata(c *chk.C) {
253	cli := getFileClient(c)
254	rec := cli.client.appendRecorder(c)
255	defer rec.Stop()
256
257	// create share
258	share := cli.GetShareReference(shareName(c))
259	c.Assert(share.Create(nil), chk.IsNil)
260	defer share.Delete(nil)
261	root := share.GetRootDirectoryReference()
262
263	fileSize := uint64(512)
264	file := root.GetFileReference("test.dat")
265	c.Assert(file.Create(fileSize, nil), chk.IsNil)
266
267	// get metadata, shouldn't be any
268	c.Assert(file.Metadata, chk.HasLen, 0)
269
270	// set some custom metadata
271	md := map[string]string{
272		"something": "somethingvalue",
273		"another":   "anothervalue",
274	}
275	file.Metadata = md
276	c.Assert(file.SetMetadata(nil), chk.IsNil)
277
278	// retrieve and verify
279	c.Assert(file.FetchAttributes(nil), chk.IsNil)
280	c.Assert(file.Metadata, chk.DeepEquals, md)
281}
282
283func (s *StorageFileSuite) TestFileMD5(c *chk.C) {
284	cli := getFileClient(c)
285	rec := cli.client.appendRecorder(c)
286	defer rec.Stop()
287
288	// create share
289	share := cli.GetShareReference(shareName(c))
290	c.Assert(share.Create(nil), chk.IsNil)
291	defer share.Delete(nil)
292	root := share.GetRootDirectoryReference()
293
294	// create file
295	const size = uint64(1024)
296	fileSize := uint64(size)
297	file := root.GetFileReference("test.dat")
298	c.Assert(file.Create(fileSize, nil), chk.IsNil)
299
300	// fill file with some data and MD5 hash
301	byteStream, contentMD5 := newByteStream(size)
302	options := WriteRangeOptions{
303		ContentMD5: contentMD5,
304	}
305	c.Assert(file.WriteRange(byteStream, FileRange{End: size - 1}, &options), chk.IsNil)
306
307	// download file and verify
308	downloadOptions := GetFileOptions{
309		GetContentMD5: true,
310	}
311	stream, err := file.DownloadRangeToStream(FileRange{Start: 0, End: size - 1}, &downloadOptions)
312	c.Assert(err, chk.IsNil)
313	defer stream.Body.Close()
314	c.Assert(stream.ContentMD5, chk.Equals, contentMD5)
315}
316
317// returns a byte stream along with a base-64 encoded MD5 hash of its contents
318func newByteStream(count uint64) (io.Reader, string) {
319	b := make([]uint8, count)
320	for i := uint64(0); i < count; i++ {
321		b[i] = 0xff
322	}
323
324	// create an MD5 hash of the array
325	hash := md5.Sum(b)
326
327	return bytes.NewReader(b), base64.StdEncoding.EncodeToString(hash[:])
328}
329
330func (s *StorageFileSuite) TestCopyFileSameAccountNoMetaData(c *chk.C) {
331	cli := getFileClient(c)
332	rec := cli.client.appendRecorder(c)
333	defer rec.Stop()
334
335	// create share
336	share := cli.GetShareReference(shareName(c))
337	c.Assert(share.Create(nil), chk.IsNil)
338	defer share.Delete(nil)
339	root := share.GetRootDirectoryReference()
340
341	// create directory structure
342	dir1 := root.GetDirectoryReference("one")
343	c.Assert(dir1.Create(nil), chk.IsNil)
344	dir2 := dir1.GetDirectoryReference("two")
345	c.Assert(dir2.Create(nil), chk.IsNil)
346
347	// create file
348	file := dir2.GetFileReference("some.file")
349	c.Assert(file.Create(1024, nil), chk.IsNil)
350	exists, err := file.Exists()
351	c.Assert(err, chk.IsNil)
352	c.Assert(exists, chk.Equals, true)
353
354	otherFile := dir2.GetFileReference("someother.file")
355
356	// copy the file, no timeout parameter
357	err = otherFile.CopyFile(file.URL(), nil)
358	c.Assert(err, chk.IsNil)
359
360	// delete files
361	c.Assert(file.Delete(nil), chk.IsNil)
362	c.Assert(otherFile.Delete(nil), chk.IsNil)
363}
364
365func (s *StorageFileSuite) TestCopyFileSameAccountTimeout(c *chk.C) {
366	cli := getFileClient(c)
367	rec := cli.client.appendRecorder(c)
368	defer rec.Stop()
369
370	// create share
371	share := cli.GetShareReference(shareName(c))
372	c.Assert(share.Create(nil), chk.IsNil)
373	defer share.Delete(nil)
374	root := share.GetRootDirectoryReference()
375
376	// create directory structure
377	dir1 := root.GetDirectoryReference("one")
378	c.Assert(dir1.Create(nil), chk.IsNil)
379	dir2 := dir1.GetDirectoryReference("two")
380	c.Assert(dir2.Create(nil), chk.IsNil)
381
382	// create file
383	file := dir2.GetFileReference("some.file")
384	c.Assert(file.Create(1024, nil), chk.IsNil)
385
386	// copy the file, 60 second timeout.
387	otherFile := dir2.GetFileReference("someother.file")
388	options := FileRequestOptions{}
389	options.Timeout = 60
390	c.Assert(otherFile.CopyFile(file.URL(), &options), chk.IsNil)
391
392	// delete files
393	c.Assert(file.Delete(nil), chk.IsNil)
394	c.Assert(otherFile.Delete(nil), chk.IsNil)
395}
396
397func (s *StorageFileSuite) TestCopyFileMissingFile(c *chk.C) {
398	cli := getFileClient(c)
399	rec := cli.client.appendRecorder(c)
400	defer rec.Stop()
401
402	// create share
403	share := cli.GetShareReference(shareName(c))
404	c.Assert(share.Create(nil), chk.IsNil)
405	defer share.Delete(nil)
406	root := share.GetRootDirectoryReference()
407
408	// create directory structure
409	dir1 := root.GetDirectoryReference("one")
410	c.Assert(dir1.Create(nil), chk.IsNil)
411
412	otherFile := dir1.GetFileReference("someother.file")
413
414	// copy the file, no timeout parameter
415	err := otherFile.CopyFile("", nil)
416	c.Assert(err, chk.NotNil)
417}
418