github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/raft/raftforwarder/worker.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 "time" 8 9 "github.com/hashicorp/raft" 10 "github.com/juju/errors" 11 "github.com/juju/pubsub" 12 "gopkg.in/juju/worker.v1" 13 "gopkg.in/juju/worker.v1/catacomb" 14 15 "github.com/juju/juju/core/raftlease" 16 ) 17 18 const applyTimeout = 5 * time.Second 19 20 // This worker receives raft commands forwarded over the hub and 21 // applies them to the raft node. 22 23 // RaftApplier allows applying a command to the raft FSM. 24 type RaftApplier interface { 25 Apply(cmd []byte, timeout time.Duration) raft.ApplyFuture 26 } 27 28 // Logger specifies the interface we use from loggo.Logger. 29 type Logger interface { 30 Errorf(string, ...interface{}) 31 Warningf(string, ...interface{}) 32 Tracef(string, ...interface{}) 33 } 34 35 // Config defines the resources the worker needs to run. 36 type Config struct { 37 Hub *pubsub.StructuredHub 38 Raft RaftApplier 39 Logger Logger 40 Topic string 41 Target raftlease.NotifyTarget 42 } 43 44 // Validate checks that this config can be used. 45 func (config Config) Validate() error { 46 if config.Hub == nil { 47 return errors.NotValidf("nil Hub") 48 } 49 if config.Raft == nil { 50 return errors.NotValidf("nil Raft") 51 } 52 if config.Logger == nil { 53 return errors.NotValidf("nil Logger") 54 } 55 if config.Topic == "" { 56 return errors.NotValidf("empty Topic") 57 } 58 if config.Target == nil { 59 return errors.NotValidf("nil Target") 60 } 61 return nil 62 } 63 64 // NewWorker creates and starts a worker that will forward leadership 65 // claims from non-raft-leader machines. 66 func NewWorker(config Config) (worker.Worker, error) { 67 if err := config.Validate(); err != nil { 68 return nil, errors.Trace(err) 69 } 70 w := &forwarder{ 71 config: config, 72 } 73 unsubscribe, err := w.config.Hub.Subscribe(w.config.Topic, w.handleRequest) 74 if err != nil { 75 return nil, errors.Annotatef(err, "subscribing to %q", w.config.Topic) 76 } 77 w.unsubscribe = unsubscribe 78 if err := catacomb.Invoke(catacomb.Plan{ 79 Site: &w.catacomb, 80 Work: w.loop, 81 }); err != nil { 82 unsubscribe() 83 return nil, errors.Trace(err) 84 } 85 return w, nil 86 } 87 88 type forwarder struct { 89 catacomb catacomb.Catacomb 90 config Config 91 unsubscribe func() 92 id int 93 } 94 95 // Kill is part of the worker.Worker interface. 96 func (w *forwarder) Kill() { 97 w.catacomb.Kill(nil) 98 } 99 100 // Wait is part of the worker.Worker interface. 101 func (w *forwarder) Wait() error { 102 return w.catacomb.Wait() 103 } 104 105 func (w *forwarder) loop() error { 106 defer w.unsubscribe() 107 <-w.catacomb.Dying() 108 return w.catacomb.ErrDying() 109 } 110 111 func (w *forwarder) handleRequest(_ string, req raftlease.ForwardRequest, err error) { 112 w.id++ 113 reqID := w.id 114 w.config.Logger.Tracef("%d: received %#v, err: %s", reqID, req, err) 115 if err != nil { 116 // This should never happen, so treat it as fatal. 117 w.catacomb.Kill(errors.Annotate(err, "requests callback failed")) 118 return 119 } 120 go func() { 121 defer w.config.Logger.Tracef("%d: done", reqID) 122 response, err := w.processRequest(req.Command) 123 if err != nil { 124 w.catacomb.Kill(errors.Annotate(err, "applying command")) 125 return 126 } 127 _, err = w.config.Hub.Publish(req.ResponseTopic, response) 128 if err != nil { 129 w.catacomb.Kill(errors.Annotate(err, "publishing response")) 130 return 131 } 132 }() 133 } 134 135 func (w *forwarder) processRequest(command string) (raftlease.ForwardResponse, error) { 136 var empty raftlease.ForwardResponse 137 future := w.config.Raft.Apply([]byte(command), applyTimeout) 138 if err := future.Error(); err != nil { 139 return empty, errors.Trace(err) 140 } 141 respValue := future.Response() 142 response, ok := respValue.(raftlease.FSMResponse) 143 if !ok { 144 return empty, errors.Errorf("expected an FSMResponse, got %T: %#v", respValue, respValue) 145 } 146 response.Notify(w.config.Target) 147 return responseFromError(response.Error()), nil 148 } 149 150 func responseFromError(err error) raftlease.ForwardResponse { 151 return raftlease.ForwardResponse{ 152 Error: raftlease.AsResponseError(err), 153 } 154 }