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 }