blob: 6c61bf5d51043de6bb19e10f28cef0239d793627 [file] [log] [blame]
khenaidoo59ce9dd2019-11-11 13:05:32 -05001// Copyright 2015 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package etcdhttp
16
17import (
18 "encoding/json"
19 "fmt"
20 "net/http"
21 "strconv"
22 "strings"
23
24 "go.etcd.io/etcd/etcdserver"
25 "go.etcd.io/etcd/etcdserver/api"
26 "go.etcd.io/etcd/etcdserver/api/membership"
27 "go.etcd.io/etcd/etcdserver/api/rafthttp"
28 "go.etcd.io/etcd/lease/leasehttp"
29 "go.etcd.io/etcd/pkg/types"
30
31 "go.uber.org/zap"
32)
33
34const (
35 peerMembersPath = "/members"
36 peerMemberPromotePrefix = "/members/promote/"
37)
38
39// NewPeerHandler generates an http.Handler to handle etcd peer requests.
40func NewPeerHandler(lg *zap.Logger, s etcdserver.ServerPeer) http.Handler {
41 return newPeerHandler(lg, s, s.RaftHandler(), s.LeaseHandler())
42}
43
44func newPeerHandler(lg *zap.Logger, s etcdserver.Server, raftHandler http.Handler, leaseHandler http.Handler) http.Handler {
45 peerMembersHandler := newPeerMembersHandler(lg, s.Cluster())
46 peerMemberPromoteHandler := newPeerMemberPromoteHandler(lg, s)
47
48 mux := http.NewServeMux()
49 mux.HandleFunc("/", http.NotFound)
50 mux.Handle(rafthttp.RaftPrefix, raftHandler)
51 mux.Handle(rafthttp.RaftPrefix+"/", raftHandler)
52 mux.Handle(peerMembersPath, peerMembersHandler)
53 mux.Handle(peerMemberPromotePrefix, peerMemberPromoteHandler)
54 if leaseHandler != nil {
55 mux.Handle(leasehttp.LeasePrefix, leaseHandler)
56 mux.Handle(leasehttp.LeaseInternalPrefix, leaseHandler)
57 }
58 mux.HandleFunc(versionPath, versionHandler(s.Cluster(), serveVersion))
59 return mux
60}
61
62func newPeerMembersHandler(lg *zap.Logger, cluster api.Cluster) http.Handler {
63 return &peerMembersHandler{
64 lg: lg,
65 cluster: cluster,
66 }
67}
68
69type peerMembersHandler struct {
70 lg *zap.Logger
71 cluster api.Cluster
72}
73
74func newPeerMemberPromoteHandler(lg *zap.Logger, s etcdserver.Server) http.Handler {
75 return &peerMemberPromoteHandler{
76 lg: lg,
77 cluster: s.Cluster(),
78 server: s,
79 }
80}
81
82type peerMemberPromoteHandler struct {
83 lg *zap.Logger
84 cluster api.Cluster
85 server etcdserver.Server
86}
87
88func (h *peerMembersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
89 if !allowMethod(w, r, "GET") {
90 return
91 }
92 w.Header().Set("X-Etcd-Cluster-ID", h.cluster.ID().String())
93
94 if r.URL.Path != peerMembersPath {
95 http.Error(w, "bad path", http.StatusBadRequest)
96 return
97 }
98 ms := h.cluster.Members()
99 w.Header().Set("Content-Type", "application/json")
100 if err := json.NewEncoder(w).Encode(ms); err != nil {
101 if h.lg != nil {
102 h.lg.Warn("failed to encode membership members", zap.Error(err))
103 } else {
104 plog.Warningf("failed to encode members response (%v)", err)
105 }
106 }
107}
108
109func (h *peerMemberPromoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
110 if !allowMethod(w, r, "POST") {
111 return
112 }
113 w.Header().Set("X-Etcd-Cluster-ID", h.cluster.ID().String())
114
115 if !strings.HasPrefix(r.URL.Path, peerMemberPromotePrefix) {
116 http.Error(w, "bad path", http.StatusBadRequest)
117 return
118 }
119 idStr := strings.TrimPrefix(r.URL.Path, peerMemberPromotePrefix)
120 id, err := strconv.ParseUint(idStr, 10, 64)
121 if err != nil {
122 http.Error(w, fmt.Sprintf("member %s not found in cluster", idStr), http.StatusNotFound)
123 return
124 }
125
126 resp, err := h.server.PromoteMember(r.Context(), id)
127 if err != nil {
128 switch err {
129 case membership.ErrIDNotFound:
130 http.Error(w, err.Error(), http.StatusNotFound)
131 case membership.ErrMemberNotLearner:
132 http.Error(w, err.Error(), http.StatusPreconditionFailed)
133 case etcdserver.ErrLearnerNotReady:
134 http.Error(w, err.Error(), http.StatusPreconditionFailed)
135 default:
136 WriteError(h.lg, w, r, err)
137 }
138 if h.lg != nil {
139 h.lg.Warn(
140 "failed to promote a member",
141 zap.String("member-id", types.ID(id).String()),
142 zap.Error(err),
143 )
144 } else {
145 plog.Errorf("error promoting member %s (%v)", types.ID(id).String(), err)
146 }
147 return
148 }
149
150 w.Header().Set("Content-Type", "application/json")
151 w.WriteHeader(http.StatusOK)
152 if err := json.NewEncoder(w).Encode(resp); err != nil {
153 if h.lg != nil {
154 h.lg.Warn("failed to encode members response", zap.Error(err))
155 } else {
156 plog.Warningf("failed to encode members response (%v)", err)
157 }
158 }
159}