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  }