1
// Copyright 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
4
package application_test
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"
23
"github.com/juju/juju/cmd/juju/application"
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"
33
type UpgradeCharmResourceSuite struct {
37
var _ = gc.Suite(&UpgradeCharmResourceSuite{})
39
func (s *UpgradeCharmResourceSuite) SetUpSuite(c *gc.C) {
40
s.RepoSuite.SetUpSuite(c)
41
all.RegisterForServer()
44
func (s *UpgradeCharmResourceSuite) SetUpTest(c *gc.C) {
45
s.RepoSuite.SetUpTest(c)
46
chPath := testcharms.Repo.ClonedDirPath(s.CharmsPath, "riak")
48
_, err := testing.RunCommand(c, application.NewDeployCommand(), chPath, "riak", "--series", "quantal")
49
c.Assert(err, jc.ErrorIsNil)
50
riak, err := s.State.Application("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)
58
var riakResourceMeta = []byte(`
60
summary: "K/V storage engine"
61
description: "Scalable K/V Store in Erlang with Clocks :-)"
74
description: some comment
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)
82
data := []byte("some-data")
83
fp, err := charmresource.GenerateFingerprint(bytes.NewReader(data))
84
c.Assert(err, jc.ErrorIsNil)
86
resourceFile := path.Join(c.MkDir(), "data.lib")
87
err = ioutil.WriteFile(resourceFile, data, 0644)
88
c.Assert(err, jc.ErrorIsNil)
90
_, err = testing.RunCommand(c, application.NewUpgradeCharmCommand(),
91
"riak", "--path="+myriakPath.Path, "--resource", "data="+resourceFile)
92
c.Assert(err, jc.ErrorIsNil)
94
resources, err := s.State.Resources()
95
c.Assert(err, jc.ErrorIsNil)
97
sr, err := resources.ListResources("riak")
98
c.Assert(err, jc.ErrorIsNil)
100
c.Check(sr.Resources, gc.HasLen, 1)
102
c.Check(sr.Resources[0].ApplicationID, gc.Equals, "riak")
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)
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{
114
Type: charmresource.TypeFile,
116
Description: "some comment",
118
Origin: charmresource.OriginUpload,
120
Size: int64(len(data)),
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
130
client *csclient.Client
133
func (s *charmStoreSuite) SetUpTest(c *gc.C) {
134
s.JujuConnSuite.SetUpTest(c)
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",
142
handler, err := charmstore.NewServer(db, nil, "", params, charmstore.V5)
143
c.Assert(err, jc.ErrorIsNil)
145
s.srv = httptest.NewServer(handler)
146
s.client = csclient.New(csclient.Params{
148
User: params.AuthUsername,
149
Password: params.AuthPassword,
152
application.PatchNewCharmStoreClient(s, s.srv.URL)
154
// Initialize the charm cache dir.
155
s.PatchValue(&charmrepo.CacheDir, c.MkDir())
157
// Point the CLI to the charm store testing server.
159
// Point the Juju API server to the charm store testing server.
160
s.PatchValue(&csclient.ServerURL, s.srv.URL)
163
func (s *charmStoreSuite) TearDownTest(c *gc.C) {
166
s.JujuConnSuite.TearDownTest(c)
169
type UpgradeCharmStoreResourceSuite struct {
173
var _ = gc.Suite(&UpgradeCharmStoreResourceSuite{})
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)
183
// TODO(ericsnow) Adapt this test to check passing revisions once the
184
// charmstore endpoints are implemented.
186
func (s *UpgradeCharmStoreResourceSuite) TestDeployStarsaySuccess(c *gc.C) {
187
testcharms.UploadCharm(c, s.client, "trusty/starsay-1", "starsay")
189
// let's make a fake resource file to upload
190
resourceContent := "some-data"
192
resourceFile := path.Join(c.MkDir(), "data.xml")
193
err := ioutil.WriteFile(resourceFile, []byte(resourceContent), 0644)
194
c.Assert(err, jc.ErrorIsNil)
196
ctx, err := testing.RunCommand(c, application.NewDeployCommand(), "trusty/starsay", "--resource", "upload-resource="+resourceFile)
197
c.Assert(err, jc.ErrorIsNil)
198
output := testing.Stderr(ctx)
200
expectedOutput := `Added charm "cs:trusty/starsay-1" to the model.
201
Deploying charm "cs:trusty/starsay-1" with the user specified series "trusty".
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"},
208
_, err = s.State.Unit("starsay/0")
209
c.Assert(err, jc.ErrorIsNil)
211
res, err := s.State.Resources()
212
c.Assert(err, jc.ErrorIsNil)
213
svcres, err := res.ListResources("starsay")
214
c.Assert(err, jc.ErrorIsNil)
216
sort.Sort(byname(svcres.Resources))
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{}
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",
232
Origin: charmresource.OriginStore,
234
Fingerprint: resourceHash("install-resource content"),
235
Size: int64(len("install-resource content")),
237
ID: "starsay/install-resource",
238
ApplicationID: "starsay",
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.",
247
Origin: charmresource.OriginStore,
249
Fingerprint: resourceHash("store-resource content"),
250
Size: int64(len("store-resource content")),
252
ID: "starsay/store-resource",
253
ApplicationID: "starsay",
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?",
262
Origin: charmresource.OriginUpload,
264
Fingerprint: resourceHash(resourceContent),
265
Size: int64(len(resourceContent)),
267
ID: "starsay/upload-resource",
268
ApplicationID: "starsay",
269
Username: "admin@local",
270
// Timestamp is checked above
273
c.Check(svcres.Resources, jc.DeepEquals, expectedResources)
275
oldCharmStoreResources := make([]charmresource.Resource, len(svcres.CharmStoreResources))
276
copy(oldCharmStoreResources, svcres.CharmStoreResources)
278
sort.Sort(csbyname(oldCharmStoreResources))
280
testcharms.UploadCharm(c, s.client, "trusty/starsay-2", "starsay")
282
_, err = testing.RunCommand(c, application.NewUpgradeCharmCommand(), "starsay")
283
c.Assert(err, jc.ErrorIsNil)
285
s.assertServicesDeployed(c, map[string]serviceInfo{
286
"starsay": {charm: "cs:trusty/starsay-2"},
289
res, err = s.State.Resources()
290
c.Assert(err, jc.ErrorIsNil)
291
svcres, err = res.ListResources("starsay")
292
c.Assert(err, jc.ErrorIsNil)
294
sort.Sort(byname(svcres.Resources))
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{}
300
// ensure that we haven't overridden the previously uploaded resource.
301
c.Check(svcres.Resources, jc.DeepEquals, expectedResources)
303
sort.Sort(csbyname(svcres.CharmStoreResources))
304
c.Check(oldCharmStoreResources, gc.DeepEquals, svcres.CharmStoreResources)
307
func resourceHash(content string) charmresource.Fingerprint {
308
fp, err := charmresource.GenerateFingerprint(strings.NewReader(content))
315
type byname []resource.Resource
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 }
321
type csbyname []charmresource.Resource
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 }
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()
335
c.Assert(uploaded, jc.SameContents, ids)
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.AllApplications()
341
c.Assert(err, jc.ErrorIsNil)
342
deployed := make(map[string]serviceInfo, len(services))
343
for _, application := range services {
344
charm, _ := application.CharmURL()
345
config, err := application.ConfigSettings()
346
c.Assert(err, jc.ErrorIsNil)
347
if len(config) == 0 {
350
constraints, err := application.Constraints()
351
c.Assert(err, jc.ErrorIsNil)
352
storage, err := application.StorageConstraints()
353
c.Assert(err, jc.ErrorIsNil)
354
if len(storage) == 0 {
357
deployed[application.Name()] = serviceInfo{
358
charm: charm.String(),
360
constraints: constraints,
361
exposed: application.IsExposed(),
365
c.Assert(deployed, jc.DeepEquals, info)
368
// serviceInfo holds information about a deployed application.
369
type serviceInfo struct {
371
config charm.Settings
372
constraints constraints.Value
374
storage map[string]state.StorageConstraints
375
endpointBindings map[string]string