go.etcd.io/etcd@v3.3.27+incompatible/lease/leasehttp/http.go (about)

     1  // Copyright 2016 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 leasehttp
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"net/http"
    24  	"time"
    25  
    26  	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
    27  	"github.com/coreos/etcd/lease"
    28  	"github.com/coreos/etcd/lease/leasepb"
    29  	"github.com/coreos/etcd/pkg/httputil"
    30  )
    31  
    32  var (
    33  	LeasePrefix         = "/leases"
    34  	LeaseInternalPrefix = "/leases/internal"
    35  	applyTimeout        = time.Second
    36  	ErrLeaseHTTPTimeout = errors.New("waiting for node to catch up its applied index has timed out")
    37  )
    38  
    39  // NewHandler returns an http Handler for lease renewals
    40  func NewHandler(l lease.Lessor, waitch func() <-chan struct{}) http.Handler {
    41  	return &leaseHandler{l, waitch}
    42  }
    43  
    44  type leaseHandler struct {
    45  	l      lease.Lessor
    46  	waitch func() <-chan struct{}
    47  }
    48  
    49  func (h *leaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    50  	if r.Method != "POST" {
    51  		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
    52  		return
    53  	}
    54  
    55  	b, err := ioutil.ReadAll(r.Body)
    56  	if err != nil {
    57  		http.Error(w, "error reading body", http.StatusBadRequest)
    58  		return
    59  	}
    60  
    61  	var v []byte
    62  	switch r.URL.Path {
    63  	case LeasePrefix:
    64  		lreq := pb.LeaseKeepAliveRequest{}
    65  		if err := lreq.Unmarshal(b); err != nil {
    66  			http.Error(w, "error unmarshalling request", http.StatusBadRequest)
    67  			return
    68  		}
    69  		select {
    70  		case <-h.waitch():
    71  		case <-time.After(applyTimeout):
    72  			http.Error(w, ErrLeaseHTTPTimeout.Error(), http.StatusRequestTimeout)
    73  			return
    74  		}
    75  		ttl, err := h.l.Renew(lease.LeaseID(lreq.ID))
    76  		if err != nil {
    77  			if err == lease.ErrLeaseNotFound {
    78  				http.Error(w, err.Error(), http.StatusNotFound)
    79  				return
    80  			}
    81  
    82  			http.Error(w, err.Error(), http.StatusBadRequest)
    83  			return
    84  		}
    85  		// TODO: fill out ResponseHeader
    86  		resp := &pb.LeaseKeepAliveResponse{ID: lreq.ID, TTL: ttl}
    87  		v, err = resp.Marshal()
    88  		if err != nil {
    89  			http.Error(w, err.Error(), http.StatusInternalServerError)
    90  			return
    91  		}
    92  
    93  	case LeaseInternalPrefix:
    94  		lreq := leasepb.LeaseInternalRequest{}
    95  		if err := lreq.Unmarshal(b); err != nil {
    96  			http.Error(w, "error unmarshalling request", http.StatusBadRequest)
    97  			return
    98  		}
    99  		select {
   100  		case <-h.waitch():
   101  		case <-time.After(applyTimeout):
   102  			http.Error(w, ErrLeaseHTTPTimeout.Error(), http.StatusRequestTimeout)
   103  			return
   104  		}
   105  		l := h.l.Lookup(lease.LeaseID(lreq.LeaseTimeToLiveRequest.ID))
   106  		if l == nil {
   107  			http.Error(w, lease.ErrLeaseNotFound.Error(), http.StatusNotFound)
   108  			return
   109  		}
   110  		// TODO: fill out ResponseHeader
   111  		resp := &leasepb.LeaseInternalResponse{
   112  			LeaseTimeToLiveResponse: &pb.LeaseTimeToLiveResponse{
   113  				Header:     &pb.ResponseHeader{},
   114  				ID:         lreq.LeaseTimeToLiveRequest.ID,
   115  				TTL:        int64(l.Remaining().Seconds()),
   116  				GrantedTTL: l.TTL(),
   117  			},
   118  		}
   119  		if lreq.LeaseTimeToLiveRequest.Keys {
   120  			ks := l.Keys()
   121  			kbs := make([][]byte, len(ks))
   122  			for i := range ks {
   123  				kbs[i] = []byte(ks[i])
   124  			}
   125  			resp.LeaseTimeToLiveResponse.Keys = kbs
   126  		}
   127  
   128  		v, err = resp.Marshal()
   129  		if err != nil {
   130  			http.Error(w, err.Error(), http.StatusInternalServerError)
   131  			return
   132  		}
   133  
   134  	default:
   135  		http.Error(w, fmt.Sprintf("unknown request path %q", r.URL.Path), http.StatusBadRequest)
   136  		return
   137  	}
   138  
   139  	w.Header().Set("Content-Type", "application/protobuf")
   140  	w.Write(v)
   141  }
   142  
   143  // RenewHTTP renews a lease at a given primary server.
   144  // TODO: Batch request in future?
   145  func RenewHTTP(ctx context.Context, id lease.LeaseID, url string, rt http.RoundTripper) (int64, error) {
   146  	// will post lreq protobuf to leader
   147  	lreq, err := (&pb.LeaseKeepAliveRequest{ID: int64(id)}).Marshal()
   148  	if err != nil {
   149  		return -1, err
   150  	}
   151  
   152  	cc := &http.Client{Transport: rt}
   153  	req, err := http.NewRequest("POST", url, bytes.NewReader(lreq))
   154  	if err != nil {
   155  		return -1, err
   156  	}
   157  	req.Header.Set("Content-Type", "application/protobuf")
   158  	req.Cancel = ctx.Done()
   159  
   160  	resp, err := cc.Do(req)
   161  	if err != nil {
   162  		return -1, err
   163  	}
   164  	b, err := readResponse(resp)
   165  	if err != nil {
   166  		return -1, err
   167  	}
   168  
   169  	if resp.StatusCode == http.StatusRequestTimeout {
   170  		return -1, ErrLeaseHTTPTimeout
   171  	}
   172  
   173  	if resp.StatusCode == http.StatusNotFound {
   174  		return -1, lease.ErrLeaseNotFound
   175  	}
   176  
   177  	if resp.StatusCode != http.StatusOK {
   178  		return -1, fmt.Errorf("lease: unknown error(%s)", string(b))
   179  	}
   180  
   181  	lresp := &pb.LeaseKeepAliveResponse{}
   182  	if err := lresp.Unmarshal(b); err != nil {
   183  		return -1, fmt.Errorf(`lease: %v. data = "%s"`, err, string(b))
   184  	}
   185  	if lresp.ID != int64(id) {
   186  		return -1, fmt.Errorf("lease: renew id mismatch")
   187  	}
   188  	return lresp.TTL, nil
   189  }
   190  
   191  // TimeToLiveHTTP retrieves lease information of the given lease ID.
   192  func TimeToLiveHTTP(ctx context.Context, id lease.LeaseID, keys bool, url string, rt http.RoundTripper) (*leasepb.LeaseInternalResponse, error) {
   193  	// will post lreq protobuf to leader
   194  	lreq, err := (&leasepb.LeaseInternalRequest{
   195  		LeaseTimeToLiveRequest: &pb.LeaseTimeToLiveRequest{
   196  			ID:   int64(id),
   197  			Keys: keys,
   198  		},
   199  	}).Marshal()
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  
   204  	req, err := http.NewRequest("POST", url, bytes.NewReader(lreq))
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  	req.Header.Set("Content-Type", "application/protobuf")
   209  
   210  	req = req.WithContext(ctx)
   211  
   212  	cc := &http.Client{Transport: rt}
   213  	var b []byte
   214  	// buffer errc channel so that errc don't block inside the go routinue
   215  	resp, err := cc.Do(req)
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  	b, err = readResponse(resp)
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  	if resp.StatusCode == http.StatusRequestTimeout {
   224  		return nil, ErrLeaseHTTPTimeout
   225  	}
   226  	if resp.StatusCode == http.StatusNotFound {
   227  		return nil, lease.ErrLeaseNotFound
   228  	}
   229  	if resp.StatusCode != http.StatusOK {
   230  		return nil, fmt.Errorf("lease: unknown error(%s)", string(b))
   231  	}
   232  
   233  	lresp := &leasepb.LeaseInternalResponse{}
   234  	if err := lresp.Unmarshal(b); err != nil {
   235  		return nil, fmt.Errorf(`lease: %v. data = "%s"`, err, string(b))
   236  	}
   237  	if lresp.LeaseTimeToLiveResponse.ID != int64(id) {
   238  		return nil, fmt.Errorf("lease: renew id mismatch")
   239  	}
   240  	return lresp, nil
   241  }
   242  
   243  func readResponse(resp *http.Response) (b []byte, err error) {
   244  	b, err = ioutil.ReadAll(resp.Body)
   245  	httputil.GracefulClose(resp)
   246  	return
   247  }