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  }