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 }