github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/state/resources_persistence_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  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/names"
    11  	"github.com/juju/testing"
    12  	jc "github.com/juju/testing/checkers"
    13  	gc "gopkg.in/check.v1"
    14  	"gopkg.in/mgo.v2/bson"
    15  	"gopkg.in/mgo.v2/txn"
    16  
    17  	"github.com/juju/juju/resource"
    18  	"github.com/juju/juju/resource/resourcetesting"
    19  	"github.com/juju/juju/state/statetest"
    20  )
    21  
    22  var _ = gc.Suite(&ResourcePersistenceSuite{})
    23  
    24  type ResourcePersistenceSuite struct {
    25  	testing.IsolationSuite
    26  
    27  	stub *testing.Stub
    28  	base *statetest.StubPersistence
    29  }
    30  
    31  func (s *ResourcePersistenceSuite) SetUpTest(c *gc.C) {
    32  	s.IsolationSuite.SetUpTest(c)
    33  
    34  	s.stub = &testing.Stub{}
    35  	s.base = statetest.NewStubPersistence(s.stub)
    36  	s.base.ReturnServiceExistsOps = []txn.Op{{
    37  		C:      "service",
    38  		Id:     "a-service",
    39  		Assert: txn.DocExists,
    40  	}}
    41  }
    42  
    43  func (s *ResourcePersistenceSuite) TestListResourcesOkay(c *gc.C) {
    44  	expected, docs := newPersistenceResources(c, "a-service", "spam", "eggs")
    45  	expected.CharmStoreResources[1].Revision += 1
    46  	docs[3].Revision += 1
    47  	unitRes, unitDocs := newPersistenceUnitResources(c, "a-service", "a-service/0", expected.Resources)
    48  	var progress int64 = 3
    49  	unitDocs[1].DownloadProgress = &progress // the "eggs" doc
    50  	expected.UnitResources = []resource.UnitResources{{
    51  		Tag:       names.NewUnitTag("a-service/0"),
    52  		Resources: unitRes,
    53  		DownloadProgress: map[string]int64{
    54  			"eggs": progress,
    55  		},
    56  	}}
    57  	docs = append(docs, unitDocs...)
    58  	s.base.ReturnAll = docs
    59  	p := NewResourcePersistence(s.base)
    60  
    61  	resources, err := p.ListResources("a-service")
    62  	c.Assert(err, jc.ErrorIsNil)
    63  
    64  	s.stub.CheckCallNames(c, "All")
    65  	s.stub.CheckCall(c, 0, "All",
    66  		"resources",
    67  		bson.D{{"service-id", "a-service"}},
    68  		&docs,
    69  	)
    70  	c.Check(resources, jc.DeepEquals, expected)
    71  }
    72  
    73  func (s *ResourcePersistenceSuite) TestListResourcesNoResources(c *gc.C) {
    74  	p := NewResourcePersistence(s.base)
    75  	resources, err := p.ListResources("a-service")
    76  	c.Assert(err, jc.ErrorIsNil)
    77  
    78  	c.Check(resources.Resources, gc.HasLen, 0)
    79  	s.stub.CheckCallNames(c, "All")
    80  	s.stub.CheckCall(c, 0, "All",
    81  		"resources",
    82  		bson.D{{"service-id", "a-service"}},
    83  		&[]resourceDoc{},
    84  	)
    85  }
    86  
    87  func (s *ResourcePersistenceSuite) TestListResourcesIgnorePending(c *gc.C) {
    88  	expected, docs := newPersistenceResources(c, "a-service", "spam", "eggs")
    89  	expected.Resources = expected.Resources[:1]
    90  	docs[2].PendingID = "some-unique-ID-001"
    91  	s.base.ReturnAll = docs
    92  	p := NewResourcePersistence(s.base)
    93  
    94  	resources, err := p.ListResources("a-service")
    95  	c.Assert(err, jc.ErrorIsNil)
    96  
    97  	s.stub.CheckCallNames(c, "All")
    98  	s.stub.CheckCall(c, 0, "All",
    99  		"resources",
   100  		bson.D{{"service-id", "a-service"}},
   101  		&docs,
   102  	)
   103  	checkResources(c, resources, expected)
   104  }
   105  
   106  func (s *ResourcePersistenceSuite) TestListResourcesBaseError(c *gc.C) {
   107  	failure := errors.New("<failure>")
   108  	s.stub.SetErrors(failure)
   109  
   110  	p := NewResourcePersistence(s.base)
   111  	_, err := p.ListResources("a-service")
   112  
   113  	c.Check(errors.Cause(err), gc.Equals, failure)
   114  	s.stub.CheckCallNames(c, "All")
   115  	s.stub.CheckCall(c, 0, "All",
   116  		"resources",
   117  		bson.D{{"service-id", "a-service"}},
   118  		&[]resourceDoc{},
   119  	)
   120  }
   121  
   122  func (s *ResourcePersistenceSuite) TestListResourcesBadDoc(c *gc.C) {
   123  	_, docs := newPersistenceResources(c, "a-service", "spam", "eggs")
   124  	docs[0].Timestamp = time.Time{}
   125  	s.base.ReturnAll = docs
   126  
   127  	p := NewResourcePersistence(s.base)
   128  	_, err := p.ListResources("a-service")
   129  
   130  	c.Check(err, gc.ErrorMatches, `got invalid data from DB.*`)
   131  	s.stub.CheckCallNames(c, "All")
   132  	s.stub.CheckCall(c, 0, "All",
   133  		"resources",
   134  		bson.D{{"service-id", "a-service"}},
   135  		&docs,
   136  	)
   137  }
   138  
   139  func (s *ResourcePersistenceSuite) TestListPendingResourcesOkay(c *gc.C) {
   140  	var expected []resource.Resource
   141  	var docs []resourceDoc
   142  	for _, name := range []string{"spam", "ham"} {
   143  		res, doc := newPersistenceResource(c, "a-service", name)
   144  		expected = append(expected, res.Resource)
   145  		docs = append(docs, doc)
   146  	}
   147  	expected = expected[1:]
   148  	expected[0].PendingID = "some-unique-ID-001"
   149  	docs[1].PendingID = "some-unique-ID-001"
   150  	s.base.ReturnAll = docs
   151  	p := NewResourcePersistence(s.base)
   152  
   153  	resources, err := p.ListPendingResources("a-service")
   154  	c.Assert(err, jc.ErrorIsNil)
   155  
   156  	s.stub.CheckCallNames(c, "All")
   157  	s.stub.CheckCall(c, 0, "All",
   158  		"resources",
   159  		bson.D{{"service-id", "a-service"}},
   160  		&docs,
   161  	)
   162  	checkBasicResources(c, resources, expected)
   163  }
   164  
   165  func (s *ResourcePersistenceSuite) TestGetResourceOkay(c *gc.C) {
   166  	expected, doc := newPersistenceResource(c, "a-service", "spam")
   167  	unitDoc := doc // a copy
   168  	unitDoc.ID = doc.ID + "#unit-a-service/0"
   169  	unitDoc.UnitID = "a-service/0"
   170  	pendingDoc := doc // a copy
   171  	pendingDoc.ID = doc.ID + "#pending-some-unique-ID"
   172  	pendingDoc.PendingID = "some-unique-ID"
   173  	s.base.ReturnAll = []resourceDoc{
   174  		doc,
   175  		unitDoc,
   176  		pendingDoc,
   177  	}
   178  	s.base.ReturnOne = doc
   179  	p := NewResourcePersistence(s.base)
   180  
   181  	res, storagePath, err := p.GetResource("a-service/spam")
   182  	c.Assert(err, jc.ErrorIsNil)
   183  
   184  	s.stub.CheckCallNames(c, "One")
   185  	s.stub.CheckCall(c, 0, "One", "resources", "resource#a-service/spam", &doc)
   186  	c.Check(res, jc.DeepEquals, expected.Resource)
   187  	c.Check(storagePath, gc.Equals, expected.storagePath)
   188  }
   189  
   190  func (s *ResourcePersistenceSuite) TestStageResourceOkay(c *gc.C) {
   191  	res, doc := newPersistenceResource(c, "a-service", "spam")
   192  	doc.DocID += "#staged"
   193  	p := NewResourcePersistence(s.base)
   194  	ignoredErr := errors.New("<never reached>")
   195  	s.stub.SetErrors(nil, nil, nil, ignoredErr)
   196  
   197  	staged, err := p.StageResource(res.Resource, res.storagePath)
   198  	c.Assert(err, jc.ErrorIsNil)
   199  
   200  	s.stub.CheckCallNames(c, "Run", "ServiceExistsOps", "RunTransaction")
   201  	s.stub.CheckCall(c, 2, "RunTransaction", []txn.Op{{
   202  		C:      "resources",
   203  		Id:     "resource#a-service/spam#staged",
   204  		Assert: txn.DocMissing,
   205  		Insert: &doc,
   206  	}, {
   207  		C:      "service",
   208  		Id:     "a-service",
   209  		Assert: txn.DocExists,
   210  	}})
   211  	c.Check(staged, jc.DeepEquals, &StagedResource{
   212  		base:   s.base,
   213  		id:     res.ID,
   214  		stored: res,
   215  	})
   216  }
   217  
   218  func (s *ResourcePersistenceSuite) TestStageResourceMissingStoragePath(c *gc.C) {
   219  	res, _ := newPersistenceResource(c, "a-service", "spam")
   220  	p := NewResourcePersistence(s.base)
   221  
   222  	_, err := p.StageResource(res.Resource, "")
   223  
   224  	s.stub.CheckNoCalls(c)
   225  	c.Check(err, gc.ErrorMatches, `missing storage path`)
   226  }
   227  
   228  func (s *ResourcePersistenceSuite) TestStageResourceBadResource(c *gc.C) {
   229  	res, _ := newPersistenceResource(c, "a-service", "spam")
   230  	res.Resource.Timestamp = time.Time{}
   231  	p := NewResourcePersistence(s.base)
   232  
   233  	_, err := p.StageResource(res.Resource, res.storagePath)
   234  
   235  	c.Check(err, jc.Satisfies, errors.IsNotValid)
   236  	c.Check(err, gc.ErrorMatches, `bad resource.*`)
   237  
   238  	s.stub.CheckNoCalls(c)
   239  }
   240  
   241  func (s *ResourcePersistenceSuite) TestSetResourceOkay(c *gc.C) {
   242  	servicename := "a-service"
   243  	res, doc := newPersistenceResource(c, servicename, "spam")
   244  	s.base.ReturnOne = doc
   245  	p := NewResourcePersistence(s.base)
   246  	ignoredErr := errors.New("<never reached>")
   247  	s.stub.SetErrors(nil, nil, nil, nil, ignoredErr)
   248  
   249  	err := p.SetResource(res.Resource)
   250  	c.Assert(err, jc.ErrorIsNil)
   251  
   252  	s.stub.CheckCallNames(c,
   253  		"One",
   254  		"Run",
   255  		"ServiceExistsOps",
   256  		"RunTransaction",
   257  	)
   258  	s.stub.CheckCall(c, 3, "RunTransaction", []txn.Op{{
   259  		C:      "resources",
   260  		Id:     "resource#a-service/spam",
   261  		Assert: txn.DocMissing,
   262  		Insert: &doc,
   263  	}, {
   264  		C:      "service",
   265  		Id:     "a-service",
   266  		Assert: txn.DocExists,
   267  	}})
   268  }
   269  
   270  func (s *ResourcePersistenceSuite) TestSetResourceNotFound(c *gc.C) {
   271  	servicename := "a-service"
   272  	res, doc := newPersistenceResource(c, servicename, "spam")
   273  	s.base.ReturnOne = doc
   274  	expected := doc // a copy
   275  	expected.StoragePath = ""
   276  	p := NewResourcePersistence(s.base)
   277  	notFound := errors.NewNotFound(nil, "")
   278  	ignoredErr := errors.New("<never reached>")
   279  	s.stub.SetErrors(notFound, nil, nil, nil, ignoredErr)
   280  
   281  	err := p.SetResource(res.Resource)
   282  	c.Assert(err, jc.ErrorIsNil)
   283  
   284  	s.stub.CheckCallNames(c,
   285  		"One",
   286  		"Run",
   287  		"ServiceExistsOps",
   288  		"RunTransaction",
   289  	)
   290  	s.stub.CheckCall(c, 3, "RunTransaction", []txn.Op{{
   291  		C:      "resources",
   292  		Id:     "resource#a-service/spam",
   293  		Assert: txn.DocMissing,
   294  		Insert: &expected,
   295  	}, {
   296  		C:      "service",
   297  		Id:     "a-service",
   298  		Assert: txn.DocExists,
   299  	}})
   300  }
   301  
   302  func (s *ResourcePersistenceSuite) TestSetCharmStoreResourceOkay(c *gc.C) {
   303  	lastPolled := time.Now().UTC()
   304  	servicename := "a-service"
   305  	res, doc := newPersistenceResource(c, servicename, "spam")
   306  	expected := doc // a copy
   307  	expected.DocID += "#charmstore"
   308  	expected.Username = ""
   309  	expected.Timestamp = time.Time{}
   310  	expected.StoragePath = ""
   311  	expected.LastPolled = lastPolled
   312  	p := NewResourcePersistence(s.base)
   313  	ignoredErr := errors.New("<never reached>")
   314  	s.stub.SetErrors(nil, nil, nil, ignoredErr)
   315  
   316  	err := p.SetCharmStoreResource(res.ID, res.ServiceID, res.Resource.Resource, lastPolled)
   317  	c.Assert(err, jc.ErrorIsNil)
   318  
   319  	s.stub.CheckCallNames(c,
   320  		"Run",
   321  		"ServiceExistsOps",
   322  		"RunTransaction",
   323  	)
   324  	s.stub.CheckCall(c, 2, "RunTransaction", []txn.Op{{
   325  		C:      "resources",
   326  		Id:     "resource#a-service/spam#charmstore",
   327  		Assert: txn.DocMissing,
   328  		Insert: &expected,
   329  	}, {
   330  		C:      "service",
   331  		Id:     "a-service",
   332  		Assert: txn.DocExists,
   333  	}})
   334  }
   335  
   336  func (s *ResourcePersistenceSuite) TestSetUnitResourceOkay(c *gc.C) {
   337  	servicename := "a-service"
   338  	unitname := "a-service/0"
   339  	res, doc := newPersistenceUnitResource(c, servicename, unitname, "eggs")
   340  	s.base.ReturnOne = doc
   341  	p := NewResourcePersistence(s.base)
   342  	ignoredErr := errors.New("<never reached>")
   343  	s.stub.SetErrors(nil, nil, nil, nil, ignoredErr)
   344  
   345  	err := p.SetUnitResource("a-service/0", res)
   346  	c.Assert(err, jc.ErrorIsNil)
   347  
   348  	s.stub.CheckCallNames(c, "One", "Run", "ServiceExistsOps", "RunTransaction")
   349  	s.stub.CheckCall(c, 3, "RunTransaction", []txn.Op{{
   350  		C:      "resources",
   351  		Id:     "resource#a-service/eggs#unit-a-service/0",
   352  		Assert: txn.DocMissing,
   353  		Insert: &doc,
   354  	}, {
   355  		C:      "service",
   356  		Id:     "a-service",
   357  		Assert: txn.DocExists,
   358  	}})
   359  }
   360  
   361  func (s *ResourcePersistenceSuite) TestSetUnitResourceNotFound(c *gc.C) {
   362  	servicename := "a-service"
   363  	unitname := "a-service/0"
   364  	res, _ := newPersistenceUnitResource(c, servicename, unitname, "eggs")
   365  	p := NewResourcePersistence(s.base)
   366  	notFound := errors.NewNotFound(nil, "")
   367  	s.stub.SetErrors(notFound)
   368  
   369  	err := p.SetUnitResource("a-service/0", res)
   370  
   371  	s.stub.CheckCallNames(c, "One")
   372  	c.Check(err, jc.Satisfies, errors.IsNotFound)
   373  	c.Check(err, gc.ErrorMatches, `resource "eggs" not found`)
   374  }
   375  
   376  func (s *ResourcePersistenceSuite) TestSetUnitResourceExists(c *gc.C) {
   377  	res, doc := newPersistenceUnitResource(c, "a-service", "a-service/0", "spam")
   378  	s.base.ReturnOne = doc
   379  	p := NewResourcePersistence(s.base)
   380  	ignoredErr := errors.New("<never reached>")
   381  	s.stub.SetErrors(nil, nil, nil, txn.ErrAborted, nil, nil, ignoredErr)
   382  
   383  	err := p.SetUnitResource("a-service/0", res)
   384  	c.Assert(err, jc.ErrorIsNil)
   385  
   386  	s.stub.CheckCallNames(c, "One", "Run", "ServiceExistsOps", "RunTransaction", "ServiceExistsOps", "RunTransaction")
   387  	s.stub.CheckCall(c, 3, "RunTransaction", []txn.Op{{
   388  		C:      "resources",
   389  		Id:     "resource#a-service/spam#unit-a-service/0",
   390  		Assert: txn.DocMissing,
   391  		Insert: &doc,
   392  	}, {
   393  		C:      "service",
   394  		Id:     "a-service",
   395  		Assert: txn.DocExists,
   396  	}})
   397  	s.stub.CheckCall(c, 5, "RunTransaction", []txn.Op{{
   398  		C:      "resources",
   399  		Id:     "resource#a-service/spam#unit-a-service/0",
   400  		Assert: txn.DocExists,
   401  		Remove: true,
   402  	}, {
   403  		C:      "resources",
   404  		Id:     "resource#a-service/spam#unit-a-service/0",
   405  		Assert: txn.DocMissing,
   406  		Insert: &doc,
   407  	}, {
   408  		C:      "service",
   409  		Id:     "a-service",
   410  		Assert: txn.DocExists,
   411  	}})
   412  }
   413  
   414  func (s *ResourcePersistenceSuite) TestSetUnitResourceBadResource(c *gc.C) {
   415  	res, doc := newPersistenceUnitResource(c, "a-service", "a-service/0", "spam")
   416  	s.base.ReturnOne = doc
   417  	res.Timestamp = time.Time{}
   418  	p := NewResourcePersistence(s.base)
   419  
   420  	err := p.SetUnitResource("a-service/0", res)
   421  
   422  	c.Check(err, jc.Satisfies, errors.IsNotValid)
   423  	c.Check(err, gc.ErrorMatches, `bad resource.*`)
   424  
   425  	s.stub.CheckCallNames(c, "One")
   426  }
   427  
   428  func (s *ResourcePersistenceSuite) TestSetUnitResourceProgress(c *gc.C) {
   429  	servicename := "a-service"
   430  	unitname := "a-service/0"
   431  	res, doc := newPersistenceUnitResource(c, servicename, unitname, "eggs")
   432  	s.base.ReturnOne = doc
   433  	pendingID := "<a pending ID>"
   434  	res.PendingID = pendingID
   435  	expected := doc // a copy
   436  	expected.PendingID = pendingID
   437  	var progress int64 = 2
   438  	expected.DownloadProgress = &progress
   439  	p := NewResourcePersistence(s.base)
   440  	ignoredErr := errors.New("<never reached>")
   441  	s.stub.SetErrors(nil, nil, nil, nil, ignoredErr)
   442  
   443  	err := p.SetUnitResourceProgress("a-service/0", res, progress)
   444  	c.Assert(err, jc.ErrorIsNil)
   445  
   446  	s.stub.CheckCallNames(c, "One", "Run", "ServiceExistsOps", "RunTransaction")
   447  	s.stub.CheckCall(c, 3, "RunTransaction", []txn.Op{{
   448  		C:      "resources",
   449  		Id:     "resource#a-service/eggs#unit-a-service/0",
   450  		Assert: txn.DocMissing,
   451  		Insert: &expected,
   452  	}, {
   453  		C:      "service",
   454  		Id:     "a-service",
   455  		Assert: txn.DocExists,
   456  	}})
   457  }
   458  
   459  func (s *ResourcePersistenceSuite) TestNewResourcePendingResourceOpsExists(c *gc.C) {
   460  	pendingID := "some-unique-ID-001"
   461  	stored, expected := newPersistenceResource(c, "a-service", "spam")
   462  	stored.PendingID = pendingID
   463  	doc := expected // a copy
   464  	doc.DocID = pendingResourceID(stored.ID, pendingID)
   465  	doc.PendingID = pendingID
   466  	s.base.ReturnOne = doc
   467  	p := NewResourcePersistence(s.base)
   468  
   469  	lastPolled := time.Now().UTC().Round(time.Second)
   470  
   471  	ops, err := p.NewResolvePendingResourceOps(stored.ID, stored.PendingID)
   472  	c.Assert(err, jc.ErrorIsNil)
   473  
   474  	csresourceDoc := expected
   475  	csresourceDoc.DocID = "resource#a-service/spam#charmstore"
   476  	csresourceDoc.Username = ""
   477  	csresourceDoc.Timestamp = time.Time{}
   478  	csresourceDoc.StoragePath = ""
   479  	csresourceDoc.LastPolled = lastPolled
   480  
   481  	res := ops[4].Insert.(*resourceDoc)
   482  	res.LastPolled = res.LastPolled.Round(time.Second)
   483  
   484  	s.stub.CheckCallNames(c, "One", "One")
   485  	s.stub.CheckCall(c, 0, "One", "resources", "resource#a-service/spam#pending-some-unique-ID-001", &doc)
   486  	c.Check(ops, jc.DeepEquals, []txn.Op{
   487  		{
   488  			C:      "resources",
   489  			Id:     doc.DocID,
   490  			Assert: txn.DocExists,
   491  			Remove: true,
   492  		}, {
   493  			C:      "resources",
   494  			Id:     expected.DocID,
   495  			Assert: txn.DocExists,
   496  			Remove: true,
   497  		}, {
   498  			C:      "resources",
   499  			Id:     expected.DocID,
   500  			Assert: txn.DocMissing,
   501  			Insert: &expected,
   502  		},
   503  		{
   504  			C:      "resources",
   505  			Id:     csresourceDoc.DocID,
   506  			Assert: txn.DocExists,
   507  			Remove: true,
   508  		},
   509  		{
   510  			C:      "resources",
   511  			Id:     csresourceDoc.DocID,
   512  			Assert: txn.DocMissing,
   513  			Insert: &csresourceDoc,
   514  		},
   515  	})
   516  }
   517  
   518  func (s *ResourcePersistenceSuite) TestNewResourcePendingResourceOpsNotFound(c *gc.C) {
   519  	pendingID := "some-unique-ID-001"
   520  	stored, expected := newPersistenceResource(c, "a-service", "spam")
   521  	stored.PendingID = pendingID
   522  	doc := expected // a copy
   523  	doc.DocID = pendingResourceID(stored.ID, pendingID)
   524  	doc.PendingID = pendingID
   525  	s.base.ReturnOne = doc
   526  	notFound := errors.NewNotFound(nil, "")
   527  	s.stub.SetErrors(nil, notFound)
   528  	p := NewResourcePersistence(s.base)
   529  
   530  	lastPolled := time.Now().UTC().Round(time.Second)
   531  	ops, err := p.NewResolvePendingResourceOps(stored.ID, stored.PendingID)
   532  	c.Assert(err, jc.ErrorIsNil)
   533  
   534  	s.stub.CheckCallNames(c, "One", "One")
   535  	s.stub.CheckCall(c, 0, "One", "resources", "resource#a-service/spam#pending-some-unique-ID-001", &doc)
   536  
   537  	csresourceDoc := expected
   538  	csresourceDoc.DocID = "resource#a-service/spam#charmstore"
   539  	csresourceDoc.Username = ""
   540  	csresourceDoc.Timestamp = time.Time{}
   541  	csresourceDoc.StoragePath = ""
   542  	csresourceDoc.LastPolled = lastPolled
   543  
   544  	res := ops[2].Insert.(*resourceDoc)
   545  	res.LastPolled = res.LastPolled.Round(time.Second)
   546  
   547  	c.Check(ops, jc.DeepEquals, []txn.Op{
   548  		{
   549  			C:      "resources",
   550  			Id:     doc.DocID,
   551  			Assert: txn.DocExists,
   552  			Remove: true,
   553  		}, {
   554  			C:      "resources",
   555  			Id:     expected.DocID,
   556  			Assert: txn.DocMissing,
   557  			Insert: &expected,
   558  		},
   559  		{
   560  			C:      "resources",
   561  			Id:     csresourceDoc.DocID,
   562  			Assert: txn.DocMissing,
   563  			Insert: &csresourceDoc,
   564  		},
   565  	})
   566  }
   567  
   568  func newPersistenceUnitResources(c *gc.C, serviceID, unitID string, resources []resource.Resource) ([]resource.Resource, []resourceDoc) {
   569  	var unitResources []resource.Resource
   570  	var docs []resourceDoc
   571  	for _, res := range resources {
   572  		res, doc := newPersistenceUnitResource(c, serviceID, unitID, res.Name)
   573  		unitResources = append(unitResources, res)
   574  		docs = append(docs, doc)
   575  	}
   576  	return unitResources, docs
   577  }
   578  
   579  func newPersistenceUnitResource(c *gc.C, serviceID, unitID, name string) (resource.Resource, resourceDoc) {
   580  	res, doc := newPersistenceResource(c, serviceID, name)
   581  	doc.DocID += "#unit-" + unitID
   582  	doc.UnitID = unitID
   583  	return res.Resource, doc
   584  }
   585  
   586  func newPersistenceResources(c *gc.C, serviceID string, names ...string) (resource.ServiceResources, []resourceDoc) {
   587  	var svcResources resource.ServiceResources
   588  	var docs []resourceDoc
   589  	for _, name := range names {
   590  		res, doc := newPersistenceResource(c, serviceID, name)
   591  		svcResources.Resources = append(svcResources.Resources, res.Resource)
   592  		svcResources.CharmStoreResources = append(svcResources.CharmStoreResources, res.Resource.Resource)
   593  		docs = append(docs, doc)
   594  		csDoc := doc // a copy
   595  		csDoc.DocID += "#charmstore"
   596  		csDoc.Username = ""
   597  		csDoc.Timestamp = time.Time{}
   598  		csDoc.StoragePath = ""
   599  		csDoc.LastPolled = time.Now().UTC()
   600  		docs = append(docs, csDoc)
   601  	}
   602  	return svcResources, docs
   603  }
   604  
   605  func newPersistenceResource(c *gc.C, serviceID, name string) (storedResource, resourceDoc) {
   606  	content := name
   607  	opened := resourcetesting.NewResource(c, nil, name, serviceID, content)
   608  	res := opened.Resource
   609  
   610  	stored := storedResource{
   611  		Resource:    res,
   612  		storagePath: "service-" + serviceID + "/resources/" + name,
   613  	}
   614  
   615  	doc := resourceDoc{
   616  		DocID:     "resource#" + res.ID,
   617  		ID:        res.ID,
   618  		ServiceID: res.ServiceID,
   619  
   620  		Name:        res.Name,
   621  		Type:        res.Type.String(),
   622  		Path:        res.Path,
   623  		Description: res.Description,
   624  
   625  		Origin:      res.Origin.String(),
   626  		Revision:    res.Revision,
   627  		Fingerprint: res.Fingerprint.Bytes(),
   628  		Size:        res.Size,
   629  
   630  		Username:  res.Username,
   631  		Timestamp: res.Timestamp,
   632  
   633  		StoragePath: stored.storagePath,
   634  	}
   635  
   636  	return stored, doc
   637  }
   638  
   639  func checkResources(c *gc.C, resources, expected resource.ServiceResources) {
   640  	resMap := make(map[string]resource.Resource)
   641  	for _, res := range resources.Resources {
   642  		resMap[res.Name] = res
   643  	}
   644  	expMap := make(map[string]resource.Resource)
   645  	for _, res := range expected.Resources {
   646  		expMap[res.Name] = res
   647  	}
   648  	c.Check(resMap, jc.DeepEquals, expMap)
   649  }
   650  
   651  func checkBasicResources(c *gc.C, resources, expected []resource.Resource) {
   652  	resMap := make(map[string]resource.Resource)
   653  	for _, res := range resources {
   654  		resMap[res.ID] = res
   655  	}
   656  	expMap := make(map[string]resource.Resource)
   657  	for _, res := range expected {
   658  		expMap[res.ID] = res
   659  	}
   660  	c.Check(resMap, jc.DeepEquals, expMap)
   661  }