github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/application/upgradecharm_resources_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package application_test
     5  
     6  import (
     7  	"bytes"
     8  	"io/ioutil"
     9  	"net/http/httptest"
    10  	"path"
    11  	"sort"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/juju/cmd/cmdtesting"
    16  	gitjujutesting "github.com/juju/testing"
    17  	jc "github.com/juju/testing/checkers"
    18  	gc "gopkg.in/check.v1"
    19  	"gopkg.in/juju/charm.v6"
    20  	charmresource "gopkg.in/juju/charm.v6/resource"
    21  	"gopkg.in/juju/charmrepo.v3"
    22  	"gopkg.in/juju/charmrepo.v3/csclient"
    23  	"gopkg.in/juju/charmstore.v5"
    24  	"gopkg.in/juju/names.v2"
    25  	"gopkg.in/mgo.v2"
    26  
    27  	"github.com/juju/juju/cmd/juju/application"
    28  	"github.com/juju/juju/component/all"
    29  	"github.com/juju/juju/controller"
    30  	"github.com/juju/juju/core/constraints"
    31  	jujutesting "github.com/juju/juju/juju/testing"
    32  	"github.com/juju/juju/resource"
    33  	"github.com/juju/juju/state"
    34  	"github.com/juju/juju/testcharms"
    35  )
    36  
    37  type UpgradeCharmResourceSuite struct {
    38  	jujutesting.RepoSuite
    39  }
    40  
    41  var _ = gc.Suite(&UpgradeCharmResourceSuite{})
    42  
    43  func (s *UpgradeCharmResourceSuite) SetUpSuite(c *gc.C) {
    44  	s.RepoSuite.SetUpSuite(c)
    45  	all.RegisterForServer()
    46  }
    47  
    48  func (s *UpgradeCharmResourceSuite) SetUpTest(c *gc.C) {
    49  	s.RepoSuite.SetUpTest(c)
    50  	chPath := testcharms.Repo.ClonedDirPath(s.CharmsPath, "riak")
    51  	_, err := runDeploy(c, chPath, "riak", "--series", "quantal", "--force")
    52  	c.Assert(err, jc.ErrorIsNil)
    53  	curl := charm.MustParseURL("local:quantal/riak-7")
    54  	riak, _ := s.RepoSuite.AssertApplication(c, "riak", curl, 1, 1)
    55  	c.Assert(err, jc.ErrorIsNil)
    56  	_, forced, err := riak.Charm()
    57  	c.Assert(err, jc.ErrorIsNil)
    58  	c.Assert(forced, jc.IsFalse)
    59  }
    60  
    61  func (s *UpgradeCharmResourceSuite) TestUpgradeWithResources(c *gc.C) {
    62  	const riakResourceMeta = `
    63  name: riak
    64  summary: "K/V storage engine"
    65  description: "Scalable K/V Store in Erlang with Clocks :-)"
    66  provides:
    67    endpoint:
    68      interface: http
    69    admin:
    70      interface: http
    71  peers:
    72    ring:
    73      interface: riak
    74  resources:
    75    data:
    76      type: file
    77      filename: foo.lib
    78      description: some comment
    79  `
    80  
    81  	myriakPath := testcharms.Repo.ClonedDir(c.MkDir(), "riak")
    82  	err := ioutil.WriteFile(path.Join(myriakPath.Path, "metadata.yaml"), []byte(riakResourceMeta), 0644)
    83  	c.Assert(err, jc.ErrorIsNil)
    84  
    85  	data := []byte("some-data")
    86  	fp, err := charmresource.GenerateFingerprint(bytes.NewReader(data))
    87  	c.Assert(err, jc.ErrorIsNil)
    88  
    89  	resourceFile := path.Join(c.MkDir(), "data.lib")
    90  	err = ioutil.WriteFile(resourceFile, data, 0644)
    91  	c.Assert(err, jc.ErrorIsNil)
    92  
    93  	_, err = cmdtesting.RunCommand(c, application.NewUpgradeCharmCommand(),
    94  		"riak", "--path="+myriakPath.Path, "--resource", "data="+resourceFile)
    95  	c.Assert(err, jc.ErrorIsNil)
    96  
    97  	resources, err := s.State.Resources()
    98  	c.Assert(err, jc.ErrorIsNil)
    99  
   100  	sr, err := resources.ListResources("riak")
   101  	c.Assert(err, jc.ErrorIsNil)
   102  
   103  	c.Check(sr.Resources, gc.HasLen, 1)
   104  
   105  	c.Check(sr.Resources[0].ApplicationID, gc.Equals, "riak")
   106  
   107  	// Most of this is just a sanity check... this is all tested elsewhere.
   108  	c.Check(sr.Resources[0].PendingID, gc.Equals, "")
   109  	c.Check(sr.Resources[0].Username, gc.Not(gc.Equals), "")
   110  	c.Check(sr.Resources[0].ID, gc.Not(gc.Equals), "")
   111  	c.Check(sr.Resources[0].Timestamp.IsZero(), jc.IsFalse)
   112  
   113  	// Ensure we get the data we passed in from the metadata.yaml.
   114  	c.Check(sr.Resources[0].Resource, gc.DeepEquals, charmresource.Resource{
   115  		Meta: charmresource.Meta{
   116  			Name:        "data",
   117  			Type:        charmresource.TypeFile,
   118  			Path:        "foo.lib",
   119  			Description: "some comment",
   120  		},
   121  		Origin:      charmresource.OriginUpload,
   122  		Fingerprint: fp,
   123  		Size:        int64(len(data)),
   124  	})
   125  }
   126  
   127  // charmStoreSuite is a suite fixture that puts the machinery in
   128  // place to allow testing code that calls addCharmViaAPI.
   129  type charmStoreSuite struct {
   130  	jujutesting.JujuConnSuite
   131  	handler    charmstore.HTTPCloseHandler
   132  	srv        *httptest.Server
   133  	srvSession *mgo.Session
   134  	client     *csclient.Client
   135  }
   136  
   137  func (s *charmStoreSuite) SetUpTest(c *gc.C) {
   138  	srvSession, err := gitjujutesting.MgoServer.Dial()
   139  	c.Assert(err, gc.IsNil)
   140  	s.srvSession = srvSession
   141  
   142  	// Set up the charm store testing server.
   143  	db := s.srvSession.DB("juju-testing")
   144  	params := charmstore.ServerParams{
   145  		AuthUsername: "test-user",
   146  		AuthPassword: "test-password",
   147  	}
   148  	handler, err := charmstore.NewServer(db, nil, "", params, charmstore.V5)
   149  	c.Assert(err, jc.ErrorIsNil)
   150  	s.handler = handler
   151  	s.srv = httptest.NewServer(handler)
   152  	s.client = csclient.New(csclient.Params{
   153  		URL:      s.srv.URL,
   154  		User:     params.AuthUsername,
   155  		Password: params.AuthPassword,
   156  	})
   157  
   158  	// Set charmstore URL config so the config is set during bootstrap
   159  	if s.ControllerConfigAttrs == nil {
   160  		s.ControllerConfigAttrs = make(map[string]interface{})
   161  	}
   162  	s.JujuConnSuite.ControllerConfigAttrs[controller.CharmStoreURL] = s.srv.URL
   163  
   164  	s.JujuConnSuite.SetUpTest(c)
   165  
   166  	// Initialize the charm cache dir.
   167  	s.PatchValue(&charmrepo.CacheDir, c.MkDir())
   168  }
   169  
   170  func (s *charmStoreSuite) TearDownTest(c *gc.C) {
   171  	s.handler.Close()
   172  	s.srv.Close()
   173  	s.srvSession.Close()
   174  	s.JujuConnSuite.TearDownTest(c)
   175  }
   176  
   177  type UpgradeCharmStoreResourceSuite struct {
   178  	charmStoreSuite
   179  }
   180  
   181  var _ = gc.Suite(&UpgradeCharmStoreResourceSuite{})
   182  
   183  func (s *UpgradeCharmStoreResourceSuite) SetUpSuite(c *gc.C) {
   184  	s.charmStoreSuite.SetUpSuite(c)
   185  	err := all.RegisterForServer()
   186  	c.Assert(err, jc.ErrorIsNil)
   187  	err = all.RegisterForClient()
   188  	c.Assert(err, jc.ErrorIsNil)
   189  }
   190  
   191  // TODO(ericsnow) Adapt this test to check passing revisions once the
   192  // charmstore endpoints are implemented.
   193  
   194  func (s *UpgradeCharmStoreResourceSuite) TestDeployStarsaySuccess(c *gc.C) {
   195  	testcharms.UploadCharm(c, s.client, "trusty/starsay-1", "starsay")
   196  
   197  	// let's make a fake resource file to upload
   198  	resourceContent := "some-data"
   199  
   200  	resourceFile := path.Join(c.MkDir(), "data.xml")
   201  	err := ioutil.WriteFile(resourceFile, []byte(resourceContent), 0644)
   202  	c.Assert(err, jc.ErrorIsNil)
   203  
   204  	output, err := runDeploy(c, "trusty/starsay", "--resource", "upload-resource="+resourceFile)
   205  	c.Assert(err, jc.ErrorIsNil)
   206  
   207  	expectedOutput := `Located charm "cs:trusty/starsay-1".
   208  Deploying charm "cs:trusty/starsay-1".`
   209  	c.Assert(output, gc.Equals, expectedOutput)
   210  	s.assertCharmsUploaded(c, "cs:trusty/starsay-1")
   211  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
   212  		"starsay": {charm: "cs:trusty/starsay-1"},
   213  	})
   214  
   215  	unit, err := s.State.Unit("starsay/0")
   216  	c.Assert(err, jc.ErrorIsNil)
   217  	tags := []names.UnitTag{unit.UnitTag()}
   218  	errs, err := s.APIState.UnitAssigner().AssignUnits(tags)
   219  	c.Assert(err, jc.ErrorIsNil)
   220  	c.Assert(errs, gc.DeepEquals, []error{nil})
   221  
   222  	res, err := s.State.Resources()
   223  	c.Assert(err, jc.ErrorIsNil)
   224  	svcres, err := res.ListResources("starsay")
   225  	c.Assert(err, jc.ErrorIsNil)
   226  
   227  	sort.Sort(byname(svcres.Resources))
   228  
   229  	c.Assert(svcres.Resources, gc.HasLen, 3)
   230  	c.Check(svcres.Resources[2].Timestamp, gc.Not(gc.Equals), time.Time{})
   231  	svcres.Resources[2].Timestamp = time.Time{}
   232  
   233  	// Note that all charm resources were uploaded by testcharms.UploadCharm
   234  	// so that the charm could be published.
   235  	expectedResources := []resource.Resource{{
   236  		Resource: charmresource.Resource{
   237  			Meta: charmresource.Meta{
   238  				Name:        "install-resource",
   239  				Type:        charmresource.TypeFile,
   240  				Path:        "gotta-have-it.txt",
   241  				Description: "get things started",
   242  			},
   243  			Origin:      charmresource.OriginStore,
   244  			Revision:    0,
   245  			Fingerprint: resourceHash("install-resource content"),
   246  			Size:        int64(len("install-resource content")),
   247  		},
   248  		ID:            "starsay/install-resource",
   249  		ApplicationID: "starsay",
   250  	}, {
   251  		Resource: charmresource.Resource{
   252  			Meta: charmresource.Meta{
   253  				Name:        "store-resource",
   254  				Type:        charmresource.TypeFile,
   255  				Path:        "filename.tgz",
   256  				Description: "One line that is useful when operators need to push it.",
   257  			},
   258  			Origin:      charmresource.OriginStore,
   259  			Revision:    0,
   260  			Fingerprint: resourceHash("store-resource content"),
   261  			Size:        int64(len("store-resource content")),
   262  		},
   263  		ID:            "starsay/store-resource",
   264  		ApplicationID: "starsay",
   265  	}, {
   266  		Resource: charmresource.Resource{
   267  			Meta: charmresource.Meta{
   268  				Name:        "upload-resource",
   269  				Type:        charmresource.TypeFile,
   270  				Path:        "somename.xml",
   271  				Description: "Who uses xml anymore?",
   272  			},
   273  			Origin:      charmresource.OriginUpload,
   274  			Revision:    0,
   275  			Fingerprint: resourceHash(resourceContent),
   276  			Size:        int64(len(resourceContent)),
   277  		},
   278  		ID:            "starsay/upload-resource",
   279  		ApplicationID: "starsay",
   280  		Username:      "admin",
   281  		// Timestamp is checked above
   282  	}}
   283  
   284  	c.Check(svcres.Resources, jc.DeepEquals, expectedResources)
   285  
   286  	oldCharmStoreResources := make([]charmresource.Resource, len(svcres.CharmStoreResources))
   287  	copy(oldCharmStoreResources, svcres.CharmStoreResources)
   288  
   289  	sort.Sort(csbyname(oldCharmStoreResources))
   290  
   291  	testcharms.UploadCharm(c, s.client, "trusty/starsay-2", "starsay")
   292  
   293  	_, err = cmdtesting.RunCommand(c, application.NewUpgradeCharmCommand(), "starsay")
   294  	c.Assert(err, jc.ErrorIsNil)
   295  
   296  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
   297  		"starsay": {charm: "cs:trusty/starsay-2"},
   298  	})
   299  
   300  	res, err = s.State.Resources()
   301  	c.Assert(err, jc.ErrorIsNil)
   302  	svcres, err = res.ListResources("starsay")
   303  	c.Assert(err, jc.ErrorIsNil)
   304  
   305  	sort.Sort(byname(svcres.Resources))
   306  
   307  	c.Assert(svcres.Resources, gc.HasLen, 3)
   308  	c.Check(svcres.Resources[2].Timestamp, gc.Not(gc.Equals), time.Time{})
   309  	svcres.Resources[2].Timestamp = time.Time{}
   310  
   311  	// ensure that we haven't overridden the previously uploaded resource.
   312  	c.Check(svcres.Resources, jc.DeepEquals, expectedResources)
   313  
   314  	sort.Sort(csbyname(svcres.CharmStoreResources))
   315  	c.Check(oldCharmStoreResources, gc.DeepEquals, svcres.CharmStoreResources)
   316  }
   317  
   318  func resourceHash(content string) charmresource.Fingerprint {
   319  	fp, err := charmresource.GenerateFingerprint(strings.NewReader(content))
   320  	if err != nil {
   321  		panic(err)
   322  	}
   323  	return fp
   324  }
   325  
   326  type byname []resource.Resource
   327  
   328  func (b byname) Len() int           { return len(b) }
   329  func (b byname) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
   330  func (b byname) Less(i, j int) bool { return b[i].Name < b[j].Name }
   331  
   332  type csbyname []charmresource.Resource
   333  
   334  func (b csbyname) Len() int           { return len(b) }
   335  func (b csbyname) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
   336  func (b csbyname) Less(i, j int) bool { return b[i].Name < b[j].Name }
   337  
   338  // assertCharmsUploaded checks that the given charm ids have been uploaded.
   339  func (s *charmStoreSuite) assertCharmsUploaded(c *gc.C, ids ...string) {
   340  	charms, err := s.State.AllCharms()
   341  	c.Assert(err, jc.ErrorIsNil)
   342  	uploaded := make([]string, len(charms))
   343  	for i, charm := range charms {
   344  		uploaded[i] = charm.URL().String()
   345  	}
   346  	c.Assert(uploaded, jc.SameContents, ids)
   347  }
   348  
   349  // assertApplicationsDeployed checks that the given applications have been deployed.
   350  func (s *charmStoreSuite) assertApplicationsDeployed(c *gc.C, info map[string]applicationInfo) {
   351  	applications, err := s.State.AllApplications()
   352  	c.Assert(err, jc.ErrorIsNil)
   353  	deployed := make(map[string]applicationInfo, len(applications))
   354  	for _, application := range applications {
   355  		charm, _ := application.CharmURL()
   356  		config, err := application.CharmConfig()
   357  		c.Assert(err, jc.ErrorIsNil)
   358  		if len(config) == 0 {
   359  			config = nil
   360  		}
   361  		constraints, err := application.Constraints()
   362  		c.Assert(err, jc.ErrorIsNil)
   363  		storage, err := application.StorageConstraints()
   364  		c.Assert(err, jc.ErrorIsNil)
   365  		if len(storage) == 0 {
   366  			storage = nil
   367  		}
   368  		deployed[application.Name()] = applicationInfo{
   369  			charm:       charm.String(),
   370  			config:      config,
   371  			constraints: constraints,
   372  			exposed:     application.IsExposed(),
   373  			storage:     storage,
   374  		}
   375  	}
   376  	c.Assert(deployed, jc.DeepEquals, info)
   377  }
   378  
   379  // applicationInfo holds information about a deployed application.
   380  type applicationInfo struct {
   381  	charm            string
   382  	config           charm.Settings
   383  	constraints      constraints.Value
   384  	exposed          bool
   385  	storage          map[string]state.StorageConstraints
   386  	endpointBindings map[string]string
   387  }
   388  
   389  // runDeploy executes the deploy command in order to deploy the given
   390  // charm or bundle. The deployment stderr output and error are returned.
   391  // TODO(rog) delete this when tests are universally internal or external.
   392  func runDeploy(c *gc.C, args ...string) (string, error) {
   393  	ctx, err := cmdtesting.RunCommand(c, application.NewDeployCommand(), args...)
   394  	return strings.Trim(cmdtesting.Stderr(ctx), "\n"), err
   395  }