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 }