github.com/m3db/m3@v1.5.0/src/cmd/tools/dtest/util/watcher.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 util 22 23 import ( 24 "bytes" 25 "fmt" 26 "sort" 27 "sync" 28 "time" 29 30 m3emnode "github.com/m3db/m3/src/dbnode/x/m3em/node" 31 xclock "github.com/m3db/m3/src/x/clock" 32 33 "go.uber.org/zap" 34 ) 35 36 type nodesWatcher struct { 37 sync.Mutex 38 pending map[string]m3emnode.Node 39 logger *zap.Logger 40 reportingInterval time.Duration 41 } 42 43 // NewNodesWatcher creates a new NodeWatcher 44 func NewNodesWatcher( 45 nodes []m3emnode.Node, 46 logger *zap.Logger, 47 reportingInterval time.Duration, 48 ) NodesWatcher { 49 watcher := &nodesWatcher{ 50 pending: make(map[string]m3emnode.Node, len(nodes)), 51 logger: logger, 52 reportingInterval: reportingInterval, 53 } 54 for _, node := range nodes { 55 watcher.addInstanceWithLock(node) 56 } 57 return watcher 58 } 59 60 func (nw *nodesWatcher) addInstanceWithLock(node m3emnode.Node) { 61 nw.pending[node.ID()] = node 62 } 63 64 func (nw *nodesWatcher) removeInstance(id string) { 65 nw.Lock() 66 defer nw.Unlock() 67 delete(nw.pending, id) 68 } 69 70 func (nw *nodesWatcher) Close() error { 71 return nil 72 } 73 74 func (nw *nodesWatcher) Pending() []m3emnode.Node { 75 nw.Lock() 76 defer nw.Unlock() 77 78 pending := make([]m3emnode.Node, 0, len(nw.pending)) 79 for _, node := range nw.pending { 80 pending = append(pending, node) 81 } 82 83 sort.Sort(nodesByID(pending)) 84 return pending 85 } 86 87 func (nw *nodesWatcher) pendingStatus() (int, string) { 88 nw.Lock() 89 defer nw.Unlock() 90 91 numPending := 0 92 var buffer bytes.Buffer 93 for _, node := range nw.pending { 94 numPending++ 95 if numPending == 1 { 96 buffer.WriteString(node.ID()) 97 } else { 98 buffer.WriteString(fmt.Sprintf(", %s", node.ID())) 99 } 100 } 101 102 return numPending, buffer.String() 103 } 104 105 func (nw *nodesWatcher) PendingAsError() error { 106 numPending, pendingString := nw.pendingStatus() 107 if numPending == 0 { 108 return nil 109 } 110 return fmt.Errorf("%d nodes not bootstrapped: %s", numPending, pendingString) 111 } 112 113 func (nw *nodesWatcher) WaitUntilAll(p NodePredicate, timeout time.Duration) bool { 114 // kick of go-routines to check condition for each pending node 115 pending := nw.Pending() 116 var wg sync.WaitGroup 117 wg.Add(len(pending)) 118 for i := range pending { 119 n := pending[i] 120 go func(node m3emnode.Node) { 121 defer wg.Done() 122 if cond := xclock.WaitUntil(func() bool { return p(node) }, timeout); cond { 123 nw.logger.Info("finished bootstrapping", zap.String("nodeID", node.ID())) 124 nw.removeInstance(node.ID()) 125 } 126 }(n) 127 } 128 129 // kick of a go-routine to log information 130 doneCh := make(chan struct{}) 131 closeCh := make(chan struct{}) 132 reporter := time.NewTicker(nw.reportingInterval) 133 defer func() { 134 reporter.Stop() 135 close(closeCh) 136 close(doneCh) 137 }() 138 go func() { 139 for { 140 select { 141 case <-closeCh: 142 doneCh <- struct{}{} 143 return 144 case <-reporter.C: 145 numPending, pendingString := nw.pendingStatus() 146 nw.logger.Info("numPending", zap.Int("num", numPending), zap.String("instances", pendingString)) 147 if numPending == 0 { 148 doneCh <- struct{}{} 149 return 150 } 151 } 152 } 153 }() 154 155 // wait until all nodes complete or timeout 156 wg.Wait() 157 158 // cleanup 159 closeCh <- struct{}{} 160 <-doneCh 161 162 // report if all nodes completed 163 numPending, _ := nw.pendingStatus() 164 return numPending == 0 165 } 166 167 type nodesByID []m3emnode.Node 168 169 func (n nodesByID) Len() int { 170 return len(n) 171 } 172 173 func (n nodesByID) Less(i, j int) bool { 174 return n[i].ID() < n[j].ID() 175 } 176 177 func (n nodesByID) Swap(i, j int) { 178 n[i], n[j] = n[j], n[i] 179 }