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