github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/aggregator/tools/deploy/validator.go (about) 1 // Copyright (c) 2017 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package deploy 22 23 import ( 24 "errors" 25 "fmt" 26 "sync" 27 28 "github.com/m3db/m3/src/aggregator/aggregator" 29 xsync "github.com/m3db/m3/src/x/sync" 30 ) 31 32 // validator performs validation on a deployment target, returning nil if 33 // validation succeeds, and an error otherwise. 34 type validator func() error 35 36 // validatorFactory is a factory of validators. 37 type validatorFactory interface { 38 // ValidatorFor generates a validator based on the instance, 39 // the instance group, and the instance target type. 40 ValidatorFor( 41 instance instanceMetadata, 42 group *instanceGroup, 43 targetType targetType, 44 ) validator 45 } 46 47 type factory struct { 48 client AggregatorClient 49 workers xsync.WorkerPool 50 } 51 52 func newValidatorFactory(client AggregatorClient, workers xsync.WorkerPool) validatorFactory { 53 return factory{client: client, workers: workers} 54 } 55 56 func (f factory) ValidatorFor( 57 instance instanceMetadata, 58 group *instanceGroup, 59 targetType targetType, 60 ) validator { 61 if targetType == leaderTarget { 62 return f.validatorForLeader(instance, group) 63 } 64 return f.validatorForFollower(instance) 65 } 66 67 func (f factory) validatorForFollower(follower instanceMetadata) validator { 68 return func() error { 69 status, err := f.client.Status(follower.APIEndpoint) 70 if err != nil { 71 return err 72 } 73 electionState := status.FlushStatus.ElectionState 74 if electionState != aggregator.FollowerState { 75 return fmt.Errorf("election state is %s not follower", electionState.String()) 76 } 77 return nil 78 } 79 } 80 81 func (f factory) validatorForLeader( 82 leader instanceMetadata, 83 group *instanceGroup, 84 ) validator { 85 return func() error { 86 status, err := f.client.Status(leader.APIEndpoint) 87 if err != nil { 88 return err 89 } 90 electionState := status.FlushStatus.ElectionState 91 if electionState != aggregator.LeaderState { 92 return fmt.Errorf("election state is %s not leader", electionState.String()) 93 } 94 // We also need to verify the followers in the same group are ready to take 95 // over the leader role when the leader resigns. 96 var ( 97 found bool 98 wg sync.WaitGroup 99 canLeadCh = make(chan bool, 1) 100 ) 101 for _, instance := range group.All { 102 // Skip check if the instance is the leader. 103 if instance.PlacementInstanceID == group.LeaderID { 104 found = true 105 continue 106 } 107 instance := instance 108 wg.Add(1) 109 f.workers.Go(func() { 110 defer wg.Done() 111 112 status, err := f.client.Status(instance.APIEndpoint) 113 if err != nil { 114 return 115 } 116 isFollower := status.FlushStatus.ElectionState == aggregator.FollowerState 117 canLead := status.FlushStatus.CanLead 118 if isFollower && canLead { 119 select { 120 case canLeadCh <- true: 121 default: 122 } 123 } 124 }) 125 } 126 127 // Asynchronously close the channel after all goroutines return. 128 f.workers.Go(func() { 129 wg.Wait() 130 close(canLeadCh) 131 }) 132 133 if !found { 134 return fmt.Errorf("instance %s is not in the instance group", leader.PlacementInstanceID) 135 } 136 if canLead := <-canLeadCh; !canLead { 137 return errors.New("no follower instance is ready to lead") 138 } 139 return nil 140 } 141 }