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  }