github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/charm_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/juju/errors"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/utils"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/juju/charm.v6-unstable"
    17  	"gopkg.in/macaroon.v1"
    18  	"gopkg.in/mgo.v2"
    19  
    20  	"github.com/juju/juju/state"
    21  	"github.com/juju/juju/state/storage"
    22  	"github.com/juju/juju/testcharms"
    23  	"github.com/juju/juju/testing/factory"
    24  )
    25  
    26  type CharmSuite struct {
    27  	ConnSuite
    28  	charm *state.Charm
    29  	curl  *charm.URL
    30  }
    31  
    32  var _ = gc.Suite(&CharmSuite{})
    33  
    34  func (s *CharmSuite) SetUpTest(c *gc.C) {
    35  	s.ConnSuite.SetUpTest(c)
    36  	s.charm = s.AddTestingCharm(c, "dummy")
    37  	s.curl = s.charm.URL()
    38  }
    39  
    40  func (s *CharmSuite) destroy(c *gc.C) {
    41  	err := s.charm.Destroy()
    42  	c.Assert(err, jc.ErrorIsNil)
    43  }
    44  
    45  func (s *CharmSuite) remove(c *gc.C) {
    46  	s.destroy(c)
    47  	err := s.charm.Remove()
    48  	c.Assert(err, jc.ErrorIsNil)
    49  }
    50  
    51  func (s *CharmSuite) checkRemoved(c *gc.C) {
    52  	_, err := s.State.Charm(s.curl)
    53  	c.Check(err, gc.ErrorMatches, `charm ".*" not found`)
    54  	c.Check(err, jc.Satisfies, errors.IsNotFound)
    55  }
    56  
    57  func (s *CharmSuite) TestAliveCharm(c *gc.C) {
    58  	s.testCharm(c)
    59  }
    60  
    61  func (s *CharmSuite) TestDyingCharm(c *gc.C) {
    62  	s.destroy(c)
    63  	s.testCharm(c)
    64  }
    65  
    66  func (s *CharmSuite) testCharm(c *gc.C) {
    67  	dummy, err := s.State.Charm(s.curl)
    68  	c.Assert(err, jc.ErrorIsNil)
    69  	c.Assert(dummy.URL().String(), gc.Equals, s.curl.String())
    70  	c.Assert(dummy.Revision(), gc.Equals, 1)
    71  	c.Assert(dummy.StoragePath(), gc.Equals, "dummy-path")
    72  	c.Assert(dummy.BundleSha256(), gc.Equals, "quantal-dummy-1-sha256")
    73  	c.Assert(dummy.IsUploaded(), jc.IsTrue)
    74  	meta := dummy.Meta()
    75  	c.Assert(meta.Name, gc.Equals, "dummy")
    76  	config := dummy.Config()
    77  	c.Assert(config.Options["title"], gc.Equals,
    78  		charm.Option{
    79  			Default:     "My Title",
    80  			Description: "A descriptive title used for the application.",
    81  			Type:        "string",
    82  		},
    83  	)
    84  	actions := dummy.Actions()
    85  	c.Assert(actions, gc.NotNil)
    86  	c.Assert(actions.ActionSpecs, gc.Not(gc.HasLen), 0)
    87  	c.Assert(actions.ActionSpecs["snapshot"], gc.NotNil)
    88  	c.Assert(actions.ActionSpecs["snapshot"].Params, gc.Not(gc.HasLen), 0)
    89  	c.Assert(actions.ActionSpecs["snapshot"], gc.DeepEquals,
    90  		charm.ActionSpec{
    91  			Description: "Take a snapshot of the database.",
    92  			Params: map[string]interface{}{
    93  				"type":        "object",
    94  				"title":       "snapshot",
    95  				"description": "Take a snapshot of the database.",
    96  				"properties": map[string]interface{}{
    97  					"outfile": map[string]interface{}{
    98  						"description": "The file to write out to.",
    99  						"type":        "string",
   100  						"default":     "foo.bz2",
   101  					},
   102  				},
   103  			},
   104  		})
   105  }
   106  
   107  func (s *CharmSuite) TestRemovedCharmNotFound(c *gc.C) {
   108  	s.remove(c)
   109  	s.checkRemoved(c)
   110  }
   111  
   112  func (s *CharmSuite) TestRemovedCharmNotListed(c *gc.C) {
   113  	s.remove(c)
   114  	charms, err := s.State.AllCharms()
   115  	c.Check(err, jc.ErrorIsNil)
   116  	c.Check(charms, gc.HasLen, 0)
   117  }
   118  
   119  func (s *CharmSuite) TestRemoveWithoutDestroy(c *gc.C) {
   120  	err := s.charm.Remove()
   121  	c.Assert(err, gc.ErrorMatches, "still alive")
   122  }
   123  
   124  func (s *CharmSuite) TestCharmNotFound(c *gc.C) {
   125  	curl := charm.MustParseURL("local:anotherseries/dummy-1")
   126  	_, err := s.State.Charm(curl)
   127  	c.Assert(err, gc.ErrorMatches, `charm "local:anotherseries/dummy-1" not found`)
   128  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   129  }
   130  
   131  func (s *CharmSuite) dummyCharm(c *gc.C, curlOverride string) state.CharmInfo {
   132  	info := state.CharmInfo{
   133  		Charm:       testcharms.Repo.CharmDir("dummy"),
   134  		StoragePath: "dummy-1",
   135  		SHA256:      "dummy-1-sha256",
   136  	}
   137  	if curlOverride != "" {
   138  		info.ID = charm.MustParseURL(curlOverride)
   139  	} else {
   140  		info.ID = charm.MustParseURL(
   141  			fmt.Sprintf("local:quantal/%s-%d", info.Charm.Meta().Name, info.Charm.Revision()),
   142  		)
   143  	}
   144  	return info
   145  }
   146  
   147  func (s *CharmSuite) TestDestroyStoreCharm(c *gc.C) {
   148  	info := s.dummyCharm(c, "cs:precise/dummy-2")
   149  	sch, err := s.State.AddCharm(info)
   150  	c.Assert(err, jc.ErrorIsNil)
   151  	err = sch.Destroy()
   152  	c.Assert(err, gc.ErrorMatches, "cannot destroy non-local charms")
   153  }
   154  
   155  func (s *CharmSuite) TestRemoveDeletesStorage(c *gc.C) {
   156  	// We normally don't actually set up charm storage in state
   157  	// tests, but we need it here.
   158  	path := s.charm.StoragePath()
   159  	stor := storage.NewStorage(s.State.ModelUUID(), s.State.MongoSession())
   160  	err := stor.Put(path, strings.NewReader("abc"), 3)
   161  	c.Assert(err, jc.ErrorIsNil)
   162  
   163  	s.destroy(c)
   164  	closer, _, err := stor.Get(path)
   165  	c.Assert(err, jc.ErrorIsNil)
   166  	closer.Close()
   167  
   168  	s.remove(c)
   169  	_, _, err = stor.Get(path)
   170  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   171  }
   172  
   173  func (s *CharmSuite) TestReferenceDyingCharm(c *gc.C) {
   174  
   175  	s.destroy(c)
   176  
   177  	args := state.AddApplicationArgs{
   178  		Name:  "blah",
   179  		Charm: s.charm,
   180  	}
   181  	_, err := s.State.AddApplication(args)
   182  	c.Check(err, gc.ErrorMatches, `cannot add application "blah": charm: not found or not alive`)
   183  }
   184  
   185  func (s *CharmSuite) TestReferenceDyingCharmRace(c *gc.C) {
   186  
   187  	defer state.SetBeforeHooks(c, s.State, func() {
   188  		s.destroy(c)
   189  	}).Check()
   190  
   191  	args := state.AddApplicationArgs{
   192  		Name:  "blah",
   193  		Charm: s.charm,
   194  	}
   195  	_, err := s.State.AddApplication(args)
   196  	// bad message: see lp:1621754. should match
   197  	// TestReferenceDyingCharm above.
   198  	c.Check(err, gc.ErrorMatches, `cannot add application "blah": application already exists`)
   199  }
   200  
   201  func (s *CharmSuite) TestDestroyReferencedCharm(c *gc.C) {
   202  	s.Factory.MakeApplication(c, &factory.ApplicationParams{
   203  		Charm: s.charm,
   204  	})
   205  
   206  	err := s.charm.Destroy()
   207  	c.Check(err, gc.ErrorMatches, "charm in use")
   208  }
   209  
   210  func (s *CharmSuite) TestDestroyReferencedCharmRace(c *gc.C) {
   211  
   212  	defer state.SetBeforeHooks(c, s.State, func() {
   213  		s.Factory.MakeApplication(c, &factory.ApplicationParams{
   214  			Charm: s.charm,
   215  		})
   216  	}).Check()
   217  
   218  	err := s.charm.Destroy()
   219  	c.Check(err, gc.ErrorMatches, "charm in use")
   220  }
   221  
   222  func (s *CharmSuite) TestDestroyUnreferencedCharm(c *gc.C) {
   223  	app := s.Factory.MakeApplication(c, &factory.ApplicationParams{
   224  		Charm: s.charm,
   225  	})
   226  	err := app.Destroy()
   227  	c.Assert(err, jc.ErrorIsNil)
   228  
   229  	err = s.charm.Destroy()
   230  	c.Assert(err, jc.ErrorIsNil)
   231  }
   232  
   233  func (s *CharmSuite) TestDestroyUnitReferencedCharm(c *gc.C) {
   234  	app := s.Factory.MakeApplication(c, &factory.ApplicationParams{
   235  		Charm: s.charm,
   236  	})
   237  	unit := s.Factory.MakeUnit(c, &factory.UnitParams{
   238  		Application: app,
   239  		SetCharmURL: true,
   240  	})
   241  
   242  	// set app charm to something different
   243  	info := s.dummyCharm(c, "cs:quantal/dummy-2")
   244  	newCh, err := s.State.AddCharm(info)
   245  	c.Assert(err, jc.ErrorIsNil)
   246  	err = app.SetCharm(state.SetCharmConfig{Charm: newCh})
   247  	c.Assert(err, jc.ErrorIsNil)
   248  
   249  	// unit should still reference original charm until updated
   250  	err = s.charm.Destroy()
   251  	c.Assert(err, gc.ErrorMatches, "charm in use")
   252  	err = unit.SetCharmURL(info.ID)
   253  	c.Assert(err, jc.ErrorIsNil)
   254  	err = s.charm.Destroy()
   255  	c.Assert(err, jc.ErrorIsNil)
   256  }
   257  
   258  func (s *CharmSuite) TestDestroyFinalUnitReference(c *gc.C) {
   259  	app := s.Factory.MakeApplication(c, &factory.ApplicationParams{
   260  		Charm: s.charm,
   261  	})
   262  	unit := s.Factory.MakeUnit(c, &factory.UnitParams{
   263  		Application: app,
   264  		SetCharmURL: true,
   265  	})
   266  
   267  	err := app.Destroy()
   268  	c.Assert(err, jc.ErrorIsNil)
   269  	removeUnit(c, unit)
   270  
   271  	assertCleanupCount(c, s.State, 1)
   272  	s.checkRemoved(c)
   273  }
   274  
   275  func (s *CharmSuite) TestAddCharm(c *gc.C) {
   276  	// Check that adding charms from scratch works correctly.
   277  	info := s.dummyCharm(c, "")
   278  	dummy, err := s.State.AddCharm(info)
   279  	c.Assert(err, jc.ErrorIsNil)
   280  	c.Assert(dummy.URL().String(), gc.Equals, info.ID.String())
   281  
   282  	doc := state.CharmDoc{}
   283  	err = s.charms.FindId(state.DocID(s.State, info.ID.String())).One(&doc)
   284  	c.Assert(err, jc.ErrorIsNil)
   285  	c.Logf("%#v", doc)
   286  	c.Assert(doc.URL, gc.DeepEquals, info.ID)
   287  }
   288  
   289  func (s *CharmSuite) TestAddCharmWithAuth(c *gc.C) {
   290  	// Check that adding charms from scratch works correctly.
   291  	info := s.dummyCharm(c, "")
   292  	m, err := macaroon.New([]byte("rootkey"), "id", "loc")
   293  	c.Assert(err, jc.ErrorIsNil)
   294  	info.Macaroon = macaroon.Slice{m}
   295  	dummy, err := s.State.AddCharm(info)
   296  	c.Assert(err, jc.ErrorIsNil)
   297  	ms, err := dummy.Macaroon()
   298  	c.Assert(err, jc.ErrorIsNil)
   299  	c.Assert(ms, gc.DeepEquals, info.Macaroon)
   300  }
   301  
   302  func (s *CharmSuite) TestAddCharmUpdatesPlaceholder(c *gc.C) {
   303  	// Check that adding charms updates any existing placeholder charm
   304  	// with the same URL.
   305  	ch := testcharms.Repo.CharmDir("dummy")
   306  
   307  	// Add a placeholder charm.
   308  	curl := charm.MustParseURL("cs:quantal/dummy-1")
   309  	err := s.State.AddStoreCharmPlaceholder(curl)
   310  	c.Assert(err, jc.ErrorIsNil)
   311  
   312  	// Add a deployed charm.
   313  	info := state.CharmInfo{
   314  		Charm:       ch,
   315  		ID:          curl,
   316  		StoragePath: "dummy-1",
   317  		SHA256:      "dummy-1-sha256",
   318  	}
   319  	dummy, err := s.State.AddCharm(info)
   320  	c.Assert(err, jc.ErrorIsNil)
   321  	c.Assert(dummy.URL().String(), gc.Equals, curl.String())
   322  
   323  	// Charm doc has been updated.
   324  	var docs []state.CharmDoc
   325  	err = s.charms.FindId(state.DocID(s.State, curl.String())).All(&docs)
   326  	c.Assert(err, jc.ErrorIsNil)
   327  	c.Assert(docs, gc.HasLen, 1)
   328  	c.Assert(docs[0].URL, gc.DeepEquals, curl)
   329  	c.Assert(docs[0].StoragePath, gc.DeepEquals, info.StoragePath)
   330  
   331  	// No more placeholder charm.
   332  	_, err = s.State.LatestPlaceholderCharm(curl)
   333  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   334  }
   335  
   336  func (s *CharmSuite) assertPendingCharmExists(c *gc.C, curl *charm.URL) {
   337  	// Find charm directly and verify only the charm URL and
   338  	// PendingUpload are set.
   339  	doc := state.CharmDoc{}
   340  	err := s.charms.FindId(state.DocID(s.State, curl.String())).One(&doc)
   341  	c.Assert(err, jc.ErrorIsNil)
   342  	c.Logf("%#v", doc)
   343  	c.Assert(doc.URL, gc.DeepEquals, curl)
   344  	c.Assert(doc.PendingUpload, jc.IsTrue)
   345  	c.Assert(doc.Placeholder, jc.IsFalse)
   346  	c.Assert(doc.Meta, gc.IsNil)
   347  	c.Assert(doc.Config, gc.IsNil)
   348  	c.Assert(doc.StoragePath, gc.Equals, "")
   349  	c.Assert(doc.BundleSha256, gc.Equals, "")
   350  
   351  	// Make sure we can't find it with st.Charm().
   352  	_, err = s.State.Charm(curl)
   353  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   354  }
   355  
   356  func (s *CharmSuite) TestPrepareLocalCharmUpload(c *gc.C) {
   357  	// First test the sanity checks.
   358  	curl, err := s.State.PrepareLocalCharmUpload(charm.MustParseURL("local:quantal/dummy"))
   359  	c.Assert(err, gc.ErrorMatches, "expected charm URL with revision, got .*")
   360  	c.Assert(curl, gc.IsNil)
   361  	curl, err = s.State.PrepareLocalCharmUpload(charm.MustParseURL("cs:quantal/dummy"))
   362  	c.Assert(err, gc.ErrorMatches, "expected charm URL with local schema, got .*")
   363  	c.Assert(curl, gc.IsNil)
   364  
   365  	// No charm in state, so the call should respect given revision.
   366  	testCurl := charm.MustParseURL("local:quantal/missing-123")
   367  	curl, err = s.State.PrepareLocalCharmUpload(testCurl)
   368  	c.Assert(err, jc.ErrorIsNil)
   369  	c.Assert(curl, gc.DeepEquals, testCurl)
   370  	s.assertPendingCharmExists(c, curl)
   371  
   372  	// Try adding it again with the same revision and ensure it gets bumped.
   373  	curl, err = s.State.PrepareLocalCharmUpload(curl)
   374  	c.Assert(err, jc.ErrorIsNil)
   375  	c.Assert(curl.Revision, gc.Equals, 124)
   376  
   377  	// Also ensure the revision cannot decrease.
   378  	curl, err = s.State.PrepareLocalCharmUpload(curl.WithRevision(42))
   379  	c.Assert(err, jc.ErrorIsNil)
   380  	c.Assert(curl.Revision, gc.Equals, 125)
   381  
   382  	// Check the given revision is respected.
   383  	curl, err = s.State.PrepareLocalCharmUpload(curl.WithRevision(1234))
   384  	c.Assert(err, jc.ErrorIsNil)
   385  	c.Assert(curl.Revision, gc.Equals, 1234)
   386  }
   387  
   388  func (s *CharmSuite) TestPrepareLocalCharmUploadRemoved(c *gc.C) {
   389  	// Remove the fixture charm and try to re-add it; it gets a new
   390  	// revision.
   391  	s.remove(c)
   392  	curl, err := s.State.PrepareLocalCharmUpload(s.curl)
   393  	c.Assert(err, jc.ErrorIsNil)
   394  	c.Assert(curl.Revision, gc.Equals, s.curl.Revision+1)
   395  }
   396  
   397  func (s *CharmSuite) TestPrepareStoreCharmUpload(c *gc.C) {
   398  	// First test the sanity checks.
   399  	sch, err := s.State.PrepareStoreCharmUpload(charm.MustParseURL("cs:quantal/dummy"))
   400  	c.Assert(err, gc.ErrorMatches, "expected charm URL with revision, got .*")
   401  	c.Assert(sch, gc.IsNil)
   402  	sch, err = s.State.PrepareStoreCharmUpload(charm.MustParseURL("local:quantal/dummy"))
   403  	c.Assert(err, gc.ErrorMatches, "expected charm URL with cs schema, got .*")
   404  	c.Assert(sch, gc.IsNil)
   405  
   406  	// No charm in state, so the call should respect given revision.
   407  	testCurl := charm.MustParseURL("cs:quantal/missing-123")
   408  	sch, err = s.State.PrepareStoreCharmUpload(testCurl)
   409  	c.Assert(err, jc.ErrorIsNil)
   410  	c.Assert(sch.URL(), gc.DeepEquals, testCurl)
   411  	c.Assert(sch.IsUploaded(), jc.IsFalse)
   412  
   413  	s.assertPendingCharmExists(c, sch.URL())
   414  
   415  	// Try adding it again with the same revision and ensure we get the same document.
   416  	schCopy, err := s.State.PrepareStoreCharmUpload(testCurl)
   417  	c.Assert(err, jc.ErrorIsNil)
   418  	c.Assert(sch, jc.DeepEquals, schCopy)
   419  
   420  	// Now add a charm and try again - we should get the same result
   421  	// as with AddCharm.
   422  	info := s.dummyCharm(c, "cs:precise/dummy-2")
   423  	sch, err = s.State.AddCharm(info)
   424  	c.Assert(err, jc.ErrorIsNil)
   425  	schCopy, err = s.State.PrepareStoreCharmUpload(info.ID)
   426  	c.Assert(err, jc.ErrorIsNil)
   427  	c.Assert(sch, jc.DeepEquals, schCopy)
   428  }
   429  
   430  func (s *CharmSuite) TestUpdateUploadedCharm(c *gc.C) {
   431  	info := s.dummyCharm(c, "")
   432  	_, err := s.State.AddCharm(info)
   433  	c.Assert(err, jc.ErrorIsNil)
   434  
   435  	// Test with already uploaded and a missing charms.
   436  	sch, err := s.State.UpdateUploadedCharm(info)
   437  	c.Assert(err, gc.ErrorMatches, fmt.Sprintf("charm %q already uploaded", info.ID))
   438  	c.Assert(sch, gc.IsNil)
   439  	info.ID = charm.MustParseURL("local:quantal/missing-1")
   440  	info.SHA256 = "missing"
   441  	sch, err = s.State.UpdateUploadedCharm(info)
   442  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   443  	c.Assert(sch, gc.IsNil)
   444  
   445  	// Test with with an uploaded local charm.
   446  	_, err = s.State.PrepareLocalCharmUpload(info.ID)
   447  	c.Assert(err, jc.ErrorIsNil)
   448  
   449  	m, err := macaroon.New([]byte("rootkey"), "id", "loc")
   450  	c.Assert(err, jc.ErrorIsNil)
   451  	info.Macaroon = macaroon.Slice{m}
   452  	c.Assert(err, jc.ErrorIsNil)
   453  	sch, err = s.State.UpdateUploadedCharm(info)
   454  	c.Assert(err, jc.ErrorIsNil)
   455  	c.Assert(sch.URL(), gc.DeepEquals, info.ID)
   456  	c.Assert(sch.Revision(), gc.Equals, info.ID.Revision)
   457  	c.Assert(sch.IsUploaded(), jc.IsTrue)
   458  	c.Assert(sch.IsPlaceholder(), jc.IsFalse)
   459  	c.Assert(sch.Meta(), gc.DeepEquals, info.Charm.Meta())
   460  	c.Assert(sch.Config(), gc.DeepEquals, info.Charm.Config())
   461  	c.Assert(sch.StoragePath(), gc.DeepEquals, info.StoragePath)
   462  	c.Assert(sch.BundleSha256(), gc.Equals, "missing")
   463  	ms, err := sch.Macaroon()
   464  	c.Assert(err, jc.ErrorIsNil)
   465  	c.Assert(ms, gc.DeepEquals, info.Macaroon)
   466  }
   467  
   468  func (s *CharmSuite) TestUpdateUploadedCharmEscapesSpecialCharsInConfig(c *gc.C) {
   469  	// Make sure when we have mongodb special characters like "$" and
   470  	// "." in the name of any charm config option, we do proper
   471  	// escaping before storing them and unescaping after loading. See
   472  	// also http://pad.lv/1308146.
   473  
   474  	// Clone the dummy charm and change the config.
   475  	configWithProblematicKeys := []byte(`
   476  options:
   477    $bad.key: {default: bad, description: bad, type: string}
   478    not.ok.key: {description: not ok, type: int}
   479    valid-key: {description: all good, type: boolean}
   480    still$bad.: {description: not good, type: float}
   481    $.$: {description: awful, type: string}
   482    ...: {description: oh boy, type: int}
   483    just$: {description: no no, type: float}
   484  `[1:])
   485  	chDir := testcharms.Repo.ClonedDirPath(c.MkDir(), "dummy")
   486  	err := utils.AtomicWriteFile(
   487  		filepath.Join(chDir, "config.yaml"),
   488  		configWithProblematicKeys,
   489  		0666,
   490  	)
   491  	c.Assert(err, jc.ErrorIsNil)
   492  	ch, err := charm.ReadCharmDir(chDir)
   493  	c.Assert(err, jc.ErrorIsNil)
   494  	missingCurl := charm.MustParseURL("local:quantal/missing-1")
   495  	storagePath := "dummy-1"
   496  
   497  	preparedCurl, err := s.State.PrepareLocalCharmUpload(missingCurl)
   498  	c.Assert(err, jc.ErrorIsNil)
   499  	info := state.CharmInfo{
   500  		Charm:       ch,
   501  		ID:          preparedCurl,
   502  		StoragePath: "dummy-1",
   503  		SHA256:      "missing",
   504  	}
   505  	sch, err := s.State.UpdateUploadedCharm(info)
   506  	c.Assert(err, jc.ErrorIsNil)
   507  	c.Assert(sch.URL(), gc.DeepEquals, missingCurl)
   508  	c.Assert(sch.Revision(), gc.Equals, missingCurl.Revision)
   509  	c.Assert(sch.IsUploaded(), jc.IsTrue)
   510  	c.Assert(sch.IsPlaceholder(), jc.IsFalse)
   511  	c.Assert(sch.Meta(), gc.DeepEquals, ch.Meta())
   512  	c.Assert(sch.Config(), gc.DeepEquals, ch.Config())
   513  	c.Assert(sch.StoragePath(), gc.DeepEquals, storagePath)
   514  	c.Assert(sch.BundleSha256(), gc.Equals, "missing")
   515  }
   516  
   517  func (s *CharmSuite) assertPlaceholderCharmExists(c *gc.C, curl *charm.URL) {
   518  	// Find charm directly and verify only the charm URL and
   519  	// Placeholder are set.
   520  	doc := state.CharmDoc{}
   521  	err := s.charms.FindId(state.DocID(s.State, curl.String())).One(&doc)
   522  	c.Assert(err, jc.ErrorIsNil)
   523  	c.Assert(doc.URL, gc.DeepEquals, curl)
   524  	c.Assert(doc.PendingUpload, jc.IsFalse)
   525  	c.Assert(doc.Placeholder, jc.IsTrue)
   526  	c.Assert(doc.Meta, gc.IsNil)
   527  	c.Assert(doc.Config, gc.IsNil)
   528  	c.Assert(doc.StoragePath, gc.Equals, "")
   529  	c.Assert(doc.BundleSha256, gc.Equals, "")
   530  
   531  	// Make sure we can't find it with st.Charm().
   532  	_, err = s.State.Charm(curl)
   533  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   534  }
   535  
   536  func (s *CharmSuite) TestLatestPlaceholderCharm(c *gc.C) {
   537  	// Add a deployed charm
   538  	info := s.dummyCharm(c, "cs:quantal/dummy-1")
   539  	_, err := s.State.AddCharm(info)
   540  	c.Assert(err, jc.ErrorIsNil)
   541  
   542  	// Deployed charm not found.
   543  	_, err = s.State.LatestPlaceholderCharm(info.ID)
   544  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   545  
   546  	// Add a charm reference
   547  	curl2 := charm.MustParseURL("cs:quantal/dummy-2")
   548  	err = s.State.AddStoreCharmPlaceholder(curl2)
   549  	c.Assert(err, jc.ErrorIsNil)
   550  	s.assertPlaceholderCharmExists(c, curl2)
   551  
   552  	// Use a URL with an arbitrary rev to search.
   553  	curl := charm.MustParseURL("cs:quantal/dummy-23")
   554  	pending, err := s.State.LatestPlaceholderCharm(curl)
   555  	c.Assert(err, jc.ErrorIsNil)
   556  	c.Assert(pending.URL(), gc.DeepEquals, curl2)
   557  	c.Assert(pending.IsPlaceholder(), jc.IsTrue)
   558  	c.Assert(pending.Meta(), gc.IsNil)
   559  	c.Assert(pending.Config(), gc.IsNil)
   560  	c.Assert(pending.StoragePath(), gc.Equals, "")
   561  	c.Assert(pending.BundleSha256(), gc.Equals, "")
   562  }
   563  
   564  func (s *CharmSuite) TestAddStoreCharmPlaceholderErrors(c *gc.C) {
   565  	ch := testcharms.Repo.CharmDir("dummy")
   566  	curl := charm.MustParseURL(
   567  		fmt.Sprintf("local:quantal/%s-%d", ch.Meta().Name, ch.Revision()),
   568  	)
   569  	err := s.State.AddStoreCharmPlaceholder(curl)
   570  	c.Assert(err, gc.ErrorMatches, "expected charm URL with cs schema, got .*")
   571  
   572  	curl = charm.MustParseURL("cs:quantal/dummy")
   573  	err = s.State.AddStoreCharmPlaceholder(curl)
   574  	c.Assert(err, gc.ErrorMatches, "expected charm URL with revision, got .*")
   575  }
   576  
   577  func (s *CharmSuite) TestAddStoreCharmPlaceholder(c *gc.C) {
   578  	curl := charm.MustParseURL("cs:quantal/dummy-1")
   579  	err := s.State.AddStoreCharmPlaceholder(curl)
   580  	c.Assert(err, jc.ErrorIsNil)
   581  	s.assertPlaceholderCharmExists(c, curl)
   582  
   583  	// Add the same one again, should be a no-op
   584  	err = s.State.AddStoreCharmPlaceholder(curl)
   585  	c.Assert(err, jc.ErrorIsNil)
   586  	s.assertPlaceholderCharmExists(c, curl)
   587  }
   588  
   589  func (s *CharmSuite) assertAddStoreCharmPlaceholder(c *gc.C) (*charm.URL, *charm.URL, *state.Charm) {
   590  	// Add a deployed charm
   591  	info := s.dummyCharm(c, "cs:quantal/dummy-1")
   592  	dummy, err := s.State.AddCharm(info)
   593  	c.Assert(err, jc.ErrorIsNil)
   594  
   595  	// Add a charm placeholder
   596  	curl2 := charm.MustParseURL("cs:quantal/dummy-2")
   597  	err = s.State.AddStoreCharmPlaceholder(curl2)
   598  	c.Assert(err, jc.ErrorIsNil)
   599  	s.assertPlaceholderCharmExists(c, curl2)
   600  
   601  	// Deployed charm is still there.
   602  	existing, err := s.State.Charm(info.ID)
   603  	c.Assert(err, jc.ErrorIsNil)
   604  	c.Assert(existing, jc.DeepEquals, dummy)
   605  
   606  	return info.ID, curl2, dummy
   607  }
   608  
   609  func (s *CharmSuite) TestAddStoreCharmPlaceholderLeavesDeployedCharmsAlone(c *gc.C) {
   610  	s.assertAddStoreCharmPlaceholder(c)
   611  }
   612  
   613  func (s *CharmSuite) TestAddStoreCharmPlaceholderDeletesOlder(c *gc.C) {
   614  	curl, curlOldRef, dummy := s.assertAddStoreCharmPlaceholder(c)
   615  
   616  	// Add a new charm placeholder
   617  	curl3 := charm.MustParseURL("cs:quantal/dummy-3")
   618  	err := s.State.AddStoreCharmPlaceholder(curl3)
   619  	c.Assert(err, jc.ErrorIsNil)
   620  	s.assertPlaceholderCharmExists(c, curl3)
   621  
   622  	// Deployed charm is still there.
   623  	existing, err := s.State.Charm(curl)
   624  	c.Assert(err, jc.ErrorIsNil)
   625  	c.Assert(existing, jc.DeepEquals, dummy)
   626  
   627  	// Older charm placeholder is gone.
   628  	doc := state.CharmDoc{}
   629  	err = s.charms.FindId(curlOldRef).One(&doc)
   630  	c.Assert(err, gc.Equals, mgo.ErrNotFound)
   631  }
   632  
   633  func (s *CharmSuite) TestAllCharms(c *gc.C) {
   634  	// Add a deployed charm
   635  	info := s.dummyCharm(c, "cs:quantal/dummy-1")
   636  	sch, err := s.State.AddCharm(info)
   637  	c.Assert(err, jc.ErrorIsNil)
   638  
   639  	// Add a charm reference
   640  	curl2 := charm.MustParseURL("cs:quantal/dummy-2")
   641  	err = s.State.AddStoreCharmPlaceholder(curl2)
   642  	c.Assert(err, jc.ErrorIsNil)
   643  
   644  	charms, err := s.State.AllCharms()
   645  	c.Assert(err, jc.ErrorIsNil)
   646  	c.Assert(charms, gc.HasLen, 3)
   647  
   648  	c.Assert(charms[0].URL().String(), gc.Equals, "local:quantal/quantal-dummy-1")
   649  	c.Assert(charms[1], gc.DeepEquals, sch)
   650  	c.Assert(charms[2].URL(), gc.DeepEquals, curl2)
   651  }
   652  
   653  type CharmTestHelperSuite struct {
   654  	ConnSuite
   655  }
   656  
   657  var _ = gc.Suite(&CharmTestHelperSuite{})
   658  
   659  func assertCustomCharm(c *gc.C, ch *state.Charm, series string, meta *charm.Meta, config *charm.Config, metrics *charm.Metrics, revision int) {
   660  	// Check Charm interface method results.
   661  	c.Assert(ch.Meta(), gc.DeepEquals, meta)
   662  	c.Assert(ch.Config(), gc.DeepEquals, config)
   663  	c.Assert(ch.Metrics(), gc.DeepEquals, metrics)
   664  	c.Assert(ch.Revision(), gc.DeepEquals, revision)
   665  
   666  	// Test URL matches charm and expected series.
   667  	url := ch.URL()
   668  	c.Assert(url.Series, gc.Equals, series)
   669  	c.Assert(url.Revision, gc.Equals, ch.Revision())
   670  
   671  	// Ignore the StoragePath and BundleSHA256 methods, they're irrelevant.
   672  }
   673  
   674  func assertStandardCharm(c *gc.C, ch *state.Charm, series string) {
   675  	chd := testcharms.Repo.CharmDir(ch.Meta().Name)
   676  	assertCustomCharm(c, ch, series, chd.Meta(), chd.Config(), chd.Metrics(), chd.Revision())
   677  }
   678  
   679  func forEachStandardCharm(c *gc.C, f func(name string)) {
   680  	for _, name := range []string{
   681  		"logging", "mysql", "riak", "wordpress",
   682  	} {
   683  		c.Logf("checking %s", name)
   684  		f(name)
   685  	}
   686  }
   687  
   688  func (s *CharmTestHelperSuite) TestSimple(c *gc.C) {
   689  	forEachStandardCharm(c, func(name string) {
   690  		chd := testcharms.Repo.CharmDir(name)
   691  		meta := chd.Meta()
   692  		config := chd.Config()
   693  		metrics := chd.Metrics()
   694  		revision := chd.Revision()
   695  
   696  		ch := s.AddTestingCharm(c, name)
   697  		assertCustomCharm(c, ch, "quantal", meta, config, metrics, revision)
   698  
   699  		ch = s.AddSeriesCharm(c, name, "anotherseries")
   700  		assertCustomCharm(c, ch, "anotherseries", meta, config, metrics, revision)
   701  	})
   702  }
   703  
   704  var configYaml = `
   705  options:
   706    working:
   707      description: when set to false, prevents service from functioning correctly
   708      default: true
   709      type: boolean
   710  `
   711  
   712  func (s *CharmTestHelperSuite) TestConfigCharm(c *gc.C) {
   713  	config, err := charm.ReadConfig(bytes.NewBuffer([]byte(configYaml)))
   714  	c.Assert(err, jc.ErrorIsNil)
   715  
   716  	forEachStandardCharm(c, func(name string) {
   717  		chd := testcharms.Repo.CharmDir(name)
   718  		meta := chd.Meta()
   719  		metrics := chd.Metrics()
   720  
   721  		ch := s.AddConfigCharm(c, name, configYaml, 123)
   722  		assertCustomCharm(c, ch, "quantal", meta, config, metrics, 123)
   723  	})
   724  }
   725  
   726  var actionsYaml = `
   727  actions:
   728     dump:
   729        description: Dump the database to STDOUT.
   730        params:
   731           redirect-file:
   732              description: Redirect to a log file.
   733              type: string
   734  `
   735  
   736  func (s *CharmTestHelperSuite) TestActionsCharm(c *gc.C) {
   737  	actions, err := charm.ReadActionsYaml(bytes.NewBuffer([]byte(actionsYaml)))
   738  	c.Assert(err, jc.ErrorIsNil)
   739  
   740  	forEachStandardCharm(c, func(name string) {
   741  		ch := s.AddActionsCharm(c, name, actionsYaml, 123)
   742  		c.Assert(ch.Actions(), gc.DeepEquals, actions)
   743  	})
   744  }
   745  
   746  var metricsYaml = `
   747  metrics:
   748    blips:
   749      description: A custom metric.
   750      type: gauge
   751  `
   752  
   753  func (s *CharmTestHelperSuite) TestMetricsCharm(c *gc.C) {
   754  	metrics, err := charm.ReadMetrics(bytes.NewBuffer([]byte(metricsYaml)))
   755  	c.Assert(err, jc.ErrorIsNil)
   756  
   757  	forEachStandardCharm(c, func(name string) {
   758  		chd := testcharms.Repo.CharmDir(name)
   759  		meta := chd.Meta()
   760  		config := chd.Config()
   761  
   762  		ch := s.AddMetricsCharm(c, name, metricsYaml, 123)
   763  		assertCustomCharm(c, ch, "quantal", meta, config, metrics, 123)
   764  	})
   765  }
   766  
   767  var metaYamlSnippet = `
   768  summary: blah
   769  description: blah blah
   770  `
   771  
   772  func (s *CharmTestHelperSuite) TestMetaCharm(c *gc.C) {
   773  	forEachStandardCharm(c, func(name string) {
   774  		chd := testcharms.Repo.CharmDir(name)
   775  		config := chd.Config()
   776  		metrics := chd.Metrics()
   777  		metaYaml := "name: " + name + metaYamlSnippet
   778  		meta, err := charm.ReadMeta(bytes.NewBuffer([]byte(metaYaml)))
   779  		c.Assert(err, jc.ErrorIsNil)
   780  
   781  		ch := s.AddMetaCharm(c, name, metaYaml, 123)
   782  		assertCustomCharm(c, ch, "quantal", meta, config, metrics, 123)
   783  	})
   784  }
   785  
   786  func (s *CharmTestHelperSuite) TestTestingCharm(c *gc.C) {
   787  	added := s.AddTestingCharm(c, "metered")
   788  	c.Assert(added.Metrics(), gc.NotNil)
   789  
   790  	chd := testcharms.Repo.CharmDir("metered")
   791  	c.Assert(chd.Metrics(), gc.DeepEquals, added.Metrics())
   792  }