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  }