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