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 }