github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/lease/manifold/manifold.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package manifold
     5  
     6  // This needs to be in a different package from the lease manager
     7  // because it uses state (to construct the raftlease store), but the
     8  // lease manager also runs as a worker in state, so the state package
     9  // depends on worker/lease. Having it in worker/lease produces an
    10  // import cycle.
    11  
    12  import (
    13  	"fmt"
    14  	"math/rand"
    15  	"time"
    16  
    17  	"github.com/juju/clock"
    18  	"github.com/juju/errors"
    19  	"github.com/juju/pubsub"
    20  	"github.com/prometheus/client_golang/prometheus"
    21  	"gopkg.in/juju/worker.v1"
    22  	"gopkg.in/juju/worker.v1/dependency"
    23  
    24  	"github.com/juju/juju/agent"
    25  	"github.com/juju/juju/core/globalclock"
    26  	corelease "github.com/juju/juju/core/lease"
    27  	"github.com/juju/juju/core/raftlease"
    28  	"github.com/juju/juju/worker/common"
    29  	"github.com/juju/juju/worker/lease"
    30  	workerstate "github.com/juju/juju/worker/state"
    31  )
    32  
    33  const (
    34  	// MaxSleep is the longest the manager will sleep before checking
    35  	// whether any leases should be expired. If it can see a lease
    36  	// expiring sooner than that it will still wake up earlier.
    37  	MaxSleep = time.Minute
    38  
    39  	// ForwardTimeout is how long the store should wait for a response
    40  	// after sending a lease operation over the hub before deciding a
    41  	// a response is never coming back (for example if we send the
    42  	// request during a raft-leadership election). This should be long
    43  	// enough that we can be very confident the request was missed.
    44  	ForwardTimeout = 5 * time.Second
    45  )
    46  
    47  // TODO(raftlease): This manifold does too much - split out a worker
    48  // that holds the lease store and a manifold that creates it. Then
    49  // make this one depend on that.
    50  
    51  // ManifoldConfig holds the resources needed to start the lease
    52  // manager in a dependency engine.
    53  type ManifoldConfig struct {
    54  	AgentName      string
    55  	ClockName      string
    56  	CentralHubName string
    57  	StateName      string
    58  
    59  	FSM                  *raftlease.FSM
    60  	RequestTopic         string
    61  	Logger               lease.Logger
    62  	PrometheusRegisterer prometheus.Registerer
    63  	NewWorker            func(lease.ManagerConfig) (worker.Worker, error)
    64  	NewStore             func(raftlease.StoreConfig) *raftlease.Store
    65  }
    66  
    67  // Validate checks that the config has all the required values.
    68  func (c ManifoldConfig) Validate() error {
    69  	if c.AgentName == "" {
    70  		return errors.NotValidf("empty AgentName")
    71  	}
    72  	if c.ClockName == "" {
    73  		return errors.NotValidf("empty ClockName")
    74  	}
    75  	if c.CentralHubName == "" {
    76  		return errors.NotValidf("empty CentralHubName")
    77  	}
    78  	if c.StateName == "" {
    79  		return errors.NotValidf("empty StateName")
    80  	}
    81  	if c.FSM == nil {
    82  		return errors.NotValidf("nil FSM")
    83  	}
    84  	if c.RequestTopic == "" {
    85  		return errors.NotValidf("empty RequestTopic")
    86  	}
    87  	if c.Logger == nil {
    88  		return errors.NotValidf("nil Logger")
    89  	}
    90  	if c.PrometheusRegisterer == nil {
    91  		return errors.NotValidf("nil PrometheusRegisterer")
    92  	}
    93  	if c.NewWorker == nil {
    94  		return errors.NotValidf("nil NewWorker")
    95  	}
    96  	if c.NewStore == nil {
    97  		return errors.NotValidf("nil NewStore")
    98  	}
    99  	return nil
   100  }
   101  
   102  type manifoldState struct {
   103  	config ManifoldConfig
   104  	store  *raftlease.Store
   105  }
   106  
   107  func (s *manifoldState) start(context dependency.Context) (worker.Worker, error) {
   108  	if err := s.config.Validate(); err != nil {
   109  		return nil, errors.Trace(err)
   110  	}
   111  
   112  	var agent agent.Agent
   113  	if err := context.Get(s.config.AgentName, &agent); err != nil {
   114  		return nil, errors.Trace(err)
   115  	}
   116  
   117  	var clock clock.Clock
   118  	if err := context.Get(s.config.ClockName, &clock); err != nil {
   119  		return nil, errors.Trace(err)
   120  	}
   121  
   122  	var hub *pubsub.StructuredHub
   123  	if err := context.Get(s.config.CentralHubName, &hub); err != nil {
   124  		return nil, errors.Trace(err)
   125  	}
   126  
   127  	var stTracker workerstate.StateTracker
   128  	if err := context.Get(s.config.StateName, &stTracker); err != nil {
   129  		return nil, errors.Trace(err)
   130  	}
   131  	statePool, err := stTracker.Use()
   132  	if err != nil {
   133  		return nil, errors.Trace(err)
   134  	}
   135  
   136  	st := statePool.SystemState()
   137  
   138  	source := rand.NewSource(clock.Now().UnixNano())
   139  	runID := rand.New(source).Int31()
   140  
   141  	s.store = s.config.NewStore(raftlease.StoreConfig{
   142  		FSM:          s.config.FSM,
   143  		Hub:          hub,
   144  		Trapdoor:     st.LeaseTrapdoorFunc(),
   145  		RequestTopic: s.config.RequestTopic,
   146  		ResponseTopic: func(requestID uint64) string {
   147  			return fmt.Sprintf("%s.%08x.%d", s.config.RequestTopic, runID, requestID)
   148  		},
   149  		Clock:          clock,
   150  		ForwardTimeout: ForwardTimeout,
   151  	})
   152  
   153  	controllerUUID := agent.CurrentConfig().Controller().Id()
   154  	w, err := s.config.NewWorker(lease.ManagerConfig{
   155  		Secretary:            lease.SecretaryFinder(controllerUUID),
   156  		Store:                s.store,
   157  		Clock:                clock,
   158  		Logger:               s.config.Logger,
   159  		MaxSleep:             MaxSleep,
   160  		EntityUUID:           controllerUUID,
   161  		PrometheusRegisterer: s.config.PrometheusRegisterer,
   162  	})
   163  	if err != nil {
   164  		stTracker.Done()
   165  		return nil, errors.Trace(err)
   166  	}
   167  	return common.NewCleanupWorker(w, func() { stTracker.Done() }), nil
   168  }
   169  
   170  func (s *manifoldState) output(in worker.Worker, out interface{}) error {
   171  	if w, ok := in.(*common.CleanupWorker); ok {
   172  		in = w.Worker
   173  	}
   174  	manager, ok := in.(*lease.Manager)
   175  	if !ok {
   176  		return errors.Errorf("expected input of type *worker/lease.Manager, got %T", in)
   177  	}
   178  	switch out := out.(type) {
   179  	case *globalclock.Updater:
   180  		*out = s.store
   181  		return nil
   182  	case *corelease.Manager:
   183  		*out = manager
   184  		return nil
   185  	default:
   186  		return errors.Errorf("expected output of type *globalclock.Updater or *core/lease.Manager, got %T", out)
   187  	}
   188  }
   189  
   190  // Manifold builds a dependency.Manifold for running a lease manager.
   191  func Manifold(config ManifoldConfig) dependency.Manifold {
   192  	s := manifoldState{config: config}
   193  	return dependency.Manifold{
   194  		Inputs: []string{
   195  			config.AgentName,
   196  			config.ClockName,
   197  			config.CentralHubName,
   198  			config.StateName,
   199  		},
   200  		Start:  s.start,
   201  		Output: s.output,
   202  	}
   203  }
   204  
   205  // NewWorker wraps NewManager to return worker.Worker for testability.
   206  func NewWorker(config lease.ManagerConfig) (worker.Worker, error) {
   207  	return lease.NewManager(config)
   208  }
   209  
   210  // NewStore is a shim to make a raftlease.Store for testability.
   211  func NewStore(config raftlease.StoreConfig) *raftlease.Store {
   212  	return raftlease.NewStore(config)
   213  }