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