github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/agent/tools/tools_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package tools_test
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"io/ioutil"
    10  	"os"
    11  	"path/filepath"
    12  	"sort"
    13  
    14  	gc "launchpad.net/gocheck"
    15  
    16  	agenttools "github.com/juju/juju/agent/tools"
    17  	"github.com/juju/juju/testing"
    18  	coretest "github.com/juju/juju/tools"
    19  	"github.com/juju/juju/version"
    20  )
    21  
    22  type ToolsSuite struct {
    23  	testing.BaseSuite
    24  	dataDir string
    25  }
    26  
    27  var _ = gc.Suite(&ToolsSuite{})
    28  
    29  func (t *ToolsSuite) SetUpTest(c *gc.C) {
    30  	t.BaseSuite.SetUpTest(c)
    31  	t.dataDir = c.MkDir()
    32  }
    33  
    34  func (t *ToolsSuite) TestPackageDependencies(c *gc.C) {
    35  	// This test is to ensure we don't bring in dependencies on state, environ
    36  	// or any of the other bigger packages that'll drag in yet more dependencies.
    37  	// Only imports that start with "github.com/juju/juju" are checked, and the
    38  	// resulting slice has that prefix removed to keep the output short.
    39  	c.Assert(testing.FindJujuCoreImports(c, "github.com/juju/juju/agent/tools"),
    40  		gc.DeepEquals,
    41  		[]string{"juju/arch", "tools", "version"})
    42  }
    43  
    44  const toolsFile = "downloaded-tools.txt"
    45  
    46  // gzyesses holds the result of running:
    47  // yes | head -17000 | gzip
    48  var gzyesses = []byte{
    49  	0x1f, 0x8b, 0x08, 0x00, 0x29, 0xae, 0x1a, 0x50,
    50  	0x00, 0x03, 0xed, 0xc2, 0x31, 0x0d, 0x00, 0x00,
    51  	0x00, 0x02, 0xa0, 0xdf, 0xc6, 0xb6, 0xb7, 0x87,
    52  	0x63, 0xd0, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00,
    53  	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    54  	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    55  	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    56  	0x00, 0x00, 0x00, 0x38, 0x31, 0x53, 0xad, 0x03,
    57  	0x8d, 0xd0, 0x84, 0x00, 0x00,
    58  }
    59  
    60  type badDataTest struct {
    61  	data     []byte
    62  	checksum string
    63  	err      string
    64  }
    65  
    66  func initBadDataTest(name string, mode os.FileMode, contents string, err string) badDataTest {
    67  	var result badDataTest
    68  	result.data, result.checksum = testing.TarGz(testing.NewTarFile(name, mode, contents))
    69  	result.err = err
    70  	return result
    71  }
    72  
    73  var unpackToolsBadDataTests = []badDataTest{
    74  	initBadDataTest("bar", os.ModeDir, "", "bad file type.*"),
    75  	initBadDataTest("../../etc/passwd", 0755, "", "bad name.*"),
    76  	initBadDataTest(`\ini.sys`, 0755, "", "bad name.*"),
    77  	badDataTest{[]byte("x"), "2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881", "unexpected EOF"},
    78  	badDataTest{gzyesses, "8d900c68a1a847aae4e95edcb29fcecd142c9b88ca4fe63209c216edbed546e1", "archive/tar: invalid tar header"},
    79  }
    80  
    81  func (t *ToolsSuite) TestUnpackToolsBadData(c *gc.C) {
    82  	for i, test := range unpackToolsBadDataTests {
    83  		c.Logf("test %d", i)
    84  		testTools := &coretest.Tools{
    85  			URL:     "http://foo/bar",
    86  			Version: version.MustParseBinary("1.2.3-foo-bar"),
    87  			Size:    int64(len(test.data)),
    88  			SHA256:  test.checksum,
    89  		}
    90  		err := agenttools.UnpackTools(t.dataDir, testTools, bytes.NewReader(test.data))
    91  		c.Assert(err, gc.ErrorMatches, test.err)
    92  		assertDirNames(c, t.toolsDir(), []string{})
    93  	}
    94  }
    95  
    96  func (t *ToolsSuite) TestUnpackToolsBadChecksum(c *gc.C) {
    97  	data, _ := testing.TarGz(testing.NewTarFile("tools", 0755, "some data"))
    98  	testTools := &coretest.Tools{
    99  		URL:     "http://foo/bar",
   100  		Version: version.MustParseBinary("1.2.3-foo-bar"),
   101  		Size:    int64(len(data)),
   102  		SHA256:  "1234",
   103  	}
   104  	err := agenttools.UnpackTools(t.dataDir, testTools, bytes.NewReader(data))
   105  	c.Assert(err, gc.ErrorMatches, "tarball sha256 mismatch, expected 1234, got .*")
   106  	_, err = os.Stat(t.toolsDir())
   107  	c.Assert(err, gc.FitsTypeOf, &os.PathError{})
   108  }
   109  
   110  func (t *ToolsSuite) toolsDir() string {
   111  	return filepath.Join(t.dataDir, "tools")
   112  }
   113  
   114  func (t *ToolsSuite) TestUnpackToolsContents(c *gc.C) {
   115  	files := []*testing.TarFile{
   116  		testing.NewTarFile("bar", 0755, "bar contents"),
   117  		testing.NewTarFile("foo", 0755, "foo contents"),
   118  	}
   119  	data, checksum := testing.TarGz(files...)
   120  	testTools := &coretest.Tools{
   121  		URL:     "http://foo/bar",
   122  		Version: version.MustParseBinary("1.2.3-foo-bar"),
   123  		Size:    int64(len(data)),
   124  		SHA256:  checksum,
   125  	}
   126  
   127  	err := agenttools.UnpackTools(t.dataDir, testTools, bytes.NewReader(data))
   128  	c.Assert(err, gc.IsNil)
   129  	assertDirNames(c, t.toolsDir(), []string{"1.2.3-foo-bar"})
   130  	t.assertToolsContents(c, testTools, files)
   131  
   132  	// Try to unpack the same version of tools again - it should succeed,
   133  	// leaving the original version around.
   134  	files2 := []*testing.TarFile{
   135  		testing.NewTarFile("bar", 0755, "bar2 contents"),
   136  		testing.NewTarFile("x", 0755, "x contents"),
   137  	}
   138  	data2, checksum2 := testing.TarGz(files2...)
   139  	tools2 := &coretest.Tools{
   140  		URL:     "http://arble",
   141  		Version: version.MustParseBinary("1.2.3-foo-bar"),
   142  		Size:    int64(len(data2)),
   143  		SHA256:  checksum2,
   144  	}
   145  	err = agenttools.UnpackTools(t.dataDir, tools2, bytes.NewReader(data2))
   146  	c.Assert(err, gc.IsNil)
   147  	assertDirNames(c, t.toolsDir(), []string{"1.2.3-foo-bar"})
   148  	t.assertToolsContents(c, testTools, files)
   149  }
   150  
   151  func (t *ToolsSuite) TestReadToolsErrors(c *gc.C) {
   152  	vers := version.MustParseBinary("1.2.3-precise-amd64")
   153  	testTools, err := agenttools.ReadTools(t.dataDir, vers)
   154  	c.Assert(testTools, gc.IsNil)
   155  	c.Assert(err, gc.ErrorMatches, "cannot read tools metadata in tools directory: .*")
   156  
   157  	dir := agenttools.SharedToolsDir(t.dataDir, vers)
   158  	err = os.MkdirAll(dir, 0755)
   159  	c.Assert(err, gc.IsNil)
   160  
   161  	err = ioutil.WriteFile(filepath.Join(dir, toolsFile), []byte(" \t\n"), 0644)
   162  	c.Assert(err, gc.IsNil)
   163  
   164  	testTools, err = agenttools.ReadTools(t.dataDir, vers)
   165  	c.Assert(testTools, gc.IsNil)
   166  	c.Assert(err, gc.ErrorMatches, "invalid tools metadata in tools directory .*")
   167  }
   168  
   169  func (t *ToolsSuite) TestChangeAgentTools(c *gc.C) {
   170  	files := []*testing.TarFile{
   171  		testing.NewTarFile("jujuc", 0755, "juju executable"),
   172  		testing.NewTarFile("jujud", 0755, "jujuc executable"),
   173  	}
   174  	data, checksum := testing.TarGz(files...)
   175  	testTools := &coretest.Tools{
   176  		URL:     "http://foo/bar1",
   177  		Version: version.MustParseBinary("1.2.3-foo-bar"),
   178  		Size:    int64(len(data)),
   179  		SHA256:  checksum,
   180  	}
   181  	err := agenttools.UnpackTools(t.dataDir, testTools, bytes.NewReader(data))
   182  	c.Assert(err, gc.IsNil)
   183  
   184  	gotTools, err := agenttools.ChangeAgentTools(t.dataDir, "testagent", testTools.Version)
   185  	c.Assert(err, gc.IsNil)
   186  	c.Assert(*gotTools, gc.Equals, *testTools)
   187  
   188  	assertDirNames(c, t.toolsDir(), []string{"1.2.3-foo-bar", "testagent"})
   189  	assertDirNames(c, agenttools.ToolsDir(t.dataDir, "testagent"), []string{"jujuc", "jujud", toolsFile})
   190  
   191  	// Upgrade again to check that the link replacement logic works ok.
   192  	files2 := []*testing.TarFile{
   193  		testing.NewTarFile("foo", 0755, "foo content"),
   194  		testing.NewTarFile("bar", 0755, "bar content"),
   195  	}
   196  	data2, checksum2 := testing.TarGz(files2...)
   197  	tools2 := &coretest.Tools{
   198  		URL:     "http://foo/bar2",
   199  		Version: version.MustParseBinary("1.2.4-foo-bar"),
   200  		Size:    int64(len(data2)),
   201  		SHA256:  checksum2,
   202  	}
   203  	err = agenttools.UnpackTools(t.dataDir, tools2, bytes.NewReader(data2))
   204  	c.Assert(err, gc.IsNil)
   205  
   206  	gotTools, err = agenttools.ChangeAgentTools(t.dataDir, "testagent", tools2.Version)
   207  	c.Assert(err, gc.IsNil)
   208  	c.Assert(*gotTools, gc.Equals, *tools2)
   209  
   210  	assertDirNames(c, t.toolsDir(), []string{"1.2.3-foo-bar", "1.2.4-foo-bar", "testagent"})
   211  	assertDirNames(c, agenttools.ToolsDir(t.dataDir, "testagent"), []string{"foo", "bar", toolsFile})
   212  }
   213  
   214  func (t *ToolsSuite) TestSharedToolsDir(c *gc.C) {
   215  	dir := agenttools.SharedToolsDir("/var/lib/juju", version.MustParseBinary("1.2.3-precise-amd64"))
   216  	c.Assert(dir, gc.Equals, "/var/lib/juju/tools/1.2.3-precise-amd64")
   217  }
   218  
   219  // assertToolsContents asserts that the directory for the tools
   220  // has the given contents.
   221  func (t *ToolsSuite) assertToolsContents(c *gc.C, testTools *coretest.Tools, files []*testing.TarFile) {
   222  	var wantNames []string
   223  	for _, f := range files {
   224  		wantNames = append(wantNames, f.Header.Name)
   225  	}
   226  	wantNames = append(wantNames, toolsFile)
   227  	dir := agenttools.SharedToolsDir(t.dataDir, testTools.Version)
   228  	assertDirNames(c, dir, wantNames)
   229  	expectedURLFileContents, err := json.Marshal(testTools)
   230  	c.Assert(err, gc.IsNil)
   231  	assertFileContents(c, dir, toolsFile, string(expectedURLFileContents), 0200)
   232  	for _, f := range files {
   233  		assertFileContents(c, dir, f.Header.Name, f.Contents, 0400)
   234  	}
   235  	gotTools, err := agenttools.ReadTools(t.dataDir, testTools.Version)
   236  	c.Assert(err, gc.IsNil)
   237  	c.Assert(*gotTools, gc.Equals, *testTools)
   238  }
   239  
   240  // assertFileContents asserts that the given file in the
   241  // given directory has the given contents.
   242  func assertFileContents(c *gc.C, dir, file, contents string, mode os.FileMode) {
   243  	file = filepath.Join(dir, file)
   244  	info, err := os.Stat(file)
   245  	c.Assert(err, gc.IsNil)
   246  	c.Assert(info.Mode()&(os.ModeType|mode), gc.Equals, mode)
   247  	data, err := ioutil.ReadFile(file)
   248  	c.Assert(err, gc.IsNil)
   249  	c.Assert(string(data), gc.Equals, contents)
   250  }
   251  
   252  // assertDirNames asserts that the given directory
   253  // holds the given file or directory names.
   254  func assertDirNames(c *gc.C, dir string, names []string) {
   255  	f, err := os.Open(dir)
   256  	c.Assert(err, gc.IsNil)
   257  	defer f.Close()
   258  	dnames, err := f.Readdirnames(0)
   259  	c.Assert(err, gc.IsNil)
   260  	sort.Strings(dnames)
   261  	sort.Strings(names)
   262  	c.Assert(dnames, gc.DeepEquals, names)
   263  }