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