github.com/outbrain/consul@v1.4.5/agent/session_endpoint.go (about)

     1  package agent
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/hashicorp/consul/agent/structs"
    10  	"github.com/hashicorp/consul/types"
    11  )
    12  
    13  const (
    14  	// lockDelayMinThreshold is used to convert a numeric lock
    15  	// delay value from nanoseconds to seconds if it is below this
    16  	// threshold. Users often send a value like 5, which they assume
    17  	// is seconds, but because Go uses nanosecond granularity, ends
    18  	// up being very small. If we see a value below this threshold,
    19  	// we multiply by time.Second
    20  	lockDelayMinThreshold = 1000
    21  )
    22  
    23  // sessionCreateResponse is used to wrap the session ID
    24  type sessionCreateResponse struct {
    25  	ID string
    26  }
    27  
    28  // SessionCreate is used to create a new session
    29  func (s *HTTPServer) SessionCreate(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    30  	// Default the session to our node + serf check + release session
    31  	// invalidate behavior.
    32  	args := structs.SessionRequest{
    33  		Op: structs.SessionCreate,
    34  		Session: structs.Session{
    35  			Node:      s.agent.config.NodeName,
    36  			Checks:    []types.CheckID{structs.SerfCheckID},
    37  			LockDelay: 15 * time.Second,
    38  			Behavior:  structs.SessionKeysRelease,
    39  			TTL:       "",
    40  		},
    41  	}
    42  	s.parseDC(req, &args.Datacenter)
    43  	s.parseToken(req, &args.Token)
    44  
    45  	// Handle optional request body
    46  	if req.ContentLength > 0 {
    47  		fixup := func(raw interface{}) error {
    48  			if err := FixupLockDelay(raw); err != nil {
    49  				return err
    50  			}
    51  			if err := FixupChecks(raw, &args.Session); err != nil {
    52  				return err
    53  			}
    54  			return nil
    55  		}
    56  		if err := decodeBody(req, &args.Session, fixup); err != nil {
    57  			resp.WriteHeader(http.StatusBadRequest)
    58  			fmt.Fprintf(resp, "Request decode failed: %v", err)
    59  			return nil, nil
    60  		}
    61  	}
    62  
    63  	// Create the session, get the ID
    64  	var out string
    65  	if err := s.agent.RPC("Session.Apply", &args, &out); err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	// Format the response as a JSON object
    70  	return sessionCreateResponse{out}, nil
    71  }
    72  
    73  // FixupLockDelay is used to handle parsing the JSON body to session/create
    74  // and properly parsing out the lock delay duration value.
    75  func FixupLockDelay(raw interface{}) error {
    76  	rawMap, ok := raw.(map[string]interface{})
    77  	if !ok {
    78  		return nil
    79  	}
    80  	var key string
    81  	for k := range rawMap {
    82  		if strings.ToLower(k) == "lockdelay" {
    83  			key = k
    84  			break
    85  		}
    86  	}
    87  	if key != "" {
    88  		val := rawMap[key]
    89  		// Convert a string value into an integer
    90  		if vStr, ok := val.(string); ok {
    91  			dur, err := time.ParseDuration(vStr)
    92  			if err != nil {
    93  				return err
    94  			}
    95  			if dur < lockDelayMinThreshold {
    96  				dur = dur * time.Second
    97  			}
    98  			rawMap[key] = dur
    99  		}
   100  		// Convert low value integers into seconds
   101  		if vNum, ok := val.(float64); ok {
   102  			dur := time.Duration(vNum)
   103  			if dur < lockDelayMinThreshold {
   104  				dur = dur * time.Second
   105  			}
   106  			rawMap[key] = dur
   107  		}
   108  	}
   109  	return nil
   110  }
   111  
   112  // FixupChecks is used to handle parsing the JSON body to default-add the Serf
   113  // health check if they didn't specify any checks, but to allow an empty list
   114  // to take out the Serf health check. This behavior broke when mapstructure was
   115  // updated after 0.9.3, likely because we have a type wrapper around the string.
   116  func FixupChecks(raw interface{}, s *structs.Session) error {
   117  	rawMap, ok := raw.(map[string]interface{})
   118  	if !ok {
   119  		return nil
   120  	}
   121  	for k := range rawMap {
   122  		if strings.ToLower(k) == "checks" {
   123  			// If they supplied a checks key in the JSON, then
   124  			// remove the default entries and respect whatever they
   125  			// specified.
   126  			s.Checks = nil
   127  			return nil
   128  		}
   129  	}
   130  	return nil
   131  }
   132  
   133  // SessionDestroy is used to destroy an existing session
   134  func (s *HTTPServer) SessionDestroy(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   135  	args := structs.SessionRequest{
   136  		Op: structs.SessionDestroy,
   137  	}
   138  	s.parseDC(req, &args.Datacenter)
   139  	s.parseToken(req, &args.Token)
   140  
   141  	// Pull out the session id
   142  	args.Session.ID = strings.TrimPrefix(req.URL.Path, "/v1/session/destroy/")
   143  	if args.Session.ID == "" {
   144  		resp.WriteHeader(http.StatusBadRequest)
   145  		fmt.Fprint(resp, "Missing session")
   146  		return nil, nil
   147  	}
   148  
   149  	var out string
   150  	if err := s.agent.RPC("Session.Apply", &args, &out); err != nil {
   151  		return nil, err
   152  	}
   153  	return true, nil
   154  }
   155  
   156  // SessionRenew is used to renew the TTL on an existing TTL session
   157  func (s *HTTPServer) SessionRenew(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   158  	args := structs.SessionSpecificRequest{}
   159  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
   160  		return nil, nil
   161  	}
   162  
   163  	// Pull out the session id
   164  	args.Session = strings.TrimPrefix(req.URL.Path, "/v1/session/renew/")
   165  	if args.Session == "" {
   166  		resp.WriteHeader(http.StatusBadRequest)
   167  		fmt.Fprint(resp, "Missing session")
   168  		return nil, nil
   169  	}
   170  
   171  	var out structs.IndexedSessions
   172  	if err := s.agent.RPC("Session.Renew", &args, &out); err != nil {
   173  		return nil, err
   174  	} else if out.Sessions == nil {
   175  		resp.WriteHeader(http.StatusNotFound)
   176  		fmt.Fprintf(resp, "Session id '%s' not found", args.Session)
   177  		return nil, nil
   178  	}
   179  
   180  	return out.Sessions, nil
   181  }
   182  
   183  // SessionGet is used to get info for a particular session
   184  func (s *HTTPServer) SessionGet(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   185  	args := structs.SessionSpecificRequest{}
   186  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
   187  		return nil, nil
   188  	}
   189  
   190  	// Pull out the session id
   191  	args.Session = strings.TrimPrefix(req.URL.Path, "/v1/session/info/")
   192  	if args.Session == "" {
   193  		resp.WriteHeader(http.StatusBadRequest)
   194  		fmt.Fprint(resp, "Missing session")
   195  		return nil, nil
   196  	}
   197  
   198  	var out structs.IndexedSessions
   199  	defer setMeta(resp, &out.QueryMeta)
   200  	if err := s.agent.RPC("Session.Get", &args, &out); err != nil {
   201  		return nil, err
   202  	}
   203  
   204  	// Use empty list instead of nil
   205  	if out.Sessions == nil {
   206  		out.Sessions = make(structs.Sessions, 0)
   207  	}
   208  	return out.Sessions, nil
   209  }
   210  
   211  // SessionList is used to list all the sessions
   212  func (s *HTTPServer) SessionList(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   213  	args := structs.DCSpecificRequest{}
   214  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
   215  		return nil, nil
   216  	}
   217  
   218  	var out structs.IndexedSessions
   219  	defer setMeta(resp, &out.QueryMeta)
   220  	if err := s.agent.RPC("Session.List", &args, &out); err != nil {
   221  		return nil, err
   222  	}
   223  
   224  	// Use empty list instead of nil
   225  	if out.Sessions == nil {
   226  		out.Sessions = make(structs.Sessions, 0)
   227  	}
   228  	return out.Sessions, nil
   229  }
   230  
   231  // SessionsForNode returns all the nodes belonging to a node
   232  func (s *HTTPServer) SessionsForNode(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   233  	args := structs.NodeSpecificRequest{}
   234  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
   235  		return nil, nil
   236  	}
   237  
   238  	// Pull out the node name
   239  	args.Node = strings.TrimPrefix(req.URL.Path, "/v1/session/node/")
   240  	if args.Node == "" {
   241  		resp.WriteHeader(http.StatusBadRequest)
   242  		fmt.Fprint(resp, "Missing node name")
   243  		return nil, nil
   244  	}
   245  
   246  	var out structs.IndexedSessions
   247  	defer setMeta(resp, &out.QueryMeta)
   248  	if err := s.agent.RPC("Session.NodeSessions", &args, &out); err != nil {
   249  		return nil, err
   250  	}
   251  
   252  	// Use empty list instead of nil
   253  	if out.Sessions == nil {
   254  		out.Sessions = make(structs.Sessions, 0)
   255  	}
   256  	return out.Sessions, nil
   257  }