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

     1  package agent
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"strconv"
     7  	"strings"
     8  
     9  	cachetype "github.com/hashicorp/consul/agent/cache-types"
    10  	"github.com/hashicorp/consul/agent/structs"
    11  	"github.com/hashicorp/consul/api"
    12  )
    13  
    14  func (s *HTTPServer) HealthChecksInState(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    15  	// Set default DC
    16  	args := structs.ChecksInStateRequest{}
    17  	s.parseSource(req, &args.Source)
    18  	args.NodeMetaFilters = s.parseMetaFilter(req)
    19  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
    20  		return nil, nil
    21  	}
    22  
    23  	// Pull out the service name
    24  	args.State = strings.TrimPrefix(req.URL.Path, "/v1/health/state/")
    25  	if args.State == "" {
    26  		resp.WriteHeader(http.StatusBadRequest)
    27  		fmt.Fprint(resp, "Missing check state")
    28  		return nil, nil
    29  	}
    30  
    31  	// Make the RPC request
    32  	var out structs.IndexedHealthChecks
    33  	defer setMeta(resp, &out.QueryMeta)
    34  RETRY_ONCE:
    35  	if err := s.agent.RPC("Health.ChecksInState", &args, &out); err != nil {
    36  		return nil, err
    37  	}
    38  	if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < out.LastContact {
    39  		args.AllowStale = false
    40  		args.MaxStaleDuration = 0
    41  		goto RETRY_ONCE
    42  	}
    43  	out.ConsistencyLevel = args.QueryOptions.ConsistencyLevel()
    44  
    45  	// Use empty list instead of nil
    46  	if out.HealthChecks == nil {
    47  		out.HealthChecks = make(structs.HealthChecks, 0)
    48  	}
    49  	for i, c := range out.HealthChecks {
    50  		if c.ServiceTags == nil {
    51  			clone := *c
    52  			clone.ServiceTags = make([]string, 0)
    53  			out.HealthChecks[i] = &clone
    54  		}
    55  	}
    56  	return out.HealthChecks, nil
    57  }
    58  
    59  func (s *HTTPServer) HealthNodeChecks(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    60  	// Set default DC
    61  	args := structs.NodeSpecificRequest{}
    62  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
    63  		return nil, nil
    64  	}
    65  
    66  	// Pull out the service name
    67  	args.Node = strings.TrimPrefix(req.URL.Path, "/v1/health/node/")
    68  	if args.Node == "" {
    69  		resp.WriteHeader(http.StatusBadRequest)
    70  		fmt.Fprint(resp, "Missing node name")
    71  		return nil, nil
    72  	}
    73  
    74  	// Make the RPC request
    75  	var out structs.IndexedHealthChecks
    76  	defer setMeta(resp, &out.QueryMeta)
    77  RETRY_ONCE:
    78  	if err := s.agent.RPC("Health.NodeChecks", &args, &out); err != nil {
    79  		return nil, err
    80  	}
    81  	if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < out.LastContact {
    82  		args.AllowStale = false
    83  		args.MaxStaleDuration = 0
    84  		goto RETRY_ONCE
    85  	}
    86  	out.ConsistencyLevel = args.QueryOptions.ConsistencyLevel()
    87  
    88  	// Use empty list instead of nil
    89  	if out.HealthChecks == nil {
    90  		out.HealthChecks = make(structs.HealthChecks, 0)
    91  	}
    92  	for i, c := range out.HealthChecks {
    93  		if c.ServiceTags == nil {
    94  			clone := *c
    95  			clone.ServiceTags = make([]string, 0)
    96  			out.HealthChecks[i] = &clone
    97  		}
    98  	}
    99  	return out.HealthChecks, nil
   100  }
   101  
   102  func (s *HTTPServer) HealthServiceChecks(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   103  	// Set default DC
   104  	args := structs.ServiceSpecificRequest{}
   105  	s.parseSource(req, &args.Source)
   106  	args.NodeMetaFilters = s.parseMetaFilter(req)
   107  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
   108  		return nil, nil
   109  	}
   110  
   111  	// Pull out the service name
   112  	args.ServiceName = strings.TrimPrefix(req.URL.Path, "/v1/health/checks/")
   113  	if args.ServiceName == "" {
   114  		resp.WriteHeader(http.StatusBadRequest)
   115  		fmt.Fprint(resp, "Missing service name")
   116  		return nil, nil
   117  	}
   118  
   119  	// Make the RPC request
   120  	var out structs.IndexedHealthChecks
   121  	defer setMeta(resp, &out.QueryMeta)
   122  RETRY_ONCE:
   123  	if err := s.agent.RPC("Health.ServiceChecks", &args, &out); err != nil {
   124  		return nil, err
   125  	}
   126  	if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < out.LastContact {
   127  		args.AllowStale = false
   128  		args.MaxStaleDuration = 0
   129  		goto RETRY_ONCE
   130  	}
   131  	out.ConsistencyLevel = args.QueryOptions.ConsistencyLevel()
   132  
   133  	// Use empty list instead of nil
   134  	if out.HealthChecks == nil {
   135  		out.HealthChecks = make(structs.HealthChecks, 0)
   136  	}
   137  	for i, c := range out.HealthChecks {
   138  		if c.ServiceTags == nil {
   139  			clone := *c
   140  			clone.ServiceTags = make([]string, 0)
   141  			out.HealthChecks[i] = &clone
   142  		}
   143  	}
   144  	return out.HealthChecks, nil
   145  }
   146  
   147  func (s *HTTPServer) HealthConnectServiceNodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   148  	return s.healthServiceNodes(resp, req, true)
   149  }
   150  
   151  func (s *HTTPServer) HealthServiceNodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   152  	return s.healthServiceNodes(resp, req, false)
   153  }
   154  
   155  func (s *HTTPServer) healthServiceNodes(resp http.ResponseWriter, req *http.Request, connect bool) (interface{}, error) {
   156  	// Set default DC
   157  	args := structs.ServiceSpecificRequest{Connect: connect}
   158  	s.parseSource(req, &args.Source)
   159  	args.NodeMetaFilters = s.parseMetaFilter(req)
   160  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
   161  		return nil, nil
   162  	}
   163  
   164  	// Check for tags
   165  	params := req.URL.Query()
   166  	if _, ok := params["tag"]; ok {
   167  		args.ServiceTags = params["tag"]
   168  		args.TagFilter = true
   169  	}
   170  
   171  	// Determine the prefix
   172  	prefix := "/v1/health/service/"
   173  	if connect {
   174  		prefix = "/v1/health/connect/"
   175  	}
   176  
   177  	// Pull out the service name
   178  	args.ServiceName = strings.TrimPrefix(req.URL.Path, prefix)
   179  	if args.ServiceName == "" {
   180  		resp.WriteHeader(http.StatusBadRequest)
   181  		fmt.Fprint(resp, "Missing service name")
   182  		return nil, nil
   183  	}
   184  
   185  	// Make the RPC request
   186  	var out structs.IndexedCheckServiceNodes
   187  	defer setMeta(resp, &out.QueryMeta)
   188  
   189  	if args.QueryOptions.UseCache {
   190  		raw, m, err := s.agent.cache.Get(cachetype.HealthServicesName, &args)
   191  		if err != nil {
   192  			return nil, err
   193  		}
   194  		defer setCacheMeta(resp, &m)
   195  		reply, ok := raw.(*structs.IndexedCheckServiceNodes)
   196  		if !ok {
   197  			// This should never happen, but we want to protect against panics
   198  			return nil, fmt.Errorf("internal error: response type not correct")
   199  		}
   200  		out = *reply
   201  	} else {
   202  	RETRY_ONCE:
   203  		if err := s.agent.RPC("Health.ServiceNodes", &args, &out); err != nil {
   204  			return nil, err
   205  		}
   206  		if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < out.LastContact {
   207  			args.AllowStale = false
   208  			args.MaxStaleDuration = 0
   209  			goto RETRY_ONCE
   210  		}
   211  	}
   212  	out.ConsistencyLevel = args.QueryOptions.ConsistencyLevel()
   213  
   214  	// Filter to only passing if specified
   215  	if _, ok := params[api.HealthPassing]; ok {
   216  		val := params.Get(api.HealthPassing)
   217  		// Backwards-compat to allow users to specify ?passing without a value. This
   218  		// should be removed in Consul 0.10.
   219  		var filter bool
   220  		if val == "" {
   221  			filter = true
   222  		} else {
   223  			var err error
   224  			filter, err = strconv.ParseBool(val)
   225  			if err != nil {
   226  				resp.WriteHeader(http.StatusBadRequest)
   227  				fmt.Fprint(resp, "Invalid value for ?passing")
   228  				return nil, nil
   229  			}
   230  		}
   231  
   232  		if filter {
   233  			out.Nodes = filterNonPassing(out.Nodes)
   234  		}
   235  	}
   236  
   237  	// Translate addresses after filtering so we don't waste effort.
   238  	s.agent.TranslateAddresses(args.Datacenter, out.Nodes)
   239  
   240  	// Use empty list instead of nil
   241  	if out.Nodes == nil {
   242  		out.Nodes = make(structs.CheckServiceNodes, 0)
   243  	}
   244  	for i := range out.Nodes {
   245  		if out.Nodes[i].Checks == nil {
   246  			out.Nodes[i].Checks = make(structs.HealthChecks, 0)
   247  		}
   248  		for j, c := range out.Nodes[i].Checks {
   249  			if c.ServiceTags == nil {
   250  				clone := *c
   251  				clone.ServiceTags = make([]string, 0)
   252  				out.Nodes[i].Checks[j] = &clone
   253  			}
   254  		}
   255  		if out.Nodes[i].Service != nil && out.Nodes[i].Service.Tags == nil {
   256  			clone := *out.Nodes[i].Service
   257  			clone.Tags = make([]string, 0)
   258  			out.Nodes[i].Service = &clone
   259  		}
   260  	}
   261  	return out.Nodes, nil
   262  }
   263  
   264  // filterNonPassing is used to filter out any nodes that have check that are not passing
   265  func filterNonPassing(nodes structs.CheckServiceNodes) structs.CheckServiceNodes {
   266  	n := len(nodes)
   267  OUTER:
   268  	for i := 0; i < n; i++ {
   269  		node := nodes[i]
   270  		for _, check := range node.Checks {
   271  			if check.Status != api.HealthPassing {
   272  				nodes[i], nodes[n-1] = nodes[n-1], structs.CheckServiceNode{}
   273  				n--
   274  				i--
   275  				continue OUTER
   276  			}
   277  		}
   278  	}
   279  	return nodes[:n]
   280  }