github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/peergrouper/mock_test.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package peergrouper 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "math/rand" 10 "path" 11 "reflect" 12 "strconv" 13 "sync" 14 15 "github.com/juju/errors" 16 "github.com/juju/replicaset" 17 "github.com/juju/utils/voyeur" 18 "gopkg.in/juju/worker.v1" 19 "gopkg.in/tomb.v2" 20 21 "github.com/juju/juju/controller" 22 "github.com/juju/juju/core/instance" 23 "github.com/juju/juju/core/status" 24 "github.com/juju/juju/environs/config" 25 "github.com/juju/juju/network" 26 "github.com/juju/juju/state" 27 coretesting "github.com/juju/juju/testing" 28 ) 29 30 // This file holds helper functions for mocking pieces of State and replicaset 31 // that we don't want to directly depend on in unit tests. 32 33 type fakeState struct { 34 mu sync.Mutex 35 errors errorPatterns 36 machines map[string]*fakeMachine 37 controllers voyeur.Value // of *state.ControllerInfo 38 statuses voyeur.Value // of statuses collection 39 controllerConfig voyeur.Value // of controller.Config 40 session *fakeMongoSession 41 checkMu sync.Mutex 42 check func(st *fakeState) error 43 } 44 45 var ( 46 _ State = (*fakeState)(nil) 47 _ Machine = (*fakeMachine)(nil) 48 _ MongoSession = (*fakeMongoSession)(nil) 49 ) 50 51 type errorPatterns struct { 52 patterns []errorPattern 53 } 54 55 type errorPattern struct { 56 pattern string 57 errFunc func() error 58 } 59 60 // setErrorFor causes the given error to be returned 61 // from any mock call that matches the given 62 // string, which may contain wildcards as 63 // in path.Match. 64 // 65 // The standard form for errors is: 66 // Type.Function <arg>... 67 // See individual functions for details. 68 func (e *errorPatterns) setErrorFor(what string, err error) { 69 e.setErrorFuncFor(what, func() error { 70 return err 71 }) 72 } 73 74 // setErrorFuncFor causes the given function 75 // to be invoked to return the error for the 76 // given pattern. 77 func (e *errorPatterns) setErrorFuncFor(what string, errFunc func() error) { 78 e.patterns = append(e.patterns, errorPattern{ 79 pattern: what, 80 errFunc: errFunc, 81 }) 82 } 83 84 // errorFor concatenates the call name 85 // with all the args, space separated, 86 // and returns any error registered with 87 // setErrorFor that matches the resulting string. 88 func (e *errorPatterns) errorFor(name string, args ...interface{}) error { 89 s := name 90 for _, arg := range args { 91 s += " " + fmt.Sprint(arg) 92 } 93 f := func() error { return nil } 94 for _, pattern := range e.patterns { 95 if ok, _ := path.Match(pattern.pattern, s); ok { 96 f = pattern.errFunc 97 break 98 } 99 } 100 err := f() 101 if err != nil { 102 logger.Errorf("errorFor %q -> %v", s, err) 103 } 104 return err 105 } 106 107 func (e *errorPatterns) resetErrors() { 108 e.patterns = e.patterns[:0] 109 } 110 111 func NewFakeState() *fakeState { 112 st := &fakeState{ 113 machines: make(map[string]*fakeMachine), 114 } 115 st.session = newFakeMongoSession(st, &st.errors) 116 st.controllerConfig.Set(controller.Config{}) 117 return st 118 } 119 120 func (st *fakeState) getCheck() func(st *fakeState) error { 121 st.checkMu.Lock() 122 check := st.check 123 st.checkMu.Unlock() 124 return check 125 } 126 127 func (st *fakeState) setCheck(check func(st *fakeState) error) { 128 st.checkMu.Lock() 129 st.check = check 130 st.checkMu.Unlock() 131 } 132 133 func (st *fakeState) checkInvariants() { 134 check := st.getCheck() 135 if check == nil { 136 return 137 } 138 if err := check(st); err != nil { 139 // Force a panic, otherwise we can deadlock 140 // when called from within the worker. 141 go panic(err) 142 select {} 143 } 144 } 145 146 // checkInvariants checks that all the expected invariants 147 // in the state hold true. Currently we check that: 148 // - total number of votes is odd. 149 // - member voting status implies that machine has vote. 150 func checkInvariants(st *fakeState) error { 151 st.mu.Lock() 152 defer st.mu.Unlock() 153 members := st.session.members.Get().([]replicaset.Member) 154 voteCount := 0 155 for _, m := range members { 156 votes := 1 157 if m.Votes != nil { 158 votes = *m.Votes 159 } 160 voteCount += votes 161 if id, ok := m.Tags[jujuMachineKey]; ok { 162 if votes > 0 { 163 m := st.machines[id] 164 if m == nil { 165 return fmt.Errorf("voting member with machine id %q has no associated Machine", id) 166 } 167 168 if !m.doc().hasVote { 169 return fmt.Errorf("machine %q should be marked as having the vote, but does not", id) 170 } 171 } 172 } 173 } 174 if voteCount%2 != 1 { 175 return fmt.Errorf("total vote count is not odd (got %d)", voteCount) 176 } 177 return nil 178 } 179 180 type invariantChecker interface { 181 checkInvariants() 182 } 183 184 // machine is similar to Machine except that 185 // it bypasses the error mocking machinery. 186 // It returns nil if there is no machine with the 187 // given id. 188 func (st *fakeState) machine(id string) *fakeMachine { 189 st.mu.Lock() 190 defer st.mu.Unlock() 191 return st.machines[id] 192 } 193 194 func (st *fakeState) Machine(id string) (Machine, error) { 195 if err := st.errors.errorFor("State.Machine", id); err != nil { 196 return nil, err 197 } 198 if m := st.machine(id); m != nil { 199 return m, nil 200 } 201 return nil, errors.NotFoundf("machine %s", id) 202 } 203 204 func (st *fakeState) addMachine(id string, wantsVote bool) *fakeMachine { 205 st.mu.Lock() 206 defer st.mu.Unlock() 207 logger.Infof("fakeState.addMachine %q", id) 208 if st.machines[id] != nil { 209 panic(fmt.Errorf("id %q already used", id)) 210 } 211 doc := machineDoc{ 212 id: id, 213 wantsVote: wantsVote, 214 statusInfo: status.StatusInfo{Status: status.Started}, 215 life: state.Alive, 216 } 217 m := &fakeMachine{ 218 errors: &st.errors, 219 checker: st, 220 } 221 st.machines[id] = m 222 m.val.Set(doc) 223 return m 224 } 225 226 func (st *fakeState) removeMachine(id string) { 227 st.mu.Lock() 228 defer st.mu.Unlock() 229 if st.machines[id] == nil { 230 panic(fmt.Errorf("removing non-existent machine %q", id)) 231 } 232 delete(st.machines, id) 233 } 234 235 func (st *fakeState) setControllers(ids ...string) { 236 st.controllers.Set(&state.ControllerInfo{ 237 MachineIds: ids, 238 }) 239 } 240 241 func (st *fakeState) ControllerInfo() (*state.ControllerInfo, error) { 242 if err := st.errors.errorFor("State.ControllerInfo"); err != nil { 243 return nil, err 244 } 245 return deepCopy(st.controllers.Get()).(*state.ControllerInfo), nil 246 } 247 248 func (st *fakeState) WatchControllerInfo() state.NotifyWatcher { 249 return WatchValue(&st.controllers) 250 } 251 252 func (st *fakeState) WatchControllerStatusChanges() state.StringsWatcher { 253 return WatchStrings(&st.statuses) 254 } 255 256 func (st *fakeState) WatchControllerConfig() state.NotifyWatcher { 257 return WatchValue(&st.controllerConfig) 258 } 259 260 func (st *fakeState) ModelConfig() (*config.Config, error) { 261 attrs := coretesting.FakeConfig() 262 cfg, err := config.New(config.NoDefaults, attrs) 263 return cfg, err 264 } 265 266 func (st *fakeState) ControllerConfig() (controller.Config, error) { 267 st.mu.Lock() 268 defer st.mu.Unlock() 269 270 if err := st.errors.errorFor("State.ControllerConfig"); err != nil { 271 return nil, err 272 } 273 return deepCopy(st.controllerConfig.Get()).(controller.Config), nil 274 } 275 276 func (st *fakeState) RemoveControllerMachine(m Machine) error { 277 st.mu.Lock() 278 defer st.mu.Unlock() 279 controllerInfo := st.controllers.Get().(*state.ControllerInfo) 280 machineIds := controllerInfo.MachineIds 281 var newMachineIds []string 282 machineId := m.Id() 283 for _, id := range machineIds { 284 if id == machineId { 285 continue 286 } 287 newMachineIds = append(newMachineIds, id) 288 } 289 st.setControllers(newMachineIds...) 290 return nil 291 } 292 293 func (st *fakeState) setHASpace(spaceName string) { 294 st.mu.Lock() 295 defer st.mu.Unlock() 296 297 cfg := st.controllerConfig.Get().(controller.Config) 298 cfg[controller.JujuHASpace] = spaceName 299 st.controllerConfig.Set(cfg) 300 } 301 302 type fakeMachine struct { 303 mu sync.Mutex 304 errors *errorPatterns 305 val voyeur.Value // of machineDoc 306 checker invariantChecker 307 } 308 309 type machineDoc struct { 310 id string 311 wantsVote bool 312 hasVote bool 313 instanceId instance.Id 314 addresses []network.Address 315 statusInfo status.StatusInfo 316 life state.Life 317 } 318 319 func (m *fakeMachine) doc() machineDoc { 320 return m.val.Get().(machineDoc) 321 } 322 323 func (m *fakeMachine) Refresh() error { 324 m.mu.Lock() 325 defer m.mu.Unlock() 326 doc := m.doc() 327 if err := m.errors.errorFor("Machine.Refresh", doc.id); err != nil { 328 return err 329 } 330 return nil 331 } 332 333 func (m *fakeMachine) GoString() string { 334 m.mu.Lock() 335 defer m.mu.Unlock() 336 return fmt.Sprintf("&fakeMachine{%#v}", m.doc()) 337 } 338 339 func (m *fakeMachine) Id() string { 340 m.mu.Lock() 341 defer m.mu.Unlock() 342 return m.doc().id 343 } 344 345 func (m *fakeMachine) Life() state.Life { 346 m.mu.Lock() 347 defer m.mu.Unlock() 348 return m.doc().life 349 } 350 351 func (m *fakeMachine) Watch() state.NotifyWatcher { 352 m.mu.Lock() 353 defer m.mu.Unlock() 354 return WatchValue(&m.val) 355 } 356 357 func (m *fakeMachine) WantsVote() bool { 358 m.mu.Lock() 359 defer m.mu.Unlock() 360 return m.doc().wantsVote 361 } 362 363 func (m *fakeMachine) HasVote() bool { 364 m.mu.Lock() 365 defer m.mu.Unlock() 366 return m.doc().hasVote 367 } 368 369 func (m *fakeMachine) Addresses() []network.Address { 370 m.mu.Lock() 371 defer m.mu.Unlock() 372 return m.doc().addresses 373 } 374 375 func (m *fakeMachine) Status() (status.StatusInfo, error) { 376 m.mu.Lock() 377 defer m.mu.Unlock() 378 return m.doc().statusInfo, nil 379 } 380 381 func (m *fakeMachine) SetStatus(sInfo status.StatusInfo) error { 382 m.mutate(func(doc *machineDoc) { 383 doc.statusInfo = sInfo 384 }) 385 return nil 386 } 387 388 // mutate atomically changes the machineDoc of 389 // the receiver by mutating it with the provided function. 390 func (m *fakeMachine) mutate(f func(*machineDoc)) { 391 m.mu.Lock() 392 doc := m.doc() 393 f(&doc) 394 m.val.Set(doc) 395 m.checker.checkInvariants() 396 m.mu.Unlock() 397 } 398 399 func (m *fakeMachine) setAddresses(addrs ...network.Address) { 400 m.mutate(func(doc *machineDoc) { 401 doc.addresses = addrs 402 }) 403 } 404 405 // SetHasVote implements Machine.SetHasVote. 406 func (m *fakeMachine) SetHasVote(hasVote bool) error { 407 doc := m.doc() 408 if err := m.errors.errorFor("Machine.SetHasVote", doc.id, hasVote); err != nil { 409 return err 410 } 411 m.mutate(func(doc *machineDoc) { 412 doc.hasVote = hasVote 413 }) 414 return nil 415 } 416 417 func (m *fakeMachine) setWantsVote(wantsVote bool) { 418 m.mutate(func(doc *machineDoc) { 419 doc.wantsVote = wantsVote 420 }) 421 } 422 423 func (m *fakeMachine) advanceLifecycle(life state.Life, wantsVote bool) { 424 m.mutate(func(doc *machineDoc) { 425 doc.life = life 426 doc.wantsVote = wantsVote 427 }) 428 } 429 430 type fakeMongoSession struct { 431 // If InstantlyReady is true, replica status of 432 // all members will be instantly reported as ready. 433 InstantlyReady bool 434 435 errors *errorPatterns 436 checker invariantChecker 437 members voyeur.Value // of []replicaset.Member 438 status voyeur.Value // of *replicaset.Status 439 } 440 441 // newFakeMongoSession returns a mock implementation of mongoSession. 442 func newFakeMongoSession(checker invariantChecker, errors *errorPatterns) *fakeMongoSession { 443 s := new(fakeMongoSession) 444 s.checker = checker 445 s.errors = errors 446 return s 447 } 448 449 // CurrentMembers implements mongoSession.CurrentMembers. 450 func (session *fakeMongoSession) CurrentMembers() ([]replicaset.Member, error) { 451 if err := session.errors.errorFor("Session.CurrentMembers"); err != nil { 452 return nil, err 453 } 454 return deepCopy(session.members.Get()).([]replicaset.Member), nil 455 } 456 457 // CurrentStatus implements mongoSession.CurrentStatus. 458 func (session *fakeMongoSession) CurrentStatus() (*replicaset.Status, error) { 459 if err := session.errors.errorFor("Session.CurrentStatus"); err != nil { 460 return nil, err 461 } 462 return deepCopy(session.status.Get()).(*replicaset.Status), nil 463 } 464 465 // setStatus sets the status of the current members of the session. 466 func (session *fakeMongoSession) setStatus(members []replicaset.MemberStatus) { 467 session.status.Set(deepCopy(&replicaset.Status{ 468 Members: members, 469 })) 470 } 471 472 // Set implements mongoSession.Set 473 func (session *fakeMongoSession) Set(members []replicaset.Member) error { 474 if err := session.errors.errorFor("Session.Set"); err != nil { 475 logger.Infof("NOT setting replicaset members to \n%s", prettyReplicaSetMembersSlice(members)) 476 return err 477 } 478 logger.Infof("setting replicaset members to \n%s", prettyReplicaSetMembersSlice(members)) 479 session.members.Set(deepCopy(members)) 480 if session.InstantlyReady { 481 statuses := make([]replicaset.MemberStatus, len(members)) 482 for i, m := range members { 483 statuses[i] = replicaset.MemberStatus{ 484 Id: m.Id, 485 Address: m.Address, 486 Healthy: true, 487 State: replicaset.SecondaryState, 488 } 489 if i == 0 { 490 statuses[i].State = replicaset.PrimaryState 491 } 492 } 493 session.setStatus(statuses) 494 } 495 session.checker.checkInvariants() 496 return nil 497 } 498 499 func (session *fakeMongoSession) StepDownPrimary() error { 500 if err := session.errors.errorFor("Session.StepDownPrimary"); err != nil { 501 logger.Debugf("StepDownPrimary error: %v", err) 502 return err 503 } 504 logger.Debugf("StepDownPrimary") 505 status := session.status.Get().(*replicaset.Status) 506 members := session.members.Get().([]replicaset.Member) 507 membersById := make(map[int]replicaset.Member, len(members)) 508 for _, member := range members { 509 membersById[member.Id] = member 510 } 511 // We use a simple algorithm, find the primary, and all the secondaries that don't have 0 priority. And then pick a 512 // random secondary and swap their states 513 primaryIndex := -1 514 secondaryIndexes := []int{} 515 var info []string 516 for i, statusMember := range status.Members { 517 if statusMember.State == replicaset.PrimaryState { 518 primaryIndex = i 519 info = append(info, fmt.Sprintf("%d: current primary", statusMember.Id)) 520 } else if statusMember.State == replicaset.SecondaryState { 521 confMember := membersById[statusMember.Id] 522 if confMember.Priority == nil || *confMember.Priority > 0 { 523 secondaryIndexes = append(secondaryIndexes, i) 524 info = append(info, fmt.Sprintf("%d: eligible secondary", statusMember.Id)) 525 } else { 526 info = append(info, fmt.Sprintf("%d: ineligible secondary", statusMember.Id)) 527 } 528 } 529 } 530 if primaryIndex == -1 { 531 return errors.Errorf("no primary to step down, broken config?") 532 } 533 if len(secondaryIndexes) < 1 { 534 return errors.Errorf("no secondaries to switch to") 535 } 536 secondaryIndex := secondaryIndexes[rand.Intn(len(secondaryIndexes))] 537 status.Members[primaryIndex].State = replicaset.SecondaryState 538 status.Members[secondaryIndex].State = replicaset.PrimaryState 539 logger.Debugf("StepDownPrimary nominated %d to be the new primary from: %v", 540 status.Members[secondaryIndex].Id, info) 541 session.setStatus(status.Members) 542 return nil 543 } 544 545 func (session *fakeMongoSession) Refresh() { 546 // If this was a testing.Stub we would track that Refresh was called. 547 } 548 549 // prettyReplicaSetMembersSlice wraps prettyReplicaSetMembers for testing 550 // purposes only. 551 func prettyReplicaSetMembersSlice(members []replicaset.Member) string { 552 vrm := make(map[string]*replicaset.Member, len(members)) 553 for i := range members { 554 m := members[i] 555 vrm[strconv.Itoa(m.Id)] = &m 556 } 557 return prettyReplicaSetMembers(vrm) 558 } 559 560 // deepCopy makes a deep copy of any type by marshalling 561 // it as JSON, then unmarshalling it. 562 func deepCopy(x interface{}) interface{} { 563 v := reflect.ValueOf(x) 564 data, err := json.Marshal(x) 565 if err != nil { 566 panic(fmt.Errorf("cannot marshal %#v: %v", x, err)) 567 } 568 newv := reflect.New(v.Type()) 569 if err := json.Unmarshal(data, newv.Interface()); err != nil { 570 panic(fmt.Errorf("cannot unmarshal %q into %s", data, newv.Type())) 571 } 572 // sanity check 573 newx := newv.Elem().Interface() 574 if !reflect.DeepEqual(newx, x) { 575 panic(fmt.Errorf("value not deep-copied correctly")) 576 } 577 return newx 578 } 579 580 type notifier struct { 581 tomb tomb.Tomb 582 w *voyeur.Watcher 583 changes chan struct{} 584 } 585 586 func (n *notifier) loop() { 587 for n.w.Next() { 588 select { 589 case n.changes <- struct{}{}: 590 case <-n.tomb.Dying(): 591 } 592 } 593 } 594 595 // WatchValue returns a NotifyWatcher that triggers 596 // when the given value changes. Its Wait and Err methods 597 // never return a non-nil error. 598 func WatchValue(val *voyeur.Value) state.NotifyWatcher { 599 n := ¬ifier{ 600 w: val.Watch(), 601 changes: make(chan struct{}), 602 } 603 n.tomb.Go(func() error { 604 n.loop() 605 return nil 606 }) 607 return n 608 } 609 610 // Changes returns a channel that sends a value when the value changes. 611 // The value itself can be retrieved by calling the value's Get method. 612 func (n *notifier) Changes() <-chan struct{} { 613 return n.changes 614 } 615 616 // Kill stops the notifier but does not wait for it to finish. 617 func (n *notifier) Kill() { 618 n.tomb.Kill(nil) 619 n.w.Close() 620 } 621 622 func (n *notifier) Err() error { 623 return n.tomb.Err() 624 } 625 626 // Wait waits for the notifier to finish. It always returns nil. 627 func (n *notifier) Wait() error { 628 return n.tomb.Wait() 629 } 630 631 func (n *notifier) Stop() error { 632 return worker.Stop(n) 633 } 634 635 type stringsNotifier struct { 636 tomb tomb.Tomb 637 w *voyeur.Watcher 638 changes chan []string 639 } 640 641 // WatchStrings returns a StringsWatcher that triggers 642 // when the given value changes. Its Wait and Err methods 643 // never return a non-nil error. 644 func WatchStrings(val *voyeur.Value) state.StringsWatcher { 645 n := &stringsNotifier{ 646 w: val.Watch(), 647 changes: make(chan []string), 648 } 649 n.tomb.Go(func() error { 650 n.loop() 651 return nil 652 }) 653 return n 654 } 655 656 func (n *stringsNotifier) loop() { 657 for n.w.Next() { 658 select { 659 case n.changes <- []string{}: 660 case <-n.tomb.Dying(): 661 } 662 } 663 } 664 665 // Changes returns a channel that sends a value when the value changes. 666 // The value itself can be retrieved by calling the value's Get method. 667 func (n *stringsNotifier) Changes() <-chan []string { 668 return n.changes 669 } 670 671 // Kill stops the notifier but does not wait for it to finish. 672 func (n *stringsNotifier) Kill() { 673 n.tomb.Kill(nil) 674 n.w.Close() 675 } 676 677 func (n *stringsNotifier) Err() error { 678 return n.tomb.Err() 679 } 680 681 // Wait waits for the notifier to finish. It always returns nil. 682 func (n *stringsNotifier) Wait() error { 683 return n.tomb.Wait() 684 } 685 686 func (n *stringsNotifier) Stop() error { 687 return worker.Stop(n) 688 }