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

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package raftforwarder
     5  
     6  import (
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	"github.com/hashicorp/raft"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/pubsub"
    14  	"github.com/juju/utils"
    15  	"gopkg.in/juju/worker.v1"
    16  	"gopkg.in/juju/worker.v1/dependency"
    17  	"gopkg.in/natefinch/lumberjack.v2"
    18  
    19  	"github.com/juju/juju/agent"
    20  	"github.com/juju/juju/core/raftlease"
    21  	"github.com/juju/juju/state"
    22  	"github.com/juju/juju/worker/common"
    23  	workerstate "github.com/juju/juju/worker/state"
    24  )
    25  
    26  const (
    27  	// maxLogs is the maximum number of backup lease log files to keep.
    28  	maxLogs = 10
    29  
    30  	// maxLogSizeMB is the maximum size of the lease log file on disk
    31  	// in megabytes.
    32  	maxLogSizeMB = 30
    33  )
    34  
    35  // ManifoldConfig holds the resources needed to start a raft forwarder
    36  // worker in a dependency engine.
    37  type ManifoldConfig struct {
    38  	AgentName      string
    39  	RaftName       string
    40  	StateName      string
    41  	CentralHubName string
    42  
    43  	RequestTopic string
    44  	Logger       Logger
    45  	NewWorker    func(Config) (worker.Worker, error)
    46  	NewTarget    func(*state.State, io.Writer, Logger) raftlease.NotifyTarget
    47  }
    48  
    49  // Validate checks that the config has all the required values.
    50  func (config ManifoldConfig) Validate() error {
    51  	if config.AgentName == "" {
    52  		return errors.NotValidf("empty AgentName")
    53  	}
    54  	if config.StateName == "" {
    55  		return errors.NotValidf("empty StateName")
    56  	}
    57  	if config.CentralHubName == "" {
    58  		return errors.NotValidf("empty CentralHubName")
    59  	}
    60  	if config.RequestTopic == "" {
    61  		return errors.NotValidf("empty RequestTopic")
    62  	}
    63  	if config.Logger == nil {
    64  		return errors.NotValidf("nil Logger")
    65  	}
    66  	if config.NewWorker == nil {
    67  		return errors.NotValidf("nil NewWorker")
    68  	}
    69  	if config.NewTarget == nil {
    70  		return errors.NotValidf("nil NewTarget")
    71  	}
    72  	return nil
    73  }
    74  
    75  func (config ManifoldConfig) start(context dependency.Context) (worker.Worker, error) {
    76  	if err := config.Validate(); err != nil {
    77  		return nil, errors.Trace(err)
    78  	}
    79  
    80  	var agent agent.Agent
    81  	if err := context.Get(config.AgentName, &agent); err != nil {
    82  		return nil, errors.Trace(err)
    83  	}
    84  
    85  	var r *raft.Raft
    86  	if err := context.Get(config.RaftName, &r); err != nil {
    87  		return nil, errors.Trace(err)
    88  	}
    89  	var hub *pubsub.StructuredHub
    90  	if err := context.Get(config.CentralHubName, &hub); err != nil {
    91  		return nil, errors.Trace(err)
    92  	}
    93  
    94  	var stTracker workerstate.StateTracker
    95  	if err := context.Get(config.StateName, &stTracker); err != nil {
    96  		return nil, errors.Trace(err)
    97  	}
    98  	statePool, err := stTracker.Use()
    99  	if err != nil {
   100  		return nil, errors.Trace(err)
   101  	}
   102  
   103  	st := statePool.SystemState()
   104  
   105  	logPath := filepath.Join(agent.CurrentConfig().LogDir(), "lease.log")
   106  	if err := primeLogFile(logPath); err != nil {
   107  		// This isn't a fatal error, so log and continue if priming
   108  		// fails.
   109  		config.Logger.Warningf(
   110  			"unable to prime log file %q (proceeding anyway): %s",
   111  			logPath,
   112  			err.Error(),
   113  		)
   114  	}
   115  
   116  	notifyTarget := config.NewTarget(st, makeLogger(logPath), config.Logger)
   117  	w, err := config.NewWorker(Config{
   118  		Raft:   r,
   119  		Hub:    hub,
   120  		Logger: config.Logger,
   121  		Topic:  config.RequestTopic,
   122  		Target: notifyTarget,
   123  	})
   124  	if err != nil {
   125  		stTracker.Done()
   126  		return nil, errors.Trace(err)
   127  	}
   128  	return common.NewCleanupWorker(w, func() { stTracker.Done() }), nil
   129  }
   130  
   131  // Manifold builds a dependency.Manifold for running a raftforwarder
   132  // worker.
   133  func Manifold(config ManifoldConfig) dependency.Manifold {
   134  	return dependency.Manifold{
   135  		Inputs: []string{
   136  			config.AgentName,
   137  			config.RaftName,
   138  			config.StateName,
   139  			config.CentralHubName,
   140  		},
   141  		Start: config.start,
   142  	}
   143  }
   144  
   145  // NewTarget is a shim to construct a raftlease.NotifyTarget for testability.
   146  func NewTarget(st *state.State, logFile io.Writer, errorLog Logger) raftlease.NotifyTarget {
   147  	return st.LeaseNotifyTarget(logFile, errorLog)
   148  }
   149  
   150  func makeLogger(path string) *lumberjack.Logger {
   151  	return &lumberjack.Logger{
   152  		Filename:   path,
   153  		MaxSize:    maxLogSizeMB,
   154  		MaxBackups: maxLogs,
   155  		Compress:   true,
   156  	}
   157  }
   158  
   159  // primeLogFile ensures the lease log file is created with the
   160  // correct mode and ownership.
   161  func primeLogFile(path string) error {
   162  	f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600)
   163  	if err != nil {
   164  		return errors.Trace(err)
   165  	}
   166  	if err := f.Close(); err != nil {
   167  		return errors.Trace(err)
   168  	}
   169  	err = utils.ChownPath(path, "syslog")
   170  	return errors.Trace(err)
   171  }