blob: c9df62ea8e605128c9b66c9cbdf36a90a224f26d [file] [log] [blame]
khenaidooab1f7bd2019-11-14 14:00:27 -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 "expvar"
20 "fmt"
21 "net/http"
22 "strings"
23
24 "go.etcd.io/etcd/etcdserver"
25 "go.etcd.io/etcd/etcdserver/api"
26 "go.etcd.io/etcd/etcdserver/api/v2error"
27 "go.etcd.io/etcd/etcdserver/api/v2http/httptypes"
28 "go.etcd.io/etcd/pkg/logutil"
29 "go.etcd.io/etcd/version"
30
31 "github.com/coreos/pkg/capnslog"
32 "go.uber.org/zap"
33)
34
35var (
36 plog = capnslog.NewPackageLogger("go.etcd.io/etcd", "etcdserver/api/etcdhttp")
37 mlog = logutil.NewMergeLogger(plog)
38)
39
40const (
41 configPath = "/config"
42 varsPath = "/debug/vars"
43 versionPath = "/version"
44)
45
46// HandleBasic adds handlers to a mux for serving JSON etcd client requests
47// that do not access the v2 store.
48func HandleBasic(mux *http.ServeMux, server etcdserver.ServerPeer) {
49 mux.HandleFunc(varsPath, serveVars)
50
51 // TODO: deprecate '/config/local/log' in v3.5
52 mux.HandleFunc(configPath+"/local/log", logHandleFunc)
53
54 HandleMetricsHealth(mux, server)
55 mux.HandleFunc(versionPath, versionHandler(server.Cluster(), serveVersion))
56}
57
58func versionHandler(c api.Cluster, fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
59 return func(w http.ResponseWriter, r *http.Request) {
60 v := c.Version()
61 if v != nil {
62 fn(w, r, v.String())
63 } else {
64 fn(w, r, "not_decided")
65 }
66 }
67}
68
69func serveVersion(w http.ResponseWriter, r *http.Request, clusterV string) {
70 if !allowMethod(w, r, "GET") {
71 return
72 }
73 vs := version.Versions{
74 Server: version.Version,
75 Cluster: clusterV,
76 }
77
78 w.Header().Set("Content-Type", "application/json")
79 b, err := json.Marshal(&vs)
80 if err != nil {
81 plog.Panicf("cannot marshal versions to json (%v)", err)
82 }
83 w.Write(b)
84}
85
86// TODO: deprecate '/config/local/log' in v3.5
87func logHandleFunc(w http.ResponseWriter, r *http.Request) {
88 if !allowMethod(w, r, "PUT") {
89 return
90 }
91
92 in := struct{ Level string }{}
93
94 d := json.NewDecoder(r.Body)
95 if err := d.Decode(&in); err != nil {
96 WriteError(nil, w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid json body"))
97 return
98 }
99
100 logl, err := capnslog.ParseLevel(strings.ToUpper(in.Level))
101 if err != nil {
102 WriteError(nil, w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid log level "+in.Level))
103 return
104 }
105
106 plog.Noticef("globalLogLevel set to %q", logl.String())
107 capnslog.SetGlobalLogLevel(logl)
108 w.WriteHeader(http.StatusNoContent)
109}
110
111func serveVars(w http.ResponseWriter, r *http.Request) {
112 if !allowMethod(w, r, "GET") {
113 return
114 }
115
116 w.Header().Set("Content-Type", "application/json; charset=utf-8")
117 fmt.Fprintf(w, "{\n")
118 first := true
119 expvar.Do(func(kv expvar.KeyValue) {
120 if !first {
121 fmt.Fprintf(w, ",\n")
122 }
123 first = false
124 fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
125 })
126 fmt.Fprintf(w, "\n}\n")
127}
128
129func allowMethod(w http.ResponseWriter, r *http.Request, m string) bool {
130 if m == r.Method {
131 return true
132 }
133 w.Header().Set("Allow", m)
134 http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
135 return false
136}
137
138// WriteError logs and writes the given Error to the ResponseWriter
139// If Error is an etcdErr, it is rendered to the ResponseWriter
140// Otherwise, it is assumed to be a StatusInternalServerError
141func WriteError(lg *zap.Logger, w http.ResponseWriter, r *http.Request, err error) {
142 if err == nil {
143 return
144 }
145 switch e := err.(type) {
146 case *v2error.Error:
147 e.WriteTo(w)
148
149 case *httptypes.HTTPError:
150 if et := e.WriteTo(w); et != nil {
151 if lg != nil {
152 lg.Debug(
153 "failed to write v2 HTTP error",
154 zap.String("remote-addr", r.RemoteAddr),
155 zap.String("internal-server-error", e.Error()),
156 zap.Error(et),
157 )
158 } else {
159 plog.Debugf("error writing HTTPError (%v) to %s", et, r.RemoteAddr)
160 }
161 }
162
163 default:
164 switch err {
165 case etcdserver.ErrTimeoutDueToLeaderFail, etcdserver.ErrTimeoutDueToConnectionLost, etcdserver.ErrNotEnoughStartedMembers,
166 etcdserver.ErrUnhealthy:
167 if lg != nil {
168 lg.Warn(
169 "v2 response error",
170 zap.String("remote-addr", r.RemoteAddr),
171 zap.String("internal-server-error", err.Error()),
172 )
173 } else {
174 mlog.MergeError(err)
175 }
176
177 default:
178 if lg != nil {
179 lg.Warn(
180 "unexpected v2 response error",
181 zap.String("remote-addr", r.RemoteAddr),
182 zap.String("internal-server-error", err.Error()),
183 )
184 } else {
185 mlog.MergeErrorf("got unexpected response error (%v)", err)
186 }
187 }
188
189 herr := httptypes.NewHTTPError(http.StatusInternalServerError, "Internal Server Error")
190 if et := herr.WriteTo(w); et != nil {
191 if lg != nil {
192 lg.Debug(
193 "failed to write v2 HTTP error",
194 zap.String("remote-addr", r.RemoteAddr),
195 zap.String("internal-server-error", err.Error()),
196 zap.Error(et),
197 )
198 } else {
199 plog.Debugf("error writing HTTPError (%v) to %s", et, r.RemoteAddr)
200 }
201 }
202 }
203}