1
// Copyright 2012-2014 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
11
jc "github.com/juju/testing/checkers"
12
ft "github.com/juju/testing/filetesting"
13
"github.com/juju/utils/set"
14
gc "gopkg.in/check.v1"
16
"github.com/juju/juju/testing"
17
"github.com/juju/juju/worker/uniter/charm"
20
type ManifestDeployerSuite struct {
24
deployer charm.Deployer
27
var _ = gc.Suite(&ManifestDeployerSuite{})
29
// because we generally use real charm bundles for testing, and charm bundling
30
// sets every file mode to 0755 or 0644, all our input data uses those modes as
33
func (s *ManifestDeployerSuite) SetUpTest(c *gc.C) {
34
s.BaseSuite.SetUpTest(c)
35
s.bundles = &bundleReader{}
36
s.targetPath = filepath.Join(c.MkDir(), "target")
37
deployerPath := filepath.Join(c.MkDir(), "deployer")
38
s.deployer = charm.NewManifestDeployer(s.targetPath, deployerPath, s.bundles)
41
func (s *ManifestDeployerSuite) addMockCharm(c *gc.C, revision int, bundle charm.Bundle) charm.BundleInfo {
42
return s.bundles.AddBundle(c, charmURL(revision), bundle)
45
func (s *ManifestDeployerSuite) addCharm(c *gc.C, revision int, content ...ft.Entry) charm.BundleInfo {
46
return s.bundles.AddCustomBundle(c, charmURL(revision), func(path string) {
47
ft.Entries(content).Create(c, path)
51
func (s *ManifestDeployerSuite) deployCharm(c *gc.C, revision int, content ...ft.Entry) charm.BundleInfo {
52
info := s.addCharm(c, revision, content...)
53
err := s.deployer.Stage(info, nil)
54
c.Assert(err, jc.ErrorIsNil)
55
err = s.deployer.Deploy()
56
c.Assert(err, jc.ErrorIsNil)
57
s.assertCharm(c, revision, content...)
61
func (s *ManifestDeployerSuite) assertCharm(c *gc.C, revision int, content ...ft.Entry) {
62
url, err := charm.ReadCharmURL(filepath.Join(s.targetPath, ".juju-charm"))
63
c.Assert(err, jc.ErrorIsNil)
64
c.Assert(url, gc.DeepEquals, charmURL(revision))
65
ft.Entries(content).Check(c, s.targetPath)
68
func (s *ManifestDeployerSuite) TestAbortStageWhenClosed(c *gc.C) {
69
info := s.addMockCharm(c, 1, mockBundle{})
70
abort := make(chan struct{})
71
errors := make(chan error)
72
s.bundles.EnableWaitForAbort()
74
errors <- s.deployer.Stage(info, abort)
78
c.Assert(err, gc.ErrorMatches, "charm read aborted")
81
func (s *ManifestDeployerSuite) TestDontAbortStageWhenNotClosed(c *gc.C) {
82
info := s.addMockCharm(c, 1, mockBundle{})
83
abort := make(chan struct{})
84
errors := make(chan error)
85
stopWaiting := s.bundles.EnableWaitForAbort()
87
errors <- s.deployer.Stage(info, abort)
91
c.Assert(err, jc.ErrorIsNil)
94
func (s *ManifestDeployerSuite) TestDeployWithoutStage(c *gc.C) {
95
err := s.deployer.Deploy()
96
c.Assert(err, gc.ErrorMatches, "charm deployment failed: no charm set")
99
func (s *ManifestDeployerSuite) TestInstall(c *gc.C) {
100
//TODO(bogdanteleaga): Fix this on windows
101
if runtime.GOOS == "windows" {
102
c.Skip("bug 1403084: cannot symlink to relative paths on windows")
105
ft.File{"some-file", "hello", 0644},
106
ft.Dir{"some-dir", 0755},
107
ft.Symlink{"some-dir/some-link", "../some-file"},
111
func (s *ManifestDeployerSuite) TestUpgradeOverwrite(c *gc.C) {
112
//TODO(bogdanteleaga): Fix this on windows
113
if runtime.GOOS == "windows" {
114
c.Skip("bug 1403084: cannot symlink to relative paths on windows")
117
ft.File{"some-file", "hello", 0644},
118
ft.Dir{"some-dir", 0755},
119
ft.File{"some-dir/another-file", "to be removed", 0755},
120
ft.Dir{"another-dir", 0755},
121
ft.Symlink{"another-dir/some-link", "../some-file"},
123
// Replace each of file, dir, and symlink with a different entry; in
124
// the case of dir, checking that contained files are also removed.
126
ft.Symlink{"some-file", "no-longer-a-file"},
127
ft.File{"some-dir", "no-longer-a-dir", 0644},
128
ft.Dir{"another-dir", 0755},
129
ft.Dir{"another-dir/some-link", 0755},
133
func (s *ManifestDeployerSuite) TestUpgradePreserveUserFiles(c *gc.C) {
134
//TODO(bogdanteleaga): Fix this on windows
135
if runtime.GOOS == "windows" {
136
c.Skip("bug 1403084: cannot symlink to relative paths on windows")
138
originalCharmContent := ft.Entries{
139
ft.File{"charm-file", "to-be-removed", 0644},
140
ft.Dir{"charm-dir", 0755},
142
s.deployCharm(c, 1, originalCharmContent...)
144
// Add user files we expect to keep to the target dir.
145
preserveUserContent := ft.Entries{
146
ft.File{"user-file", "to-be-preserved", 0644},
147
ft.Dir{"user-dir", 0755},
148
ft.File{"user-dir/user-file", "also-preserved", 0644},
149
}.Create(c, s.targetPath)
151
// Add some user files we expect to be removed.
152
removeUserContent := ft.Entries{
153
ft.File{"charm-dir/user-file", "whoops-removed", 0755},
154
}.Create(c, s.targetPath)
156
// Add some user files we expect to be replaced.
158
ft.File{"replace-file", "original", 0644},
159
ft.Dir{"replace-dir", 0755},
160
ft.Symlink{"replace-symlink", "replace-file"},
161
}.Create(c, s.targetPath)
163
// Deploy an upgrade; all new content overwrites the old...
165
ft.File{"replace-file", "updated", 0644},
166
ft.Dir{"replace-dir", 0755},
167
ft.Symlink{"replace-symlink", "replace-dir"},
170
// ...and other files are preserved or removed according to
171
// source and location.
172
preserveUserContent.Check(c, s.targetPath)
173
removeUserContent.AsRemoveds().Check(c, s.targetPath)
174
originalCharmContent.AsRemoveds().Check(c, s.targetPath)
177
func (s *ManifestDeployerSuite) TestUpgradeConflictResolveRetrySameCharm(c *gc.C) {
178
// Create base install.
180
ft.File{"shared-file", "old", 0755},
181
ft.File{"old-file", "old", 0644},
184
// Create mock upgrade charm that can (claim to) fail to expand...
186
upgradeContent := ft.Entries{
187
ft.File{"shared-file", "new", 0755},
188
ft.File{"new-file", "new", 0644},
190
mockCharm := mockBundle{
191
paths: set.NewStrings(upgradeContent.Paths()...),
192
expand: func(targetPath string) error {
193
upgradeContent.Create(c, targetPath)
195
return fmt.Errorf("oh noes")
200
info := s.addMockCharm(c, 2, mockCharm)
201
err := s.deployer.Stage(info, nil)
202
c.Assert(err, jc.ErrorIsNil)
204
// ...and see it fail to expand. We're not too bothered about the actual
205
// content of the target dir at this stage, but we do want to check it's
206
// still marked as based on the original charm...
207
err = s.deployer.Deploy()
208
c.Assert(err, gc.Equals, charm.ErrConflict)
211
// ...and we want to verify that if we "fix the errors" and redeploy the
214
err = s.deployer.NotifyResolved()
215
c.Assert(err, jc.ErrorIsNil)
216
err = s.deployer.Deploy()
217
c.Assert(err, jc.ErrorIsNil)
219
// ...we end up with the right stuff in play.
220
s.assertCharm(c, 2, upgradeContent...)
221
ft.Removed{"old-file"}.Check(c, s.targetPath)
224
func (s *ManifestDeployerSuite) TestUpgradeConflictRevertRetryDifferentCharm(c *gc.C) {
225
// Create base install and add a user file.
227
ft.File{"shared-file", "old", 0755},
228
ft.File{"old-file", "old", 0644},
230
userFile := ft.File{"user-file", "user", 0644}.Create(c, s.targetPath)
232
// Create a charm upgrade that never works (but still writes a bunch of files),
234
badUpgradeContent := ft.Entries{
235
ft.File{"shared-file", "bad", 0644},
236
ft.File{"bad-file", "bad", 0644},
238
badCharm := mockBundle{
239
paths: set.NewStrings(badUpgradeContent.Paths()...),
240
expand: func(targetPath string) error {
241
badUpgradeContent.Create(c, targetPath)
242
return fmt.Errorf("oh noes")
245
badInfo := s.addMockCharm(c, 2, badCharm)
246
err := s.deployer.Stage(badInfo, nil)
247
c.Assert(err, jc.ErrorIsNil)
248
err = s.deployer.Deploy()
249
c.Assert(err, gc.Equals, charm.ErrConflict)
251
// Notify the Deployer that it'll be expected to revert the changes from
253
err = s.deployer.NotifyRevert()
254
c.Assert(err, jc.ErrorIsNil)
256
// Create a charm upgrade that creates a bunch of different files, without
257
// error, and deploy it; check user files are preserved, and nothing from
260
ft.File{"shared-file", "new", 0755},
261
ft.File{"new-file", "new", 0644},
263
userFile.Check(c, s.targetPath)
264
ft.Removed{"old-file"}.Check(c, s.targetPath)
265
ft.Removed{"bad-file"}.Check(c, s.targetPath)