github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/maas/storage_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package maas 5 6 import ( 7 "bytes" 8 "encoding/base64" 9 "io/ioutil" 10 "math/rand" 11 "net/url" 12 13 "github.com/juju/errors" 14 "github.com/juju/gomaasapi" 15 jc "github.com/juju/testing/checkers" 16 gc "gopkg.in/check.v1" 17 18 "github.com/juju/juju/environs/storage" 19 ) 20 21 var _ storage.Storage = (*maas1Storage)(nil) 22 23 type storageSuite struct { 24 providerSuite 25 } 26 27 var _ = gc.Suite(&storageSuite{}) 28 29 // makeStorage creates a MAAS storage object for the running test. 30 func (s *storageSuite) makeStorage(name string) *maas1Storage { 31 maasobj := s.testMAASObject.MAASObject 32 env := s.makeEnviron() 33 env.name = name 34 env.maasClientUnlocked = &maasobj 35 return NewStorage(env).(*maas1Storage) 36 } 37 38 // makeRandomBytes returns an array of arbitrary byte values. 39 func makeRandomBytes(length int) []byte { 40 data := make([]byte, length) 41 for index := range data { 42 data[index] = byte(rand.Intn(256)) 43 } 44 return data 45 } 46 47 // fakeStoredFile creates a file directly in the (simulated) MAAS file store. 48 // It will contain an arbitrary amount of random data. The contents are also 49 // returned. 50 // 51 // If you want properly random data here, initialize the randomizer first. 52 // Or don't, if you want consistent (and debuggable) results. 53 func (s *storageSuite) fakeStoredFile(stor storage.Storage, name string) gomaasapi.MAASObject { 54 data := makeRandomBytes(rand.Intn(10)) 55 // The filename must be prefixed with the private namespace as we're 56 // bypassing the Put() method that would normally do that. 57 prefixFilename := stor.(*maas1Storage).prefixWithPrivateNamespace("") + name 58 return s.testMAASObject.TestServer.NewFile(prefixFilename, data) 59 } 60 61 func (s *storageSuite) TestGetRetrievesFile(c *gc.C) { 62 const filename = "stored-data" 63 stor := s.makeStorage("get-retrieves-file") 64 file := s.fakeStoredFile(stor, filename) 65 base64Content, err := file.GetField("content") 66 c.Assert(err, jc.ErrorIsNil) 67 content, err := base64.StdEncoding.DecodeString(base64Content) 68 c.Assert(err, jc.ErrorIsNil) 69 70 reader, err := storage.Get(stor, filename) 71 c.Assert(err, jc.ErrorIsNil) 72 defer reader.Close() 73 74 buf, err := ioutil.ReadAll(reader) 75 c.Assert(err, jc.ErrorIsNil) 76 c.Check(len(buf), gc.Equals, len(content)) 77 c.Check(buf, gc.DeepEquals, content) 78 } 79 80 func (s *storageSuite) TestRetrieveFileObjectReturnsFileObject(c *gc.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") 85 c.Assert(err, jc.ErrorIsNil) 86 fileContent, err := file.GetField("content") 87 c.Assert(err, jc.ErrorIsNil) 88 89 prefixFilename := stor.prefixWithPrivateNamespace(filename) 90 obj, err := stor.retrieveFileObject(prefixFilename) 91 c.Assert(err, jc.ErrorIsNil) 92 93 uri, err := obj.GetField("anon_resource_uri") 94 c.Assert(err, jc.ErrorIsNil) 95 c.Check(uri, gc.Equals, fileURI) 96 content, err := obj.GetField("content") 97 c.Check(content, gc.Equals, fileContent) 98 } 99 100 func (s *storageSuite) TestRetrieveFileObjectReturnsNotFoundForMissingFile(c *gc.C) { 101 stor := s.makeStorage("rfo-test") 102 _, err := stor.retrieveFileObject("nonexistent-file") 103 c.Assert(err, gc.NotNil) 104 c.Check(err, jc.Satisfies, errors.IsNotFound) 105 } 106 107 func (s *storageSuite) TestRetrieveFileObjectEscapesName(c *gc.C) { 108 const filename = "#a?b c&d%e!" 109 data := []byte("File contents here") 110 stor := s.makeStorage("rfo-test") 111 err := stor.Put(filename, bytes.NewReader(data), int64(len(data))) 112 c.Assert(err, jc.ErrorIsNil) 113 114 prefixFilename := stor.prefixWithPrivateNamespace(filename) 115 obj, err := stor.retrieveFileObject(prefixFilename) 116 c.Assert(err, jc.ErrorIsNil) 117 118 base64Content, err := obj.GetField("content") 119 c.Assert(err, jc.ErrorIsNil) 120 content, err := base64.StdEncoding.DecodeString(base64Content) 121 c.Assert(err, jc.ErrorIsNil) 122 c.Check(content, gc.DeepEquals, data) 123 } 124 125 func (s *storageSuite) TestFileContentsAreBinary(c *gc.C) { 126 const filename = "myfile.bin" 127 data := []byte{0, 1, 255, 2, 254, 3} 128 stor := s.makeStorage("binary-test") 129 130 err := stor.Put(filename, bytes.NewReader(data), int64(len(data))) 131 c.Assert(err, jc.ErrorIsNil) 132 file, err := storage.Get(stor, filename) 133 c.Assert(err, jc.ErrorIsNil) 134 content, err := ioutil.ReadAll(file) 135 c.Assert(err, jc.ErrorIsNil) 136 137 c.Check(content, gc.DeepEquals, data) 138 } 139 140 func (s *storageSuite) TestGetReturnsNotFoundErrorIfNotFound(c *gc.C) { 141 const filename = "lost-data" 142 stor := NewStorage(s.makeEnviron()) 143 _, err := storage.Get(stor, filename) 144 c.Assert(err, jc.Satisfies, errors.IsNotFound) 145 } 146 147 func (s *storageSuite) TestListReturnsEmptyIfNoFilesStored(c *gc.C) { 148 stor := NewStorage(s.makeEnviron()) 149 listing, err := storage.List(stor, "") 150 c.Assert(err, jc.ErrorIsNil) 151 c.Check(listing, gc.DeepEquals, []string{}) 152 } 153 154 func (s *storageSuite) TestListReturnsAllFilesIfPrefixEmpty(c *gc.C) { 155 stor := NewStorage(s.makeEnviron()) 156 files := []string{"1a", "2b", "3c"} 157 for _, name := range files { 158 s.fakeStoredFile(stor, name) 159 } 160 161 listing, err := storage.List(stor, "") 162 c.Assert(err, jc.ErrorIsNil) 163 c.Check(listing, gc.DeepEquals, files) 164 } 165 166 func (s *storageSuite) TestListSortsResults(c *gc.C) { 167 stor := NewStorage(s.makeEnviron()) 168 files := []string{"4d", "1a", "3c", "2b"} 169 for _, name := range files { 170 s.fakeStoredFile(stor, name) 171 } 172 173 listing, err := storage.List(stor, "") 174 c.Assert(err, jc.ErrorIsNil) 175 c.Check(listing, gc.DeepEquals, []string{"1a", "2b", "3c", "4d"}) 176 } 177 178 func (s *storageSuite) TestListReturnsNoFilesIfNoFilesMatchPrefix(c *gc.C) { 179 stor := NewStorage(s.makeEnviron()) 180 s.fakeStoredFile(stor, "foo") 181 182 listing, err := storage.List(stor, "bar") 183 c.Assert(err, jc.ErrorIsNil) 184 c.Check(listing, gc.DeepEquals, []string{}) 185 } 186 187 func (s *storageSuite) TestListReturnsOnlyFilesWithMatchingPrefix(c *gc.C) { 188 stor := NewStorage(s.makeEnviron()) 189 s.fakeStoredFile(stor, "abc") 190 s.fakeStoredFile(stor, "xyz") 191 192 listing, err := storage.List(stor, "x") 193 c.Assert(err, jc.ErrorIsNil) 194 c.Check(listing, gc.DeepEquals, []string{"xyz"}) 195 } 196 197 func (s *storageSuite) TestListMatchesPrefixOnly(c *gc.C) { 198 stor := NewStorage(s.makeEnviron()) 199 s.fakeStoredFile(stor, "abc") 200 s.fakeStoredFile(stor, "xabc") 201 202 listing, err := storage.List(stor, "a") 203 c.Assert(err, jc.ErrorIsNil) 204 c.Check(listing, gc.DeepEquals, []string{"abc"}) 205 } 206 207 func (s *storageSuite) TestListOperatesOnFlatNamespace(c *gc.C) { 208 stor := NewStorage(s.makeEnviron()) 209 s.fakeStoredFile(stor, "a/b/c/d") 210 211 listing, err := storage.List(stor, "a/b") 212 c.Assert(err, jc.ErrorIsNil) 213 c.Check(listing, gc.DeepEquals, []string{"a/b/c/d"}) 214 } 215 216 func (s *storageSuite) TestURLReturnsURLCorrespondingToFile(c *gc.C) { 217 const filename = "my-file.txt" 218 stor := NewStorage(s.makeEnviron()).(*maas1Storage) 219 file := s.fakeStoredFile(stor, filename) 220 // The file contains an anon_resource_uri, which lacks a network part 221 // (but will probably contain a query part). anonURL will be the 222 // file's full URL. 223 anonURI, err := file.GetField("anon_resource_uri") 224 c.Assert(err, jc.ErrorIsNil) 225 parsedURI, err := url.Parse(anonURI) 226 c.Assert(err, jc.ErrorIsNil) 227 anonURL := stor.maasClient.URL().ResolveReference(parsedURI) 228 c.Assert(err, jc.ErrorIsNil) 229 230 fileURL, err := stor.URL(filename) 231 c.Assert(err, jc.ErrorIsNil) 232 233 c.Check(fileURL, gc.NotNil) 234 c.Check(fileURL, gc.Equals, anonURL.String()) 235 } 236 237 func (s *storageSuite) TestPutStoresRetrievableFile(c *gc.C) { 238 const filename = "broken-toaster.jpg" 239 contents := []byte("Contents here") 240 length := int64(len(contents)) 241 stor := NewStorage(s.makeEnviron()) 242 243 err := stor.Put(filename, bytes.NewReader(contents), length) 244 245 reader, err := storage.Get(stor, filename) 246 c.Assert(err, jc.ErrorIsNil) 247 defer reader.Close() 248 249 buf, err := ioutil.ReadAll(reader) 250 c.Assert(err, jc.ErrorIsNil) 251 c.Check(buf, gc.DeepEquals, contents) 252 } 253 254 func (s *storageSuite) TestPutOverwritesFile(c *gc.C) { 255 const filename = "foo.bar" 256 stor := NewStorage(s.makeEnviron()) 257 s.fakeStoredFile(stor, filename) 258 newContents := []byte("Overwritten") 259 260 err := stor.Put(filename, bytes.NewReader(newContents), int64(len(newContents))) 261 c.Assert(err, jc.ErrorIsNil) 262 263 reader, err := storage.Get(stor, filename) 264 c.Assert(err, jc.ErrorIsNil) 265 defer reader.Close() 266 267 buf, err := ioutil.ReadAll(reader) 268 c.Assert(err, jc.ErrorIsNil) 269 c.Check(len(buf), gc.Equals, len(newContents)) 270 c.Check(buf, gc.DeepEquals, newContents) 271 } 272 273 func (s *storageSuite) TestPutStopsAtGivenLength(c *gc.C) { 274 const filename = "xyzzyz.2.xls" 275 const length = 5 276 contents := []byte("abcdefghijklmnopqrstuvwxyz") 277 stor := NewStorage(s.makeEnviron()) 278 279 err := stor.Put(filename, bytes.NewReader(contents), length) 280 c.Assert(err, jc.ErrorIsNil) 281 282 reader, err := storage.Get(stor, filename) 283 c.Assert(err, jc.ErrorIsNil) 284 defer reader.Close() 285 286 buf, err := ioutil.ReadAll(reader) 287 c.Assert(err, jc.ErrorIsNil) 288 c.Check(len(buf), gc.Equals, length) 289 } 290 291 func (s *storageSuite) TestPutToExistingFileTruncatesAtGivenLength(c *gc.C) { 292 const filename = "a-file-which-is-mine" 293 oldContents := []byte("abcdefghijklmnopqrstuvwxyz") 294 newContents := []byte("xyz") 295 stor := NewStorage(s.makeEnviron()) 296 err := stor.Put(filename, bytes.NewReader(oldContents), int64(len(oldContents))) 297 c.Assert(err, jc.ErrorIsNil) 298 299 err = stor.Put(filename, bytes.NewReader(newContents), int64(len(newContents))) 300 c.Assert(err, jc.ErrorIsNil) 301 302 reader, err := storage.Get(stor, filename) 303 c.Assert(err, jc.ErrorIsNil) 304 defer reader.Close() 305 306 buf, err := ioutil.ReadAll(reader) 307 c.Assert(err, jc.ErrorIsNil) 308 c.Check(len(buf), gc.Equals, len(newContents)) 309 c.Check(buf, gc.DeepEquals, newContents) 310 } 311 312 func (s *storageSuite) TestRemoveDeletesFile(c *gc.C) { 313 const filename = "doomed.txt" 314 stor := NewStorage(s.makeEnviron()) 315 s.fakeStoredFile(stor, filename) 316 317 err := stor.Remove(filename) 318 c.Assert(err, jc.ErrorIsNil) 319 320 _, err = storage.Get(stor, filename) 321 c.Assert(err, jc.Satisfies, errors.IsNotFound) 322 323 listing, err := storage.List(stor, filename) 324 c.Assert(err, jc.ErrorIsNil) 325 c.Assert(listing, gc.DeepEquals, []string{}) 326 } 327 328 func (s *storageSuite) TestRemoveIsIdempotent(c *gc.C) { 329 const filename = "half-a-file" 330 stor := NewStorage(s.makeEnviron()) 331 s.fakeStoredFile(stor, filename) 332 333 err := stor.Remove(filename) 334 c.Assert(err, jc.ErrorIsNil) 335 336 err = stor.Remove(filename) 337 c.Assert(err, jc.ErrorIsNil) 338 } 339 340 func (s *storageSuite) TestNamesMayHaveSlashes(c *gc.C) { 341 const filename = "name/with/slashes" 342 content := []byte("File contents") 343 stor := NewStorage(s.makeEnviron()) 344 345 err := stor.Put(filename, bytes.NewReader(content), int64(len(content))) 346 c.Assert(err, jc.ErrorIsNil) 347 348 // There's not much we can say about the anonymous URL, except that 349 // we get one. 350 anonURL, err := stor.URL(filename) 351 c.Assert(err, jc.ErrorIsNil) 352 c.Check(anonURL, gc.Matches, "http[s]*://.*") 353 354 reader, err := storage.Get(stor, filename) 355 c.Assert(err, jc.ErrorIsNil) 356 defer reader.Close() 357 data, err := ioutil.ReadAll(reader) 358 c.Assert(err, jc.ErrorIsNil) 359 c.Check(data, gc.DeepEquals, content) 360 } 361 362 func (s *storageSuite) TestRemoveAllDeletesAllFiles(c *gc.C) { 363 stor := s.makeStorage("get-retrieves-file") 364 const filename1 = "stored-data1" 365 s.fakeStoredFile(stor, filename1) 366 const filename2 = "stored-data2" 367 s.fakeStoredFile(stor, filename2) 368 369 err := stor.RemoveAll() 370 c.Assert(err, jc.ErrorIsNil) 371 listing, err := storage.List(stor, "") 372 c.Assert(err, jc.ErrorIsNil) 373 c.Assert(listing, gc.DeepEquals, []string{}) 374 } 375 376 func (s *storageSuite) TestprefixWithPrivateNamespacePrefixesWithAgentName(c *gc.C) { 377 env := s.makeEnviron() 378 sstor := NewStorage(env) 379 stor := sstor.(*maas1Storage) 380 expectedPrefix := env.Config().UUID() + "-" 381 const name = "myname" 382 expectedResult := expectedPrefix + name 383 c.Assert(stor.prefixWithPrivateNamespace(name), gc.Equals, expectedResult) 384 }