launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/worker/uniter/relation/relation_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package relation_test
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	gc "launchpad.net/gocheck"
    10  	"os"
    11  	"path/filepath"
    12  	"strconv"
    13  
    14  	"launchpad.net/juju-core/charm/hooks"
    15  	jc "launchpad.net/juju-core/testing/checkers"
    16  	"launchpad.net/juju-core/worker/uniter/hook"
    17  	"launchpad.net/juju-core/worker/uniter/relation"
    18  )
    19  
    20  type StateDirSuite struct{}
    21  
    22  var _ = gc.Suite(&StateDirSuite{})
    23  
    24  func (s *StateDirSuite) TestReadStateDirEmpty(c *gc.C) {
    25  	basedir := c.MkDir()
    26  	reldir := filepath.Join(basedir, "123")
    27  
    28  	dir, err := relation.ReadStateDir(basedir, 123)
    29  	c.Assert(err, gc.IsNil)
    30  	state := dir.State()
    31  	c.Assert(state.RelationId, gc.Equals, 123)
    32  	c.Assert(msi(state.Members), gc.DeepEquals, msi{})
    33  	c.Assert(state.ChangedPending, gc.Equals, "")
    34  
    35  	_, err = os.Stat(reldir)
    36  	c.Assert(err, jc.Satisfies, os.IsNotExist)
    37  
    38  	err = dir.Ensure()
    39  	c.Assert(err, gc.IsNil)
    40  	fi, err := os.Stat(reldir)
    41  	c.Assert(err, gc.IsNil)
    42  	c.Assert(fi, jc.Satisfies, os.FileInfo.IsDir)
    43  }
    44  
    45  func (s *StateDirSuite) TestReadStateDirValid(c *gc.C) {
    46  	basedir := c.MkDir()
    47  	reldir := setUpDir(c, basedir, "123", map[string]string{
    48  		"foo-bar-1":           "change-version: 99\n",
    49  		"foo-bar-1.preparing": "change-version: 100\n",
    50  		"baz-qux-7":           "change-version: 101\nchanged-pending: true\n",
    51  		"nonsensical":         "blah",
    52  		"27":                  "blah",
    53  	})
    54  	setUpDir(c, reldir, "ignored", nil)
    55  
    56  	dir, err := relation.ReadStateDir(basedir, 123)
    57  	c.Assert(err, gc.IsNil)
    58  	state := dir.State()
    59  	c.Assert(state.RelationId, gc.Equals, 123)
    60  	c.Assert(msi(state.Members), gc.DeepEquals, msi{"foo-bar/1": 99, "baz-qux/7": 101})
    61  	c.Assert(state.ChangedPending, gc.Equals, "baz-qux/7")
    62  }
    63  
    64  var badRelationsTests = []struct {
    65  	contents map[string]string
    66  	subdirs  []string
    67  	err      string
    68  }{
    69  	{
    70  		nil, []string{"foo-bar-1"},
    71  		`.* is a directory`,
    72  	}, {
    73  		map[string]string{"foo-1": "'"}, nil,
    74  		`invalid unit file "foo-1": YAML error: .*`,
    75  	}, {
    76  		map[string]string{"foo-1": "blah: blah\n"}, nil,
    77  		`invalid unit file "foo-1": "changed-version" not set`,
    78  	}, {
    79  		map[string]string{
    80  			"foo-1": "change-version: 123\nchanged-pending: true\n",
    81  			"foo-2": "change-version: 456\nchanged-pending: true\n",
    82  		}, nil,
    83  		`"foo/1" and "foo/2" both have pending changed hooks`,
    84  	},
    85  }
    86  
    87  func (s *StateDirSuite) TestBadRelations(c *gc.C) {
    88  	for i, t := range badRelationsTests {
    89  		c.Logf("test %d", i)
    90  		basedir := c.MkDir()
    91  		reldir := setUpDir(c, basedir, "123", t.contents)
    92  		for _, subdir := range t.subdirs {
    93  			setUpDir(c, reldir, subdir, nil)
    94  		}
    95  		_, err := relation.ReadStateDir(basedir, 123)
    96  		expect := `cannot load relation state from ".*": ` + t.err
    97  		c.Assert(err, gc.ErrorMatches, expect)
    98  	}
    99  }
   100  
   101  var defaultMembers = msi{"foo/1": 0, "foo/2": 0}
   102  
   103  // writeTests verify the behaviour of sequences of HookInfos on a relation
   104  // state that starts off containing defaultMembers.
   105  var writeTests = []struct {
   106  	hooks   []hook.Info
   107  	members msi
   108  	pending string
   109  	err     string
   110  	deleted bool
   111  }{
   112  	// Verify that valid changes work.
   113  	{
   114  		hooks: []hook.Info{
   115  			{Kind: hooks.RelationChanged, RelationId: 123, RemoteUnit: "foo/1", ChangeVersion: 1},
   116  		},
   117  		members: msi{"foo/1": 1, "foo/2": 0},
   118  	}, {
   119  		hooks: []hook.Info{
   120  			{Kind: hooks.RelationJoined, RelationId: 123, RemoteUnit: "foo/3"},
   121  		},
   122  		members: msi{"foo/1": 0, "foo/2": 0, "foo/3": 0},
   123  		pending: "foo/3",
   124  	}, {
   125  		hooks: []hook.Info{
   126  			{Kind: hooks.RelationJoined, RelationId: 123, RemoteUnit: "foo/3"},
   127  			{Kind: hooks.RelationChanged, RelationId: 123, RemoteUnit: "foo/3"},
   128  		},
   129  		members: msi{"foo/1": 0, "foo/2": 0, "foo/3": 0},
   130  	}, {
   131  		hooks: []hook.Info{
   132  			{Kind: hooks.RelationDeparted, RelationId: 123, RemoteUnit: "foo/1"},
   133  		},
   134  		members: msi{"foo/2": 0},
   135  	}, {
   136  		hooks: []hook.Info{
   137  			{Kind: hooks.RelationDeparted, RelationId: 123, RemoteUnit: "foo/1"},
   138  			{Kind: hooks.RelationJoined, RelationId: 123, RemoteUnit: "foo/1"},
   139  		},
   140  		members: msi{"foo/1": 0, "foo/2": 0},
   141  		pending: "foo/1",
   142  	}, {
   143  		hooks: []hook.Info{
   144  			{Kind: hooks.RelationDeparted, RelationId: 123, RemoteUnit: "foo/1"},
   145  			{Kind: hooks.RelationJoined, RelationId: 123, RemoteUnit: "foo/1"},
   146  			{Kind: hooks.RelationChanged, RelationId: 123, RemoteUnit: "foo/1"},
   147  		},
   148  		members: msi{"foo/1": 0, "foo/2": 0},
   149  	}, {
   150  		hooks: []hook.Info{
   151  			{Kind: hooks.RelationDeparted, RelationId: 123, RemoteUnit: "foo/1"},
   152  			{Kind: hooks.RelationDeparted, RelationId: 123, RemoteUnit: "foo/2"},
   153  			{Kind: hooks.RelationBroken, RelationId: 123},
   154  		},
   155  		deleted: true,
   156  	},
   157  	// Verify detection of various error conditions.
   158  	{
   159  		hooks: []hook.Info{
   160  			{Kind: hooks.RelationJoined, RelationId: 456, RemoteUnit: "foo/1"},
   161  		},
   162  		err: "expected relation 123, got relation 456",
   163  	}, {
   164  		hooks: []hook.Info{
   165  			{Kind: hooks.RelationJoined, RelationId: 123, RemoteUnit: "foo/3"},
   166  			{Kind: hooks.RelationJoined, RelationId: 123, RemoteUnit: "foo/4"},
   167  		},
   168  		members: msi{"foo/1": 0, "foo/2": 0, "foo/3": 0},
   169  		pending: "foo/3",
   170  		err:     `expected "relation-changed" for "foo/3"`,
   171  	}, {
   172  		hooks: []hook.Info{
   173  			{Kind: hooks.RelationJoined, RelationId: 123, RemoteUnit: "foo/3"},
   174  			{Kind: hooks.RelationChanged, RelationId: 123, RemoteUnit: "foo/1"},
   175  		},
   176  		members: msi{"foo/1": 0, "foo/2": 0, "foo/3": 0},
   177  		pending: "foo/3",
   178  		err:     `expected "relation-changed" for "foo/3"`,
   179  	}, {
   180  		hooks: []hook.Info{
   181  			{Kind: hooks.RelationJoined, RelationId: 123, RemoteUnit: "foo/1"},
   182  		},
   183  		err: "unit already joined",
   184  	}, {
   185  		hooks: []hook.Info{
   186  			{Kind: hooks.RelationChanged, RelationId: 123, RemoteUnit: "foo/3"},
   187  		},
   188  		err: "unit has not joined",
   189  	}, {
   190  		hooks: []hook.Info{
   191  			{Kind: hooks.RelationDeparted, RelationId: 123, RemoteUnit: "foo/3"},
   192  		},
   193  		err: "unit has not joined",
   194  	}, {
   195  		hooks: []hook.Info{
   196  			{Kind: hooks.RelationBroken, RelationId: 123},
   197  		},
   198  		err: `cannot run "relation-broken" while units still present`,
   199  	}, {
   200  		hooks: []hook.Info{
   201  			{Kind: hooks.RelationDeparted, RelationId: 123, RemoteUnit: "foo/1"},
   202  			{Kind: hooks.RelationDeparted, RelationId: 123, RemoteUnit: "foo/2"},
   203  			{Kind: hooks.RelationBroken, RelationId: 123},
   204  			{Kind: hooks.RelationJoined, RelationId: 123, RemoteUnit: "foo/1"},
   205  		},
   206  		err:     `relation is broken and cannot be changed further`,
   207  		deleted: true,
   208  	},
   209  }
   210  
   211  func (s *StateDirSuite) TestWrite(c *gc.C) {
   212  	for i, t := range writeTests {
   213  		c.Logf("test %d", i)
   214  		basedir := c.MkDir()
   215  		setUpDir(c, basedir, "123", map[string]string{
   216  			"foo-1": "change-version: 0\n",
   217  			"foo-2": "change-version: 0\n",
   218  		})
   219  		dir, err := relation.ReadStateDir(basedir, 123)
   220  		c.Assert(err, gc.IsNil)
   221  		for i, hi := range t.hooks {
   222  			c.Logf("  hook %d", i)
   223  			if i == len(t.hooks)-1 && t.err != "" {
   224  				err = dir.State().Validate(hi)
   225  				expect := fmt.Sprintf(`inappropriate %q for %q: %s`, hi.Kind, hi.RemoteUnit, t.err)
   226  				c.Assert(err, gc.ErrorMatches, expect)
   227  			} else {
   228  				err = dir.State().Validate(hi)
   229  				c.Assert(err, gc.IsNil)
   230  				err = dir.Write(hi)
   231  				c.Assert(err, gc.IsNil)
   232  				// Check that writing the same change again is OK.
   233  				err = dir.Write(hi)
   234  				c.Assert(err, gc.IsNil)
   235  			}
   236  		}
   237  		members := t.members
   238  		if members == nil && !t.deleted {
   239  			members = defaultMembers
   240  		}
   241  		assertState(c, dir, basedir, 123, members, t.pending, t.deleted)
   242  	}
   243  }
   244  
   245  func (s *StateDirSuite) TestRemove(c *gc.C) {
   246  	basedir := c.MkDir()
   247  	dir, err := relation.ReadStateDir(basedir, 1)
   248  	c.Assert(err, gc.IsNil)
   249  	err = dir.Ensure()
   250  	c.Assert(err, gc.IsNil)
   251  	err = dir.Remove()
   252  	c.Assert(err, gc.IsNil)
   253  	err = dir.Remove()
   254  	c.Assert(err, gc.IsNil)
   255  
   256  	setUpDir(c, basedir, "99", map[string]string{
   257  		"foo-1": "change-version: 0\n",
   258  	})
   259  	dir, err = relation.ReadStateDir(basedir, 99)
   260  	c.Assert(err, gc.IsNil)
   261  	err = dir.Remove()
   262  	c.Assert(err, gc.ErrorMatches, ".*: directory not empty")
   263  }
   264  
   265  type ReadAllStateDirsSuite struct{}
   266  
   267  var _ = gc.Suite(&ReadAllStateDirsSuite{})
   268  
   269  func (s *ReadAllStateDirsSuite) TestNoDir(c *gc.C) {
   270  	basedir := c.MkDir()
   271  	relsdir := filepath.Join(basedir, "relations")
   272  
   273  	dirs, err := relation.ReadAllStateDirs(relsdir)
   274  	c.Assert(err, gc.IsNil)
   275  	c.Assert(dirs, gc.HasLen, 0)
   276  
   277  	_, err = os.Stat(relsdir)
   278  	c.Assert(err, jc.Satisfies, os.IsNotExist)
   279  }
   280  
   281  func (s *ReadAllStateDirsSuite) TestBadStateDir(c *gc.C) {
   282  	basedir := c.MkDir()
   283  	relsdir := setUpDir(c, basedir, "relations", nil)
   284  	setUpDir(c, relsdir, "123", map[string]string{
   285  		"bad-0": "blah: blah\n",
   286  	})
   287  	_, err := relation.ReadAllStateDirs(relsdir)
   288  	c.Assert(err, gc.ErrorMatches, `cannot load relations state from .*: cannot load relation state from .*: invalid unit file "bad-0": "changed-version" not set`)
   289  }
   290  
   291  func (s *ReadAllStateDirsSuite) TestReadAllStateDirs(c *gc.C) {
   292  	basedir := c.MkDir()
   293  	relsdir := setUpDir(c, basedir, "relations", map[string]string{
   294  		"ignored":     "blah",
   295  		"foo-bar-123": "gibberish",
   296  	})
   297  	setUpDir(c, relsdir, "123", map[string]string{
   298  		"foo-0":     "change-version: 1\n",
   299  		"foo-1":     "change-version: 2\nchanged-pending: true\n",
   300  		"gibberish": "gibberish",
   301  	})
   302  	setUpDir(c, relsdir, "456", map[string]string{
   303  		"bar-0": "change-version: 3\n",
   304  		"bar-1": "change-version: 4\n",
   305  	})
   306  	setUpDir(c, relsdir, "789", nil)
   307  	setUpDir(c, relsdir, "onethousand", map[string]string{
   308  		"baz-0": "change-version: 3\n",
   309  		"baz-1": "change-version: 4\n",
   310  	})
   311  
   312  	dirs, err := relation.ReadAllStateDirs(relsdir)
   313  	c.Assert(err, gc.IsNil)
   314  	for id, dir := range dirs {
   315  		c.Logf("%d: %#v", id, dir)
   316  	}
   317  	assertState(c, dirs[123], relsdir, 123, msi{"foo/0": 1, "foo/1": 2}, "foo/1", false)
   318  	assertState(c, dirs[456], relsdir, 456, msi{"bar/0": 3, "bar/1": 4}, "", false)
   319  	assertState(c, dirs[789], relsdir, 789, msi{}, "", false)
   320  	c.Assert(dirs, gc.HasLen, 3)
   321  }
   322  
   323  func setUpDir(c *gc.C, basedir, name string, contents map[string]string) string {
   324  	reldir := filepath.Join(basedir, name)
   325  	err := os.Mkdir(reldir, 0777)
   326  	c.Assert(err, gc.IsNil)
   327  	for name, content := range contents {
   328  		path := filepath.Join(reldir, name)
   329  		err := ioutil.WriteFile(path, []byte(content), 0777)
   330  		c.Assert(err, gc.IsNil)
   331  	}
   332  	return reldir
   333  }
   334  
   335  func assertState(c *gc.C, dir *relation.StateDir, relsdir string, relationId int, members msi, pending string, deleted bool) {
   336  	expect := &relation.State{
   337  		RelationId:     relationId,
   338  		Members:        map[string]int64(members),
   339  		ChangedPending: pending,
   340  	}
   341  	c.Assert(dir.State(), gc.DeepEquals, expect)
   342  	if deleted {
   343  		_, err := os.Stat(filepath.Join(relsdir, strconv.Itoa(relationId)))
   344  		c.Assert(err, jc.Satisfies, os.IsNotExist)
   345  	} else {
   346  		fresh, err := relation.ReadStateDir(relsdir, relationId)
   347  		c.Assert(err, gc.IsNil)
   348  		c.Assert(fresh.State(), gc.DeepEquals, expect)
   349  	}
   350  }