github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/raft/manifold.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package raft 5 6 import ( 7 "path/filepath" 8 9 "github.com/hashicorp/raft" 10 "github.com/juju/clock" 11 "github.com/juju/errors" 12 "github.com/prometheus/client_golang/prometheus" 13 "gopkg.in/juju/worker.v1" 14 "gopkg.in/juju/worker.v1/dependency" 15 16 "github.com/juju/juju/agent" 17 ) 18 19 // ManifoldConfig holds the information necessary to run a raft 20 // worker in a dependency.Engine. 21 type ManifoldConfig struct { 22 ClockName string 23 AgentName string 24 TransportName string 25 26 FSM raft.FSM 27 Logger Logger 28 PrometheusRegisterer prometheus.Registerer 29 NewWorker func(Config) (worker.Worker, error) 30 } 31 32 // Validate validates the manifold configuration. 33 func (config ManifoldConfig) Validate() error { 34 if config.ClockName == "" { 35 return errors.NotValidf("empty ClockName") 36 } 37 if config.AgentName == "" { 38 return errors.NotValidf("empty AgentName") 39 } 40 if config.TransportName == "" { 41 return errors.NotValidf("empty TransportName") 42 } 43 if config.FSM == nil { 44 return errors.NotValidf("nil FSM") 45 } 46 if config.Logger == nil { 47 return errors.NotValidf("nil Logger") 48 } 49 if config.NewWorker == nil { 50 return errors.NotValidf("nil NewWorker") 51 } 52 return nil 53 } 54 55 // Manifold returns a dependency.Manifold that will run a raft worker. 56 func Manifold(config ManifoldConfig) dependency.Manifold { 57 return dependency.Manifold{ 58 Inputs: []string{ 59 config.ClockName, 60 config.AgentName, 61 config.TransportName, 62 }, 63 Start: config.start, 64 Output: raftOutput, 65 } 66 } 67 68 // start is a method on ManifoldConfig because it's more readable than a closure. 69 func (config ManifoldConfig) start(context dependency.Context) (worker.Worker, error) { 70 if err := config.Validate(); err != nil { 71 return nil, errors.Trace(err) 72 } 73 74 var clk clock.Clock 75 if err := context.Get(config.ClockName, &clk); err != nil { 76 return nil, errors.Trace(err) 77 } 78 79 var agent agent.Agent 80 if err := context.Get(config.AgentName, &agent); err != nil { 81 return nil, errors.Trace(err) 82 } 83 84 var transport raft.Transport 85 if err := context.Get(config.TransportName, &transport); err != nil { 86 return nil, errors.Trace(err) 87 } 88 89 // TODO(axw) make the directory path configurable, so we can 90 // potentially have multiple Rafts. The dqlite raft should go 91 // in <data-dir>/dqlite. 92 agentConfig := agent.CurrentConfig() 93 raftDir := filepath.Join(agentConfig.DataDir(), "raft") 94 95 return config.NewWorker(Config{ 96 FSM: config.FSM, 97 Logger: config.Logger, 98 StorageDir: raftDir, 99 LocalID: raft.ServerID(agentConfig.Tag().Id()), 100 Transport: transport, 101 Clock: clk, 102 PrometheusRegisterer: config.PrometheusRegisterer, 103 }) 104 } 105 106 func raftOutput(in worker.Worker, out interface{}) error { 107 w, ok := in.(withRaftOutputs) 108 if !ok { 109 return errors.Errorf("expected input of type withRaftOutputs, got %T", in) 110 } 111 switch out := out.(type) { 112 case **raft.Raft: 113 r, err := w.Raft() 114 if err != nil { 115 return err 116 } 117 *out = r 118 case *raft.LogStore: 119 store, err := w.LogStore() 120 if err != nil { 121 return err 122 } 123 *out = store 124 default: 125 return errors.Errorf("expected output of **raft.Raft or *raft.LogStore, got %T", out) 126 } 127 return nil 128 } 129 130 type withRaftOutputs interface { 131 Raft() (*raft.Raft, error) 132 LogStore() (raft.LogStore, error) 133 }