github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/worker/uniter/charm/manifest_deployer_test.go (about) 1 // Copyright 2012-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package charm_test 5 6 import ( 7 "fmt" 8 "path/filepath" 9 10 ft "github.com/juju/testing/filetesting" 11 "github.com/juju/utils/set" 12 gc "launchpad.net/gocheck" 13 14 "github.com/juju/juju/testing" 15 "github.com/juju/juju/worker/uniter/charm" 16 ) 17 18 type ManifestDeployerSuite struct { 19 testing.BaseSuite 20 bundles *bundleReader 21 targetPath string 22 deployer charm.Deployer 23 } 24 25 var _ = gc.Suite(&ManifestDeployerSuite{}) 26 27 // because we generally use real charm bundles for testing, and charm bundling 28 // sets every file mode to 0755 or 0644, all our input data uses those modes as 29 // well. 30 31 func (s *ManifestDeployerSuite) SetUpTest(c *gc.C) { 32 s.BaseSuite.SetUpTest(c) 33 s.bundles = &bundleReader{} 34 s.targetPath = filepath.Join(c.MkDir(), "target") 35 deployerPath := filepath.Join(c.MkDir(), "deployer") 36 s.deployer = charm.NewManifestDeployer(s.targetPath, deployerPath, s.bundles) 37 } 38 39 func (s *ManifestDeployerSuite) addMockCharm(c *gc.C, revision int, bundle charm.Bundle) charm.BundleInfo { 40 return s.bundles.AddBundle(c, charmURL(revision), bundle) 41 } 42 43 func (s *ManifestDeployerSuite) addCharm(c *gc.C, revision int, content ...ft.Entry) charm.BundleInfo { 44 return s.bundles.AddCustomBundle(c, charmURL(revision), func(path string) { 45 ft.Entries(content).Create(c, path) 46 }) 47 } 48 49 func (s *ManifestDeployerSuite) deployCharm(c *gc.C, revision int, content ...ft.Entry) charm.BundleInfo { 50 info := s.addCharm(c, revision, content...) 51 err := s.deployer.Stage(info, nil) 52 c.Assert(err, gc.IsNil) 53 err = s.deployer.Deploy() 54 c.Assert(err, gc.IsNil) 55 s.assertCharm(c, revision, content...) 56 return info 57 } 58 59 func (s *ManifestDeployerSuite) assertCharm(c *gc.C, revision int, content ...ft.Entry) { 60 url, err := charm.ReadCharmURL(filepath.Join(s.targetPath, ".juju-charm")) 61 c.Assert(err, gc.IsNil) 62 c.Assert(url, gc.DeepEquals, charmURL(revision)) 63 ft.Entries(content).Check(c, s.targetPath) 64 } 65 66 func (s *ManifestDeployerSuite) TestAbortStageWhenClosed(c *gc.C) { 67 info := s.addMockCharm(c, 1, mockBundle{}) 68 abort := make(chan struct{}) 69 errors := make(chan error) 70 s.bundles.EnableWaitForAbort() 71 go func() { 72 errors <- s.deployer.Stage(info, abort) 73 }() 74 close(abort) 75 err := <-errors 76 c.Assert(err, gc.ErrorMatches, "charm read aborted") 77 } 78 79 func (s *ManifestDeployerSuite) TestDontAbortStageWhenNotClosed(c *gc.C) { 80 info := s.addMockCharm(c, 1, mockBundle{}) 81 abort := make(chan struct{}) 82 errors := make(chan error) 83 stopWaiting := s.bundles.EnableWaitForAbort() 84 go func() { 85 errors <- s.deployer.Stage(info, abort) 86 }() 87 close(stopWaiting) 88 err := <-errors 89 c.Assert(err, gc.IsNil) 90 } 91 92 func (s *ManifestDeployerSuite) TestDeployWithoutStage(c *gc.C) { 93 err := s.deployer.Deploy() 94 c.Assert(err, gc.ErrorMatches, "charm deployment failed: no charm set") 95 } 96 97 func (s *ManifestDeployerSuite) TestInstall(c *gc.C) { 98 s.deployCharm(c, 1, 99 ft.File{"some-file", "hello", 0644}, 100 ft.Dir{"some-dir", 0755}, 101 ft.Symlink{"some-dir/some-link", "../some-file"}, 102 ) 103 } 104 105 func (s *ManifestDeployerSuite) TestUpgradeOverwrite(c *gc.C) { 106 s.deployCharm(c, 1, 107 ft.File{"some-file", "hello", 0644}, 108 ft.Dir{"some-dir", 0755}, 109 ft.File{"some-dir/another-file", "to be removed", 0755}, 110 ft.Dir{"another-dir", 0755}, 111 ft.Symlink{"another-dir/some-link", "../some-file"}, 112 ) 113 // Replace each of file, dir, and symlink with a different entry; in 114 // the case of dir, checking that contained files are also removed. 115 s.deployCharm(c, 2, 116 ft.Symlink{"some-file", "no-longer-a-file"}, 117 ft.File{"some-dir", "no-longer-a-dir", 0644}, 118 ft.Dir{"another-dir", 0755}, 119 ft.Dir{"another-dir/some-link", 0755}, 120 ) 121 } 122 123 func (s *ManifestDeployerSuite) TestUpgradePreserveUserFiles(c *gc.C) { 124 originalCharmContent := ft.Entries{ 125 ft.File{"charm-file", "to-be-removed", 0644}, 126 ft.Dir{"charm-dir", 0755}, 127 } 128 s.deployCharm(c, 1, originalCharmContent...) 129 130 // Add user files we expect to keep to the target dir. 131 preserveUserContent := ft.Entries{ 132 ft.File{"user-file", "to-be-preserved", 0644}, 133 ft.Dir{"user-dir", 0755}, 134 ft.File{"user-dir/user-file", "also-preserved", 0644}, 135 }.Create(c, s.targetPath) 136 137 // Add some user files we expect to be removed. 138 removeUserContent := ft.Entries{ 139 ft.File{"charm-dir/user-file", "whoops-removed", 0755}, 140 }.Create(c, s.targetPath) 141 142 // Add some user files we expect to be replaced. 143 ft.Entries{ 144 ft.File{"replace-file", "original", 0644}, 145 ft.Dir{"replace-dir", 0755}, 146 ft.Symlink{"replace-symlink", "replace-file"}, 147 }.Create(c, s.targetPath) 148 149 // Deploy an upgrade; all new content overwrites the old... 150 s.deployCharm(c, 2, 151 ft.File{"replace-file", "updated", 0644}, 152 ft.Dir{"replace-dir", 0755}, 153 ft.Symlink{"replace-symlink", "replace-dir"}, 154 ) 155 156 // ...and other files are preserved or removed according to 157 // source and location. 158 preserveUserContent.Check(c, s.targetPath) 159 removeUserContent.AsRemoveds().Check(c, s.targetPath) 160 originalCharmContent.AsRemoveds().Check(c, s.targetPath) 161 } 162 163 func (s *ManifestDeployerSuite) TestUpgradeConflictResolveRetrySameCharm(c *gc.C) { 164 // Create base install. 165 s.deployCharm(c, 1, 166 ft.File{"shared-file", "old", 0755}, 167 ft.File{"old-file", "old", 0644}, 168 ) 169 170 // Create mock upgrade charm that can (claim to) fail to expand... 171 failDeploy := true 172 upgradeContent := ft.Entries{ 173 ft.File{"shared-file", "new", 0755}, 174 ft.File{"new-file", "new", 0644}, 175 } 176 mockCharm := mockBundle{ 177 paths: set.NewStrings(upgradeContent.Paths()...), 178 expand: func(targetPath string) error { 179 upgradeContent.Create(c, targetPath) 180 if failDeploy { 181 return fmt.Errorf("oh noes") 182 } 183 return nil 184 }, 185 } 186 info := s.addMockCharm(c, 2, mockCharm) 187 err := s.deployer.Stage(info, nil) 188 c.Assert(err, gc.IsNil) 189 190 // ...and see it fail to expand. We're not too bothered about the actual 191 // content of the target dir at this stage, but we do want to check it's 192 // still marked as based on the original charm... 193 err = s.deployer.Deploy() 194 c.Assert(err, gc.Equals, charm.ErrConflict) 195 s.assertCharm(c, 1) 196 197 // ...and we want to verify that if we "fix the errors" and redeploy the 198 // same charm... 199 failDeploy = false 200 err = s.deployer.NotifyResolved() 201 c.Assert(err, gc.IsNil) 202 err = s.deployer.Deploy() 203 c.Assert(err, gc.IsNil) 204 205 // ...we end up with the right stuff in play. 206 s.assertCharm(c, 2, upgradeContent...) 207 ft.Removed{"old-file"}.Check(c, s.targetPath) 208 } 209 210 func (s *ManifestDeployerSuite) TestUpgradeConflictRevertRetryDifferentCharm(c *gc.C) { 211 // Create base install and add a user file. 212 s.deployCharm(c, 1, 213 ft.File{"shared-file", "old", 0755}, 214 ft.File{"old-file", "old", 0644}, 215 ) 216 userFile := ft.File{"user-file", "user", 0644}.Create(c, s.targetPath) 217 218 // Create a charm upgrade that never works (but still writes a bunch of files), 219 // and deploy it. 220 badUpgradeContent := ft.Entries{ 221 ft.File{"shared-file", "bad", 0644}, 222 ft.File{"bad-file", "bad", 0644}, 223 } 224 badCharm := mockBundle{ 225 paths: set.NewStrings(badUpgradeContent.Paths()...), 226 expand: func(targetPath string) error { 227 badUpgradeContent.Create(c, targetPath) 228 return fmt.Errorf("oh noes") 229 }, 230 } 231 badInfo := s.addMockCharm(c, 2, badCharm) 232 err := s.deployer.Stage(badInfo, nil) 233 c.Assert(err, gc.IsNil) 234 err = s.deployer.Deploy() 235 c.Assert(err, gc.Equals, charm.ErrConflict) 236 237 // Notify the Deployer that it'll be expected to revert the changes from 238 // the last attempt. 239 err = s.deployer.NotifyRevert() 240 c.Assert(err, gc.IsNil) 241 242 // Create a charm upgrade that creates a bunch of different files, without 243 // error, and deploy it; check user files are preserved, and nothing from 244 // charm 1 or 2 is. 245 s.deployCharm(c, 3, 246 ft.File{"shared-file", "new", 0755}, 247 ft.File{"new-file", "new", 0644}, 248 ) 249 userFile.Check(c, s.targetPath) 250 ft.Removed{"old-file"}.Check(c, s.targetPath) 251 ft.Removed{"bad-file"}.Check(c, s.targetPath) 252 }