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