7
. "launchpad.net/gocheck"
8
"launchpad.net/gomaasapi"
9
"launchpad.net/juju-core/environs"
16
type StorageSuite struct {
20
var _ = Suite(new(StorageSuite))
22
// makeStorage creates a MAAS storage object for the running test.
23
func (s *StorageSuite) makeStorage(name string) *maasStorage {
24
maasobj := s.testMAASObject.MAASObject
25
env := maasEnviron{name: name, maasClientUnlocked: &maasobj}
26
return NewStorage(&env).(*maasStorage)
29
// makeRandomBytes returns an array of arbitrary byte values.
30
func makeRandomBytes(length int) []byte {
31
data := make([]byte, length)
32
for index := range data {
33
data[index] = byte(rand.Intn(256))
38
// fakeStoredFile creates a file directly in the (simulated) MAAS file store.
39
// It will contain an arbitrary amount of random data. The contents are also
42
// If you want properly random data here, initialize the randomizer first.
43
// Or don't, if you want consistent (and debuggable) results.
44
func (s *StorageSuite) fakeStoredFile(storage environs.Storage, name string) gomaasapi.MAASObject {
45
data := makeRandomBytes(rand.Intn(10))
46
return s.testMAASObject.TestServer.NewFile(name, data)
49
func (s *StorageSuite) TestGetSnapshotCreatesClone(c *C) {
50
original := s.makeStorage("storage-name")
51
snapshot := original.getSnapshot()
52
c.Check(snapshot.environUnlocked, Equals, original.environUnlocked)
53
c.Check(snapshot.maasClientUnlocked.URL().String(), Equals, original.maasClientUnlocked.URL().String())
54
// Snapshotting locks the original internally, but does not leave
55
// either the original or the snapshot locked.
56
unlockedMutexValue := sync.Mutex{}
57
c.Check(original.Mutex, Equals, unlockedMutexValue)
58
c.Check(snapshot.Mutex, Equals, unlockedMutexValue)
61
func (s *StorageSuite) TestGetRetrievesFile(c *C) {
62
const filename = "stored-data"
63
storage := s.makeStorage("get-retrieves-file")
64
file := s.fakeStoredFile(storage, filename)
65
base64Content, err := file.GetField("content")
67
content, err := base64.StdEncoding.DecodeString(base64Content)
70
reader, err := storage.Get(filename)
74
buf, err := ioutil.ReadAll(reader)
76
c.Check(len(buf), Equals, len(content))
77
c.Check(buf, DeepEquals, content)
80
func (s *StorageSuite) TestRetrieveFileObjectReturnsFileObject(c *C) {
81
const filename = "myfile"
82
stor := s.makeStorage("rfo-test")
83
file := s.fakeStoredFile(stor, filename)
84
fileURI, err := file.GetField("anon_resource_uri")
86
fileContent, err := file.GetField("content")
89
obj, err := stor.retrieveFileObject(filename)
92
uri, err := obj.GetField("anon_resource_uri")
94
c.Check(uri, Equals, fileURI)
95
content, err := obj.GetField("content")
96
c.Check(content, Equals, fileContent)
99
func (s *StorageSuite) TestRetrieveFileObjectReturnsNotFoundForMissingFile(c *C) {
100
stor := s.makeStorage("rfo-test")
101
_, err := stor.retrieveFileObject("nonexistent-file")
102
c.Assert(err, NotNil)
103
c.Check(err, FitsTypeOf, &environs.NotFoundError{})
106
func (s *StorageSuite) TestRetrieveFileObjectEscapesName(c *C) {
107
const filename = "#a?b c&d%e!"
108
data := []byte("File contents here")
109
stor := s.makeStorage("rfo-test")
110
err := stor.Put(filename, bytes.NewReader(data), int64(len(data)))
113
obj, err := stor.retrieveFileObject(filename)
116
base64Content, err := obj.GetField("content")
118
content, err := base64.StdEncoding.DecodeString(base64Content)
120
c.Check(content, DeepEquals, data)
123
func (s *StorageSuite) TestFileContentsAreBinary(c *C) {
124
const filename = "myfile.bin"
125
data := []byte{0, 1, 255, 2, 254, 3}
126
stor := s.makeStorage("binary-test")
128
err := stor.Put(filename, bytes.NewReader(data), int64(len(data)))
130
file, err := stor.Get(filename)
132
content, err := ioutil.ReadAll(file)
135
c.Check(content, DeepEquals, data)
138
func (s *StorageSuite) TestGetReturnsNotFoundErrorIfNotFound(c *C) {
139
const filename = "lost-data"
140
storage := NewStorage(s.environ)
141
_, err := storage.Get(filename)
142
c.Assert(err, FitsTypeOf, &environs.NotFoundError{})
145
func (s *StorageSuite) TestListReturnsEmptyIfNoFilesStored(c *C) {
146
storage := NewStorage(s.environ)
147
listing, err := storage.List("")
149
c.Check(listing, DeepEquals, []string{})
152
func (s *StorageSuite) TestListReturnsAllFilesIfPrefixEmpty(c *C) {
153
storage := NewStorage(s.environ)
154
files := []string{"1a", "2b", "3c"}
155
for _, name := range files {
156
s.fakeStoredFile(storage, name)
159
listing, err := storage.List("")
161
c.Check(listing, DeepEquals, files)
164
func (s *StorageSuite) TestListSortsResults(c *C) {
165
storage := NewStorage(s.environ)
166
files := []string{"4d", "1a", "3c", "2b"}
167
for _, name := range files {
168
s.fakeStoredFile(storage, name)
171
listing, err := storage.List("")
173
c.Check(listing, DeepEquals, []string{"1a", "2b", "3c", "4d"})
176
func (s *StorageSuite) TestListReturnsNoFilesIfNoFilesMatchPrefix(c *C) {
177
storage := NewStorage(s.environ)
178
s.fakeStoredFile(storage, "foo")
180
listing, err := storage.List("bar")
182
c.Check(listing, DeepEquals, []string{})
185
func (s *StorageSuite) TestListReturnsOnlyFilesWithMatchingPrefix(c *C) {
186
storage := NewStorage(s.environ)
187
s.fakeStoredFile(storage, "abc")
188
s.fakeStoredFile(storage, "xyz")
190
listing, err := storage.List("x")
192
c.Check(listing, DeepEquals, []string{"xyz"})
195
func (s *StorageSuite) TestListMatchesPrefixOnly(c *C) {
196
storage := NewStorage(s.environ)
197
s.fakeStoredFile(storage, "abc")
198
s.fakeStoredFile(storage, "xabc")
200
listing, err := storage.List("a")
202
c.Check(listing, DeepEquals, []string{"abc"})
205
func (s *StorageSuite) TestListOperatesOnFlatNamespace(c *C) {
206
storage := NewStorage(s.environ)
207
s.fakeStoredFile(storage, "a/b/c/d")
209
listing, err := storage.List("a/b")
211
c.Check(listing, DeepEquals, []string{"a/b/c/d"})
214
// getFileAtURL requests, and returns, the file at the given URL.
215
func getFileAtURL(fileURL string) ([]byte, error) {
216
response, err := http.Get(fileURL)
220
body, err := ioutil.ReadAll(response.Body)
227
func (s *StorageSuite) TestURLReturnsURLCorrespondingToFile(c *C) {
228
const filename = "my-file.txt"
229
storage := NewStorage(s.environ).(*maasStorage)
230
file := s.fakeStoredFile(storage, filename)
231
// The file contains an anon_resource_uri, which lacks a network part
232
// (but will probably contain a query part). anonURL will be the
234
anonURI, err := file.GetField("anon_resource_uri")
236
parsedURI, err := url.Parse(anonURI)
238
anonURL := storage.maasClientUnlocked.URL().ResolveReference(parsedURI)
241
fileURL, err := storage.URL(filename)
244
c.Check(fileURL, NotNil)
245
c.Check(fileURL, Equals, anonURL.String())
248
func (s *StorageSuite) TestPutStoresRetrievableFile(c *C) {
249
const filename = "broken-toaster.jpg"
250
contents := []byte("Contents here")
251
length := int64(len(contents))
252
storage := NewStorage(s.environ)
254
err := storage.Put(filename, bytes.NewReader(contents), length)
256
reader, err := storage.Get(filename)
260
buf, err := ioutil.ReadAll(reader)
262
c.Check(buf, DeepEquals, contents)
265
func (s *StorageSuite) TestPutOverwritesFile(c *C) {
266
const filename = "foo.bar"
267
storage := NewStorage(s.environ)
268
s.fakeStoredFile(storage, filename)
269
newContents := []byte("Overwritten")
271
err := storage.Put(filename, bytes.NewReader(newContents), int64(len(newContents)))
274
reader, err := storage.Get(filename)
278
buf, err := ioutil.ReadAll(reader)
280
c.Check(len(buf), Equals, len(newContents))
281
c.Check(buf, DeepEquals, newContents)
284
func (s *StorageSuite) TestPutStopsAtGivenLength(c *C) {
285
const filename = "xyzzyz.2.xls"
287
contents := []byte("abcdefghijklmnopqrstuvwxyz")
288
storage := NewStorage(s.environ)
290
err := storage.Put(filename, bytes.NewReader(contents), length)
293
reader, err := storage.Get(filename)
297
buf, err := ioutil.ReadAll(reader)
299
c.Check(len(buf), Equals, length)
302
func (s *StorageSuite) TestPutToExistingFileTruncatesAtGivenLength(c *C) {
303
const filename = "a-file-which-is-mine"
304
oldContents := []byte("abcdefghijklmnopqrstuvwxyz")
305
newContents := []byte("xyz")
306
storage := NewStorage(s.environ)
307
err := storage.Put(filename, bytes.NewReader(oldContents), int64(len(oldContents)))
310
err = storage.Put(filename, bytes.NewReader(newContents), int64(len(newContents)))
313
reader, err := storage.Get(filename)
317
buf, err := ioutil.ReadAll(reader)
319
c.Check(len(buf), Equals, len(newContents))
320
c.Check(buf, DeepEquals, newContents)
323
func (s *StorageSuite) TestRemoveDeletesFile(c *C) {
324
const filename = "doomed.txt"
325
storage := NewStorage(s.environ)
326
s.fakeStoredFile(storage, filename)
328
err := storage.Remove(filename)
331
_, err = storage.Get(filename)
332
c.Assert(err, FitsTypeOf, &environs.NotFoundError{})
334
listing, err := storage.List(filename)
336
c.Assert(listing, DeepEquals, []string{})
339
func (s *StorageSuite) TestRemoveIsIdempotent(c *C) {
340
const filename = "half-a-file"
341
storage := NewStorage(s.environ)
342
s.fakeStoredFile(storage, filename)
344
err := storage.Remove(filename)
347
err = storage.Remove(filename)
351
func (s *StorageSuite) TestNamesMayHaveSlashes(c *C) {
352
const filename = "name/with/slashes"
353
content := []byte("File contents")
354
storage := NewStorage(s.environ)
356
err := storage.Put(filename, bytes.NewReader(content), int64(len(content)))
359
// There's not much we can say about the anonymous URL, except that
361
anonURL, err := storage.URL(filename)
363
c.Check(anonURL, Matches, "http[s]*://.*")
365
reader, err := storage.Get(filename)
368
data, err := ioutil.ReadAll(reader)
370
c.Check(data, DeepEquals, content)
373
func (s *StorageSuite) TestDeleteAllDeletesAllFiles(c *C) {
374
storage := s.makeStorage("get-retrieves-file")
375
const filename1 = "stored-data1"
376
s.fakeStoredFile(storage, filename1)
377
const filename2 = "stored-data2"
378
s.fakeStoredFile(storage, filename2)
380
err := storage.deleteAll()
382
listing, err := storage.List("")
384
c.Assert(listing, DeepEquals, []string{})