go.etcd.io/etcd@v3.3.27+incompatible/etcdserver/api/etcdhttp/base.go (about)

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