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