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