github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/cmd/juju/service/upgradecharm_resources_test.go (about)

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