github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/resource/state/resource_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"bytes"
     8  	"io"
     9  	"io/ioutil"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/testing"
    15  	jc "github.com/juju/testing/checkers"
    16  	gc "gopkg.in/check.v1"
    17  	charmresource "gopkg.in/juju/charm.v6-unstable/resource"
    18  	"gopkg.in/juju/names.v2"
    19  	"gopkg.in/mgo.v2/txn"
    20  
    21  	"github.com/juju/juju/resource"
    22  	"github.com/juju/juju/resource/resourcetesting"
    23  	"github.com/juju/utils/clock"
    24  )
    25  
    26  var _ = gc.Suite(&ResourceSuite{})
    27  
    28  type ResourceSuite struct {
    29  	testing.IsolationSuite
    30  
    31  	stub      *testing.Stub
    32  	raw       *stubRawState
    33  	persist   *stubPersistence
    34  	storage   *stubStorage
    35  	timestamp time.Time
    36  	pendingID string
    37  }
    38  
    39  func (s *ResourceSuite) SetUpTest(c *gc.C) {
    40  	s.IsolationSuite.SetUpTest(c)
    41  
    42  	s.stub = &testing.Stub{}
    43  	s.raw = &stubRawState{stub: s.stub}
    44  	s.persist = &stubPersistence{stub: s.stub}
    45  	s.persist.ReturnStageResource = &stubStagedResource{stub: s.stub}
    46  	s.storage = &stubStorage{stub: s.stub}
    47  	s.raw.ReturnPersistence = s.persist
    48  	s.raw.ReturnStorage = s.storage
    49  	s.timestamp = time.Now().UTC()
    50  	s.pendingID = ""
    51  }
    52  
    53  func (s *ResourceSuite) now() time.Time {
    54  	s.stub.AddCall("currentTimestamp")
    55  	s.stub.NextErr() // Pop one off.
    56  
    57  	return s.timestamp
    58  }
    59  
    60  func (s *ResourceSuite) newPendingID() (string, error) {
    61  	s.stub.AddCall("newPendingID")
    62  	if err := s.stub.NextErr(); err != nil {
    63  		return "", errors.Trace(err)
    64  	}
    65  
    66  	return s.pendingID, nil
    67  }
    68  
    69  func (s *ResourceSuite) TestListResourcesOkay(c *gc.C) {
    70  	expected := newUploadResources(c, "spam", "eggs")
    71  	s.persist.ReturnListResources = resource.ServiceResources{Resources: expected}
    72  	tag := names.NewUnitTag("a-application/0")
    73  	s.raw.ReturnUnits = []names.UnitTag{tag}
    74  	st := NewState(s.raw)
    75  	s.stub.ResetCalls()
    76  
    77  	resources, err := st.ListResources("a-application")
    78  	c.Assert(err, jc.ErrorIsNil)
    79  
    80  	c.Check(resources.Resources, jc.DeepEquals, expected)
    81  	c.Check(resources.UnitResources, jc.DeepEquals, []resource.UnitResources{{
    82  		Tag: tag,
    83  	}})
    84  	s.stub.CheckCallNames(c, "ListResources", "Units")
    85  	s.stub.CheckCall(c, 0, "ListResources", "a-application")
    86  }
    87  
    88  func (s *ResourceSuite) TestListResourcesNoUnits(c *gc.C) {
    89  	expected := newUploadResources(c, "spam", "eggs")
    90  	s.persist.ReturnListResources = resource.ServiceResources{Resources: expected}
    91  	st := NewState(s.raw)
    92  	s.stub.ResetCalls()
    93  
    94  	resources, err := st.ListResources("a-application")
    95  	c.Assert(err, jc.ErrorIsNil)
    96  
    97  	c.Check(resources.Resources, jc.DeepEquals, expected)
    98  	c.Check(resources.UnitResources, gc.HasLen, 0)
    99  	s.stub.CheckCallNames(c, "ListResources", "Units")
   100  	s.stub.CheckCall(c, 0, "ListResources", "a-application")
   101  }
   102  
   103  func (s *ResourceSuite) TestListResourcesEmpty(c *gc.C) {
   104  	s.raw.ReturnUnits = []names.UnitTag{
   105  		names.NewUnitTag("a-application/0"),
   106  	}
   107  	st := NewState(s.raw)
   108  	s.stub.ResetCalls()
   109  
   110  	resources, err := st.ListResources("a-application")
   111  	c.Assert(err, jc.ErrorIsNil)
   112  
   113  	c.Check(resources.Resources, gc.HasLen, 0)
   114  	c.Check(resources.UnitResources, gc.HasLen, 1)
   115  	s.stub.CheckCallNames(c, "ListResources", "Units")
   116  }
   117  
   118  func (s *ResourceSuite) TestListResourcesError(c *gc.C) {
   119  	expected := newUploadResources(c, "spam", "eggs")
   120  	s.persist.ReturnListResources = resource.ServiceResources{Resources: expected}
   121  	st := NewState(s.raw)
   122  	s.stub.ResetCalls()
   123  	failure := errors.New("<failure>")
   124  	s.stub.SetErrors(failure)
   125  
   126  	_, err := st.ListResources("a-application")
   127  
   128  	c.Check(errors.Cause(err), gc.Equals, failure)
   129  	s.stub.CheckCallNames(c, "ListResources", "VerifyService")
   130  }
   131  
   132  func (s *ResourceSuite) TestGetPendingResource(c *gc.C) {
   133  	resources := newUploadResources(c, "spam", "eggs")
   134  	resources[0].PendingID = "some-unique-id"
   135  	resources[1].PendingID = "other-unique-id"
   136  	s.persist.ReturnListPendingResources = resources
   137  	st := NewState(s.raw)
   138  	s.stub.ResetCalls()
   139  
   140  	res, err := st.GetPendingResource("a-application", "eggs", "other-unique-id")
   141  	c.Assert(err, jc.ErrorIsNil)
   142  
   143  	s.stub.CheckCallNames(c, "ListPendingResources")
   144  	s.stub.CheckCall(c, 0, "ListPendingResources", "a-application")
   145  	c.Check(res, jc.DeepEquals, resources[1])
   146  }
   147  
   148  func (s *ResourceSuite) TestSetResourceOkay(c *gc.C) {
   149  	expected := newUploadResource(c, "spam", "spamspamspam")
   150  	expected.Timestamp = s.timestamp
   151  	chRes := expected.Resource
   152  	hash := chRes.Fingerprint.String()
   153  	path := "application-a-application/resources/spam"
   154  	file := &stubReader{stub: s.stub}
   155  	st := NewState(s.raw)
   156  	st.currentTimestamp = s.now
   157  	s.stub.ResetCalls()
   158  
   159  	res, err := st.SetResource("a-application", "a-user", chRes, file)
   160  	c.Assert(err, jc.ErrorIsNil)
   161  
   162  	s.stub.CheckCallNames(c,
   163  		"currentTimestamp",
   164  		"StageResource",
   165  		"PutAndCheckHash",
   166  		"Activate",
   167  	)
   168  	s.stub.CheckCall(c, 1, "StageResource", expected, path)
   169  	s.stub.CheckCall(c, 2, "PutAndCheckHash", path, file, res.Size, hash)
   170  	c.Check(res, jc.DeepEquals, resource.Resource{
   171  		Resource:      chRes,
   172  		ID:            "a-application/" + res.Name,
   173  		ApplicationID: "a-application",
   174  		Username:      "a-user",
   175  		Timestamp:     s.timestamp,
   176  	})
   177  }
   178  
   179  func (s *ResourceSuite) TestSetResourceInfoOnly(c *gc.C) {
   180  	expected := newUploadResource(c, "spam", "spamspamspam")
   181  	expected.Timestamp = time.Time{}
   182  	expected.Username = ""
   183  	chRes := expected.Resource
   184  	st := NewState(s.raw)
   185  	st.currentTimestamp = s.now
   186  	s.stub.ResetCalls()
   187  
   188  	res, err := st.SetResource("a-application", "a-user", chRes, nil)
   189  	c.Assert(err, jc.ErrorIsNil)
   190  
   191  	s.stub.CheckCallNames(c,
   192  		"SetResource",
   193  	)
   194  	s.stub.CheckCall(c, 0, "SetResource", expected)
   195  	c.Check(res, jc.DeepEquals, resource.Resource{
   196  		Resource:      chRes,
   197  		ID:            "a-application/" + res.Name,
   198  		ApplicationID: "a-application",
   199  	})
   200  }
   201  
   202  func (s *ResourceSuite) TestSetResourceBadResource(c *gc.C) {
   203  	res := newUploadResource(c, "spam", "spamspamspam")
   204  	res.Fingerprint = charmresource.Fingerprint{}
   205  	file := &stubReader{stub: s.stub}
   206  	st := NewState(s.raw)
   207  	st.currentTimestamp = s.now
   208  	s.stub.ResetCalls()
   209  
   210  	_, err := st.SetResource("a-application", "a-user", res.Resource, file)
   211  
   212  	c.Check(err, jc.Satisfies, errors.IsNotValid)
   213  	c.Check(err, gc.ErrorMatches, `bad resource metadata.*`)
   214  	s.stub.CheckCallNames(c, "currentTimestamp")
   215  }
   216  
   217  func (s *ResourceSuite) TestSetResourceStagingFailure(c *gc.C) {
   218  	expected := newUploadResource(c, "spam", "spamspamspam")
   219  	expected.Timestamp = s.timestamp
   220  	path := "application-a-application/resources/spam"
   221  	file := &stubReader{stub: s.stub}
   222  	st := NewState(s.raw)
   223  	st.currentTimestamp = s.now
   224  	s.stub.ResetCalls()
   225  	failure := errors.New("<failure>")
   226  	ignoredErr := errors.New("<never reached>")
   227  	s.stub.SetErrors(nil, failure, ignoredErr)
   228  
   229  	_, err := st.SetResource("a-application", "a-user", expected.Resource, file)
   230  
   231  	c.Check(errors.Cause(err), gc.Equals, failure)
   232  	s.stub.CheckCallNames(c, "currentTimestamp", "StageResource")
   233  	s.stub.CheckCall(c, 1, "StageResource", expected, path)
   234  }
   235  
   236  func (s *ResourceSuite) TestSetResourcePutFailureBasic(c *gc.C) {
   237  	expected := newUploadResource(c, "spam", "spamspamspam")
   238  	expected.Timestamp = s.timestamp
   239  	hash := expected.Fingerprint.String()
   240  	path := "application-a-application/resources/spam"
   241  	file := &stubReader{stub: s.stub}
   242  	st := NewState(s.raw)
   243  	st.currentTimestamp = s.now
   244  	s.stub.ResetCalls()
   245  	failure := errors.New("<failure>")
   246  	ignoredErr := errors.New("<never reached>")
   247  	s.stub.SetErrors(nil, nil, failure, nil, ignoredErr)
   248  
   249  	_, err := st.SetResource("a-application", "a-user", expected.Resource, file)
   250  
   251  	c.Check(errors.Cause(err), gc.Equals, failure)
   252  	s.stub.CheckCallNames(c,
   253  		"currentTimestamp",
   254  		"StageResource",
   255  		"PutAndCheckHash",
   256  		"Unstage",
   257  	)
   258  	s.stub.CheckCall(c, 1, "StageResource", expected, path)
   259  	s.stub.CheckCall(c, 2, "PutAndCheckHash", path, file, expected.Size, hash)
   260  }
   261  
   262  func (s *ResourceSuite) TestSetResourcePutFailureExtra(c *gc.C) {
   263  	expected := newUploadResource(c, "spam", "spamspamspam")
   264  	expected.Timestamp = s.timestamp
   265  	hash := expected.Fingerprint.String()
   266  	path := "application-a-application/resources/spam"
   267  	file := &stubReader{stub: s.stub}
   268  	st := NewState(s.raw)
   269  	st.currentTimestamp = s.now
   270  	s.stub.ResetCalls()
   271  	failure := errors.New("<failure>")
   272  	extraErr := errors.New("<just not your day>")
   273  	ignoredErr := errors.New("<never reached>")
   274  	s.stub.SetErrors(nil, nil, failure, extraErr, ignoredErr)
   275  
   276  	_, err := st.SetResource("a-application", "a-user", expected.Resource, file)
   277  
   278  	c.Check(errors.Cause(err), gc.Equals, failure)
   279  	s.stub.CheckCallNames(c,
   280  		"currentTimestamp",
   281  		"StageResource",
   282  		"PutAndCheckHash",
   283  		"Unstage",
   284  	)
   285  	s.stub.CheckCall(c, 1, "StageResource", expected, path)
   286  	s.stub.CheckCall(c, 2, "PutAndCheckHash", path, file, expected.Size, hash)
   287  }
   288  
   289  func (s *ResourceSuite) TestSetResourceSetFailureBasic(c *gc.C) {
   290  	expected := newUploadResource(c, "spam", "spamspamspam")
   291  	expected.Timestamp = s.timestamp
   292  	hash := expected.Fingerprint.String()
   293  	path := "application-a-application/resources/spam"
   294  	file := &stubReader{stub: s.stub}
   295  	st := NewState(s.raw)
   296  	st.currentTimestamp = s.now
   297  	s.stub.ResetCalls()
   298  	failure := errors.New("<failure>")
   299  	ignoredErr := errors.New("<never reached>")
   300  	s.stub.SetErrors(nil, nil, nil, failure, nil, nil, ignoredErr)
   301  
   302  	_, err := st.SetResource("a-application", "a-user", expected.Resource, file)
   303  
   304  	c.Check(errors.Cause(err), gc.Equals, failure)
   305  	s.stub.CheckCallNames(c,
   306  		"currentTimestamp",
   307  		"StageResource",
   308  		"PutAndCheckHash",
   309  		"Activate",
   310  		"Remove",
   311  		"Unstage",
   312  	)
   313  	s.stub.CheckCall(c, 1, "StageResource", expected, path)
   314  	s.stub.CheckCall(c, 2, "PutAndCheckHash", path, file, expected.Size, hash)
   315  	s.stub.CheckCall(c, 4, "Remove", path)
   316  }
   317  
   318  func (s *ResourceSuite) TestSetResourceSetFailureExtra(c *gc.C) {
   319  	expected := newUploadResource(c, "spam", "spamspamspam")
   320  	expected.Timestamp = s.timestamp
   321  	hash := expected.Fingerprint.String()
   322  	path := "application-a-application/resources/spam"
   323  	file := &stubReader{stub: s.stub}
   324  	st := NewState(s.raw)
   325  	st.currentTimestamp = s.now
   326  	s.stub.ResetCalls()
   327  	failure := errors.New("<failure>")
   328  	extraErr1 := errors.New("<just not your day>")
   329  	extraErr2 := errors.New("<wow...just wow>")
   330  	ignoredErr := errors.New("<never reached>")
   331  	s.stub.SetErrors(nil, nil, nil, failure, extraErr1, extraErr2, ignoredErr)
   332  
   333  	_, err := st.SetResource("a-application", "a-user", expected.Resource, file)
   334  
   335  	c.Check(errors.Cause(err), gc.Equals, failure)
   336  	s.stub.CheckCallNames(c,
   337  		"currentTimestamp",
   338  		"StageResource",
   339  		"PutAndCheckHash",
   340  		"Activate",
   341  		"Remove",
   342  		"Unstage",
   343  	)
   344  	s.stub.CheckCall(c, 1, "StageResource", expected, path)
   345  	s.stub.CheckCall(c, 2, "PutAndCheckHash", path, file, expected.Size, hash)
   346  	s.stub.CheckCall(c, 4, "Remove", path)
   347  }
   348  
   349  func (s *ResourceSuite) TestUpdatePendingResourceOkay(c *gc.C) {
   350  	expected := newUploadResource(c, "spam", "spamspamspam")
   351  	expected.PendingID = "some-unique-id"
   352  	expected.Timestamp = s.timestamp
   353  	chRes := expected.Resource
   354  	hash := chRes.Fingerprint.String()
   355  	path := "application-a-application/resources/spam-some-unique-id"
   356  	file := &stubReader{stub: s.stub}
   357  	st := NewState(s.raw)
   358  	st.currentTimestamp = s.now
   359  	s.stub.ResetCalls()
   360  
   361  	res, err := st.UpdatePendingResource("a-application", "some-unique-id", "a-user", chRes, file)
   362  	c.Assert(err, jc.ErrorIsNil)
   363  
   364  	s.stub.CheckCallNames(c,
   365  		"currentTimestamp",
   366  		"StageResource",
   367  		"PutAndCheckHash",
   368  		"Activate",
   369  	)
   370  	s.stub.CheckCall(c, 1, "StageResource", expected, path)
   371  	s.stub.CheckCall(c, 2, "PutAndCheckHash", path, file, res.Size, hash)
   372  	c.Check(res, jc.DeepEquals, resource.Resource{
   373  		Resource:      chRes,
   374  		ID:            "a-application/" + res.Name,
   375  		ApplicationID: "a-application",
   376  		PendingID:     "some-unique-id",
   377  		Username:      "a-user",
   378  		Timestamp:     s.timestamp,
   379  	})
   380  }
   381  
   382  func (s *ResourceSuite) TestAddPendingResourceOkay(c *gc.C) {
   383  	s.pendingID = "some-unique-ID-001"
   384  	expected := newUploadResource(c, "spam", "spamspamspam")
   385  	expected.PendingID = s.pendingID
   386  	expected.Timestamp = s.timestamp
   387  	chRes := expected.Resource
   388  	hash := chRes.Fingerprint.String()
   389  	path := "application-a-application/resources/spam-some-unique-ID-001"
   390  	file := &stubReader{stub: s.stub}
   391  	st := NewState(s.raw)
   392  	st.currentTimestamp = s.now
   393  	st.newPendingID = s.newPendingID
   394  	s.stub.ResetCalls()
   395  
   396  	pendingID, err := st.AddPendingResource("a-application", "a-user", chRes, file)
   397  	c.Assert(err, jc.ErrorIsNil)
   398  
   399  	s.stub.CheckCallNames(c,
   400  		"newPendingID",
   401  		"currentTimestamp",
   402  		"StageResource",
   403  		"PutAndCheckHash",
   404  		"Activate",
   405  	)
   406  	s.stub.CheckCall(c, 2, "StageResource", expected, path)
   407  	s.stub.CheckCall(c, 3, "PutAndCheckHash", path, file, expected.Size, hash)
   408  	c.Check(pendingID, gc.Equals, s.pendingID)
   409  }
   410  
   411  func (s *ResourceSuite) TestOpenResourceOkay(c *gc.C) {
   412  	data := "some data"
   413  	opened := resourcetesting.NewResource(c, s.stub, "spam", "a-application", data)
   414  	s.persist.ReturnGetResource = opened.Resource
   415  	s.persist.ReturnGetResourcePath = "application-a-application/resources/spam"
   416  	s.storage.ReturnGet = opened.Content()
   417  	st := NewState(s.raw)
   418  	s.stub.ResetCalls()
   419  
   420  	info, reader, err := st.OpenResource("a-application", "spam")
   421  	c.Assert(err, jc.ErrorIsNil)
   422  
   423  	s.stub.CheckCallNames(c, "GetResource", "Get")
   424  	s.stub.CheckCall(c, 1, "Get", "application-a-application/resources/spam")
   425  	c.Check(info, jc.DeepEquals, opened.Resource)
   426  	c.Check(reader, gc.Equals, opened.ReadCloser)
   427  }
   428  
   429  func (s *ResourceSuite) TestOpenResourceNotFound(c *gc.C) {
   430  	st := NewState(s.raw)
   431  	s.stub.ResetCalls()
   432  	errNotFound := errors.NotFoundf("resource")
   433  	s.stub.SetErrors(errNotFound)
   434  
   435  	_, _, err := st.OpenResource("a-application", "spam")
   436  
   437  	s.stub.CheckCallNames(c, "GetResource", "VerifyService")
   438  	c.Check(err, jc.Satisfies, errors.IsNotFound)
   439  }
   440  
   441  func (s *ResourceSuite) TestOpenResourcePlaceholder(c *gc.C) {
   442  	res := resourcetesting.NewPlaceholderResource(c, "spam", "a-application")
   443  	s.persist.ReturnGetResource = res
   444  	s.persist.ReturnGetResourcePath = "application-a-application/resources/spam"
   445  	st := NewState(s.raw)
   446  	s.stub.ResetCalls()
   447  
   448  	_, _, err := st.OpenResource("a-application", "spam")
   449  
   450  	s.stub.CheckCallNames(c, "GetResource")
   451  	c.Check(err, jc.Satisfies, errors.IsNotFound)
   452  }
   453  
   454  func (s *ResourceSuite) TestOpenResourceSizeMismatch(c *gc.C) {
   455  	opened := resourcetesting.NewResource(c, s.stub, "spam", "a-application", "some data")
   456  	s.persist.ReturnGetResource = opened.Resource
   457  	s.persist.ReturnGetResourcePath = "application-a-application/resources/spam"
   458  	content := opened.Content()
   459  	content.Size += 1
   460  	s.storage.ReturnGet = content
   461  	st := NewState(s.raw)
   462  	s.stub.ResetCalls()
   463  
   464  	_, _, err := st.OpenResource("a-application", "spam")
   465  
   466  	s.stub.CheckCallNames(c, "GetResource", "Get")
   467  	c.Check(err, gc.ErrorMatches, `storage returned a size \(10\) which doesn't match resource metadata \(9\)`)
   468  }
   469  
   470  func (s *ResourceSuite) TestOpenResourceForUniterOkay(c *gc.C) {
   471  	data := "some data"
   472  	opened := resourcetesting.NewResource(c, s.stub, "spam", "a-application", data)
   473  	s.persist.ReturnGetResource = opened.Resource
   474  	s.persist.ReturnGetResourcePath = "application-a-application/resources/spam"
   475  	s.storage.ReturnGet = opened.Content()
   476  	unit := newUnit(s.stub, "a-application/0")
   477  	st := NewState(s.raw)
   478  	s.stub.ResetCalls()
   479  
   480  	info, reader, err := st.OpenResourceForUniter(unit, "spam")
   481  	c.Assert(err, jc.ErrorIsNil)
   482  
   483  	s.stub.CheckCallNames(c, "ApplicationName", "GetResource", "Get", "Name", "SetUnitResourceProgress")
   484  	s.stub.CheckCall(c, 2, "Get", "application-a-application/resources/spam")
   485  	c.Check(info, jc.DeepEquals, opened.Resource)
   486  
   487  	b, err := ioutil.ReadAll(reader)
   488  	// note ioutil.ReadAll converts EOF to nil
   489  	c.Check(err, jc.ErrorIsNil)
   490  	c.Check(b, gc.DeepEquals, []byte(data))
   491  }
   492  
   493  func (s *ResourceSuite) TestOpenResourceForUniterNotFound(c *gc.C) {
   494  	unit := newUnit(s.stub, "a-application/0")
   495  	st := NewState(s.raw)
   496  	s.stub.ResetCalls()
   497  	errNotFound := errors.NotFoundf("resource")
   498  	s.stub.SetErrors(nil, errNotFound)
   499  
   500  	_, _, err := st.OpenResourceForUniter(unit, "spam")
   501  
   502  	s.stub.CheckCallNames(c, "ApplicationName", "GetResource", "VerifyService")
   503  	c.Check(err, jc.Satisfies, errors.IsNotFound)
   504  }
   505  
   506  func (s *ResourceSuite) TestOpenResourceForUniterPlaceholder(c *gc.C) {
   507  	res := resourcetesting.NewPlaceholderResource(c, "spam", "a-application")
   508  	s.persist.ReturnGetResource = res
   509  	s.persist.ReturnGetResourcePath = "application-a-application/resources/spam"
   510  	unit := newUnit(s.stub, "a-application/0")
   511  	st := NewState(s.raw)
   512  	s.stub.ResetCalls()
   513  
   514  	_, _, err := st.OpenResourceForUniter(unit, "spam")
   515  
   516  	s.stub.CheckCallNames(c, "ApplicationName", "GetResource")
   517  	c.Check(err, jc.Satisfies, errors.IsNotFound)
   518  }
   519  
   520  func (s *ResourceSuite) TestOpenResourceForUniterSizeMismatch(c *gc.C) {
   521  	opened := resourcetesting.NewResource(c, s.stub, "spam", "a-application", "some data")
   522  	s.persist.ReturnGetResource = opened.Resource
   523  	s.persist.ReturnGetResourcePath = "application-a-application/resources/spam"
   524  	content := opened.Content()
   525  	content.Size += 1
   526  	s.storage.ReturnGet = content
   527  	unit := newUnit(s.stub, "a-application/0")
   528  	st := NewState(s.raw)
   529  	s.stub.ResetCalls()
   530  
   531  	_, _, err := st.OpenResourceForUniter(unit, "spam")
   532  
   533  	s.stub.CheckCallNames(c, "ApplicationName", "GetResource", "Get")
   534  	c.Check(err, gc.ErrorMatches, `storage returned a size \(10\) which doesn't match resource metadata \(9\)`)
   535  }
   536  
   537  func (s *ResourceSuite) TestSetCharmStoreResources(c *gc.C) {
   538  	lastPolled := time.Now().UTC()
   539  	resources := newStoreResources(c, "spam", "eggs")
   540  	var info []charmresource.Resource
   541  	for _, res := range resources {
   542  		chRes := res.Resource
   543  		info = append(info, chRes)
   544  	}
   545  	st := NewState(s.raw)
   546  	s.stub.ResetCalls()
   547  
   548  	err := st.SetCharmStoreResources("a-application", info, lastPolled)
   549  	c.Assert(err, jc.ErrorIsNil)
   550  
   551  	s.stub.CheckCallNames(c,
   552  		"SetCharmStoreResource",
   553  		"SetCharmStoreResource",
   554  	)
   555  	s.stub.CheckCall(c, 0, "SetCharmStoreResource", "a-application/spam", "a-application", info[0], lastPolled)
   556  	s.stub.CheckCall(c, 1, "SetCharmStoreResource", "a-application/eggs", "a-application", info[1], lastPolled)
   557  }
   558  
   559  func (s *ResourceSuite) TestNewResourcePendingResourcesOps(c *gc.C) {
   560  	doc1 := map[string]string{"a": "1"}
   561  	doc2 := map[string]string{"b": "2"}
   562  	expected := []txn.Op{{
   563  		C:      "resources",
   564  		Id:     "resource#a-application/spam#pending-some-unique-ID-001",
   565  		Assert: txn.DocExists,
   566  		Remove: true,
   567  	}, {
   568  		C:      "resources",
   569  		Id:     "resource#a-application/spam",
   570  		Assert: txn.DocMissing,
   571  		Insert: &doc1,
   572  	}, {
   573  		C:      "resources",
   574  		Id:     "resource#a-application/spam#pending-some-unique-ID-001",
   575  		Assert: txn.DocExists,
   576  		Remove: true,
   577  	}, {
   578  		C:      "resources",
   579  		Id:     "resource#a-application/spam",
   580  		Assert: txn.DocMissing,
   581  		Insert: &doc2,
   582  	}}
   583  	s.persist.ReturnNewResolvePendingResourceOps = [][]txn.Op{
   584  		expected[:2],
   585  		expected[2:],
   586  	}
   587  	applicationID := "a-application"
   588  	st := NewState(s.raw)
   589  	s.stub.ResetCalls()
   590  	pendingIDs := map[string]string{
   591  		"spam": "some-unique-id",
   592  		"eggs": "other-unique-id",
   593  	}
   594  
   595  	ops, err := st.NewResolvePendingResourcesOps(applicationID, pendingIDs)
   596  	c.Assert(err, jc.ErrorIsNil)
   597  
   598  	s.stub.CheckCallNames(c,
   599  		"NewResolvePendingResourceOps",
   600  		"NewResolvePendingResourceOps",
   601  	)
   602  	c.Check(s.persist.CallsForNewResolvePendingResourceOps, jc.DeepEquals, map[string]string{
   603  		"a-application/spam": "some-unique-id",
   604  		"a-application/eggs": "other-unique-id",
   605  	})
   606  	c.Check(ops, jc.DeepEquals, expected)
   607  }
   608  
   609  func (s *ResourceSuite) TestUnitSetterEOF(c *gc.C) {
   610  	r := unitSetter{
   611  		ReadCloser: ioutil.NopCloser(&bytes.Buffer{}),
   612  		persist:    &stubPersistence{stub: s.stub},
   613  		unit:       newUnit(s.stub, "some-application/0"),
   614  		resource:   newUploadResource(c, "res", "res"),
   615  		clock:      clock.WallClock,
   616  	}
   617  	// have to try to read non-zero data, or bytes.buffer will happily return
   618  	// nil.
   619  	p := make([]byte, 5)
   620  	n, err := r.Read(p)
   621  	c.Assert(n, gc.Equals, 0)
   622  	c.Assert(err, gc.Equals, io.EOF)
   623  
   624  	s.stub.CheckCallNames(c, "Name", "SetUnitResource")
   625  	s.stub.CheckCall(c, 1, "SetUnitResource", "some-application/0", r.resource)
   626  }
   627  
   628  func (s *ResourceSuite) TestUnitSetterNoEOF(c *gc.C) {
   629  	r := unitSetter{
   630  		ReadCloser: ioutil.NopCloser(bytes.NewBufferString("foobar")),
   631  		persist:    &stubPersistence{stub: s.stub},
   632  		unit:       newUnit(s.stub, "some-application/0"),
   633  		resource:   newUploadResource(c, "res", "res"),
   634  		clock:      clock.WallClock,
   635  	}
   636  	// read less than the full buffer
   637  	p := make([]byte, 3)
   638  	n, err := r.Read(p)
   639  	c.Assert(n, gc.Equals, 3)
   640  	c.Assert(err, gc.Equals, nil)
   641  
   642  	// Assert that we don't call SetUnitResource if we read but don't reach the
   643  	// end of the buffer.
   644  	s.stub.CheckCallNames(c, "Name", "SetUnitResourceProgress")
   645  }
   646  
   647  func (s *ResourceSuite) TestUnitSetterSetUnitErr(c *gc.C) {
   648  	r := unitSetter{
   649  		ReadCloser: ioutil.NopCloser(&bytes.Buffer{}),
   650  		persist:    &stubPersistence{stub: s.stub},
   651  		unit:       newUnit(s.stub, "some-application/0"),
   652  		resource:   newUploadResource(c, "res", "res"),
   653  		clock:      clock.WallClock,
   654  	}
   655  
   656  	s.stub.SetErrors(errors.Errorf("oops!"))
   657  	// have to try to read non-zero data, or bytes.buffer will happily return
   658  	// nil.
   659  	p := make([]byte, 5)
   660  	n, err := r.Read(p)
   661  	c.Assert(n, gc.Equals, 0)
   662  
   663  	// ensure that we return the EOF from bytes.buffer and not the error from SetUnitResource.
   664  	c.Assert(err, gc.Equals, io.EOF)
   665  
   666  	s.stub.CheckCallNames(c, "Name", "SetUnitResource")
   667  	s.stub.CheckCall(c, 1, "SetUnitResource", "some-application/0", r.resource)
   668  }
   669  
   670  func (s *ResourceSuite) TestUnitSetterErr(c *gc.C) {
   671  	r := unitSetter{
   672  		ReadCloser: ioutil.NopCloser(&stubReader{stub: s.stub}),
   673  		persist:    &stubPersistence{stub: s.stub},
   674  		unit:       newUnit(s.stub, "some-application/0"),
   675  		resource:   newUploadResource(c, "res", "res"),
   676  		clock:      clock.WallClock,
   677  	}
   678  	expected := errors.Errorf("some-err")
   679  	s.stub.SetErrors(expected)
   680  	// have to try to read non-zero data, or bytes.buffer will happily return
   681  	// nil.
   682  	p := make([]byte, 5)
   683  	n, err := r.Read(p)
   684  	c.Assert(n, gc.Equals, 0)
   685  	c.Assert(err, gc.Equals, expected)
   686  
   687  	s.stub.CheckCall(c, 0, "Read", p)
   688  }
   689  
   690  func newUploadResources(c *gc.C, names ...string) []resource.Resource {
   691  	var resources []resource.Resource
   692  	for _, name := range names {
   693  		res := newUploadResource(c, name, name)
   694  		resources = append(resources, res)
   695  	}
   696  	return resources
   697  }
   698  
   699  func newUploadResource(c *gc.C, name, data string) resource.Resource {
   700  	opened := resourcetesting.NewResource(c, nil, name, "a-application", data)
   701  	return opened.Resource
   702  }
   703  
   704  func newStoreResources(c *gc.C, names ...string) []resource.Resource {
   705  	var resources []resource.Resource
   706  	for _, name := range names {
   707  		res := newStoreResource(c, name, name)
   708  		resources = append(resources, res)
   709  	}
   710  	return resources
   711  }
   712  
   713  func newStoreResource(c *gc.C, name, data string) resource.Resource {
   714  	opened := resourcetesting.NewResource(c, nil, name, "a-application", data)
   715  	res := opened.Resource
   716  	res.Origin = charmresource.OriginStore
   717  	res.Revision = 1
   718  	res.Username = ""
   719  	res.Timestamp = time.Time{}
   720  	return res
   721  }
   722  
   723  func newCharmResource(c *gc.C, name, data string, rev int) charmresource.Resource {
   724  	opened := resourcetesting.NewResource(c, nil, name, "a-application", data)
   725  	chRes := opened.Resource.Resource
   726  	chRes.Origin = charmresource.OriginStore
   727  	chRes.Revision = rev
   728  	return chRes
   729  }
   730  
   731  func newUnit(stub *testing.Stub, name string) *resourcetesting.StubUnit {
   732  	return &resourcetesting.StubUnit{
   733  		Stub:                  stub,
   734  		ReturnName:            name,
   735  		ReturnApplicationName: strings.Split(name, "/")[0],
   736  	}
   737  }