github.com/mailgun/holster/v4@v4.20.0/cmd/election/main.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "net/http" 9 "os" 10 "os/signal" 11 "syscall" 12 "time" 13 14 "github.com/google/uuid" 15 "github.com/mailgun/holster/v4/discovery" 16 "github.com/mailgun/holster/v4/election" 17 "github.com/mailgun/holster/v4/errors" 18 "github.com/sirupsen/logrus" 19 ) 20 21 func sendRPC(ctx context.Context, peer string, req election.RPCRequest, resp *election.RPCResponse) error { 22 // Marshall the RPC request to json 23 b, err := json.Marshal(req) 24 if err != nil { 25 return errors.Wrap(err, "while encoding request") 26 } 27 28 // Create a new http request with context 29 hr, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("http://%s/rpc", peer), bytes.NewBuffer(b)) 30 if err != nil { 31 return errors.Wrap(err, "while creating request") 32 } 33 34 // Send the request 35 hp, err := http.DefaultClient.Do(hr) 36 if err != nil { 37 return errors.Wrap(err, "while sending http request") 38 } 39 defer func() { 40 _ = hp.Body.Close() 41 }() 42 43 // Decode the response from JSON 44 dec := json.NewDecoder(hp.Body) 45 if err := dec.Decode(&resp); err != nil { 46 return errors.Wrap(err, "while decoding response") 47 } 48 return nil 49 } 50 51 func newHandler(node election.Node) func(w http.ResponseWriter, r *http.Request) { 52 return func(w http.ResponseWriter, r *http.Request) { 53 dec := json.NewDecoder(r.Body) 54 var req election.RPCRequest 55 if err := dec.Decode(&req); err != nil { 56 status := http.StatusBadRequest 57 w.WriteHeader(status) 58 if _, err := w.Write([]byte(err.Error())); err != nil { 59 logrus.WithError(err).WithField("status", status).Warn("while writing response") 60 } 61 } 62 63 // Example of how a peer might exclude RPC 64 // commands it doesn't want made. 65 if req.RPC == election.SetPeersRPC { 66 status := http.StatusBadRequest 67 w.WriteHeader(status) 68 if _, err := fmt.Fprintf(w, "RPC request '%s' not allowed", req.RPC); err != nil { 69 logrus.WithError(err).WithField("status", status).Warn("while writing response") 70 } 71 return 72 } 73 74 var resp election.RPCResponse 75 node.ReceiveRPC(req, &resp) 76 77 enc := json.NewEncoder(w) 78 if err := enc.Encode(resp); err != nil { 79 status := http.StatusInternalServerError 80 w.WriteHeader(status) 81 if _, err = w.Write([]byte(err.Error())); err != nil { 82 logrus.WithError(err).WithField("status", status).Warn("while writing response") 83 } 84 } 85 } 86 } 87 88 func main() { 89 if len(os.Args) != 4 { 90 logrus.Fatal("usage: <election-address:8080> <memberlist-address:8180> <known-address:8180>") 91 } 92 93 electionAddr, memberListAddr, knownAddr := os.Args[1], os.Args[2], os.Args[3] 94 // logrus.SetLevel(logrus.DebugLevel) 95 96 node, err := election.NewNode(election.Config{ 97 // A unique identifier used to identify us in a list of peers 98 UniqueID: electionAddr, 99 // Called whenever the library detects a change in leadership 100 OnUpdate: func(leader string) { 101 logrus.Printf("Current Leader: %s\n", leader) 102 }, 103 // Called when the library wants to contact other peers 104 SendRPC: sendRPC, 105 }) 106 if err != nil { 107 logrus.Fatal(err) 108 } 109 110 // Create a member list catalog 111 ml, err := discovery.NewMemberList(context.Background(), discovery.MemberListConfig{ 112 BindAddress: memberListAddr, 113 Peer: discovery.Peer{ 114 ID: uuid.New().String(), 115 Metadata: []byte(electionAddr), 116 }, 117 KnownPeers: []string{knownAddr}, 118 OnUpdate: func(peers []discovery.Peer) { 119 var result []string 120 for _, p := range peers { 121 result = append(result, string(p.Metadata)) 122 } 123 logrus.Infof("Update Peers: %s", result) 124 if err := node.SetPeers(context.Background(), result); err != nil { 125 logrus.WithError(err).Warn("while setting peers") 126 } 127 }, 128 }) 129 if err != nil { 130 logrus.Fatal(err) 131 } 132 133 mux := http.NewServeMux() 134 mux.HandleFunc("/rpc", newHandler(node)) 135 go func() { 136 server := &http.Server{ 137 Addr: electionAddr, 138 Handler: mux, 139 ReadHeaderTimeout: 1 * time.Minute, 140 } 141 logrus.Fatal(server.ListenAndServe()) 142 }() 143 144 // Wait until the http server is up and can receive RPC requests 145 if err := election.WaitForConnect(electionAddr, 10, time.Millisecond*100); err != nil { 146 logrus.Fatal(err) 147 } 148 149 // Now that our http handler is listening for requests we 150 // can safely start the election. 151 if err := node.Start(context.Background()); err != nil { 152 logrus.Fatal(err) 153 } 154 155 // Wait here for signals to clean up our mess 156 c := make(chan os.Signal, 1) 157 signal.Notify(c, os.Interrupt, syscall.SIGTERM) 158 for range c { 159 ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 160 if err := ml.Close(ctx); err != nil { 161 logrus.WithError(err).Error("during member list catalog close") 162 } 163 cancel() 164 _ = node.Stop(context.Background()) 165 os.Exit(0) 166 } 167 }