github.com/kjdelisle/consul@v1.4.5/api/health.go (about)

     1  package api
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  )
     9  
    10  const (
    11  	// HealthAny is special, and is used as a wild card,
    12  	// not as a specific state.
    13  	HealthAny      = "any"
    14  	HealthPassing  = "passing"
    15  	HealthWarning  = "warning"
    16  	HealthCritical = "critical"
    17  	HealthMaint    = "maintenance"
    18  )
    19  
    20  const (
    21  	// NodeMaint is the special key set by a node in maintenance mode.
    22  	NodeMaint = "_node_maintenance"
    23  
    24  	// ServiceMaintPrefix is the prefix for a service in maintenance mode.
    25  	ServiceMaintPrefix = "_service_maintenance:"
    26  )
    27  
    28  // HealthCheck is used to represent a single check
    29  type HealthCheck struct {
    30  	Node        string
    31  	CheckID     string
    32  	Name        string
    33  	Status      string
    34  	Notes       string
    35  	Output      string
    36  	ServiceID   string
    37  	ServiceName string
    38  	ServiceTags []string
    39  
    40  	Definition HealthCheckDefinition
    41  
    42  	CreateIndex uint64
    43  	ModifyIndex uint64
    44  }
    45  
    46  // HealthCheckDefinition is used to store the details about
    47  // a health check's execution.
    48  type HealthCheckDefinition struct {
    49  	HTTP                                   string
    50  	Header                                 map[string][]string
    51  	Method                                 string
    52  	TLSSkipVerify                          bool
    53  	TCP                                    string
    54  	IntervalDuration                       time.Duration `json:"-"`
    55  	TimeoutDuration                        time.Duration `json:"-"`
    56  	DeregisterCriticalServiceAfterDuration time.Duration `json:"-"`
    57  
    58  	// DEPRECATED in Consul 1.4.1. Use the above time.Duration fields instead.
    59  	Interval                       ReadableDuration
    60  	Timeout                        ReadableDuration
    61  	DeregisterCriticalServiceAfter ReadableDuration
    62  }
    63  
    64  func (d *HealthCheckDefinition) MarshalJSON() ([]byte, error) {
    65  	type Alias HealthCheckDefinition
    66  	out := &struct {
    67  		Interval                       string
    68  		Timeout                        string
    69  		DeregisterCriticalServiceAfter string
    70  		*Alias
    71  	}{
    72  		Interval:                       d.Interval.String(),
    73  		Timeout:                        d.Timeout.String(),
    74  		DeregisterCriticalServiceAfter: d.DeregisterCriticalServiceAfter.String(),
    75  		Alias:                          (*Alias)(d),
    76  	}
    77  
    78  	if d.IntervalDuration != 0 {
    79  		out.Interval = d.IntervalDuration.String()
    80  	} else if d.Interval != 0 {
    81  		out.Interval = d.Interval.String()
    82  	}
    83  	if d.TimeoutDuration != 0 {
    84  		out.Timeout = d.TimeoutDuration.String()
    85  	} else if d.Timeout != 0 {
    86  		out.Timeout = d.Timeout.String()
    87  	}
    88  	if d.DeregisterCriticalServiceAfterDuration != 0 {
    89  		out.DeregisterCriticalServiceAfter = d.DeregisterCriticalServiceAfterDuration.String()
    90  	} else if d.DeregisterCriticalServiceAfter != 0 {
    91  		out.DeregisterCriticalServiceAfter = d.DeregisterCriticalServiceAfter.String()
    92  	}
    93  
    94  	return json.Marshal(out)
    95  }
    96  
    97  func (d *HealthCheckDefinition) UnmarshalJSON(data []byte) error {
    98  	type Alias HealthCheckDefinition
    99  	aux := &struct {
   100  		Interval                       string
   101  		Timeout                        string
   102  		DeregisterCriticalServiceAfter string
   103  		*Alias
   104  	}{
   105  		Alias: (*Alias)(d),
   106  	}
   107  	if err := json.Unmarshal(data, &aux); err != nil {
   108  		return err
   109  	}
   110  
   111  	// Parse the values into both the time.Duration and old ReadableDuration fields.
   112  	var err error
   113  	if aux.Interval != "" {
   114  		if d.IntervalDuration, err = time.ParseDuration(aux.Interval); err != nil {
   115  			return err
   116  		}
   117  		d.Interval = ReadableDuration(d.IntervalDuration)
   118  	}
   119  	if aux.Timeout != "" {
   120  		if d.TimeoutDuration, err = time.ParseDuration(aux.Timeout); err != nil {
   121  			return err
   122  		}
   123  		d.Timeout = ReadableDuration(d.TimeoutDuration)
   124  	}
   125  	if aux.DeregisterCriticalServiceAfter != "" {
   126  		if d.DeregisterCriticalServiceAfterDuration, err = time.ParseDuration(aux.DeregisterCriticalServiceAfter); err != nil {
   127  			return err
   128  		}
   129  		d.DeregisterCriticalServiceAfter = ReadableDuration(d.DeregisterCriticalServiceAfterDuration)
   130  	}
   131  	return nil
   132  }
   133  
   134  // HealthChecks is a collection of HealthCheck structs.
   135  type HealthChecks []*HealthCheck
   136  
   137  // AggregatedStatus returns the "best" status for the list of health checks.
   138  // Because a given entry may have many service and node-level health checks
   139  // attached, this function determines the best representative of the status as
   140  // as single string using the following heuristic:
   141  //
   142  //  maintenance > critical > warning > passing
   143  //
   144  func (c HealthChecks) AggregatedStatus() string {
   145  	var passing, warning, critical, maintenance bool
   146  	for _, check := range c {
   147  		id := string(check.CheckID)
   148  		if id == NodeMaint || strings.HasPrefix(id, ServiceMaintPrefix) {
   149  			maintenance = true
   150  			continue
   151  		}
   152  
   153  		switch check.Status {
   154  		case HealthPassing:
   155  			passing = true
   156  		case HealthWarning:
   157  			warning = true
   158  		case HealthCritical:
   159  			critical = true
   160  		default:
   161  			return ""
   162  		}
   163  	}
   164  
   165  	switch {
   166  	case maintenance:
   167  		return HealthMaint
   168  	case critical:
   169  		return HealthCritical
   170  	case warning:
   171  		return HealthWarning
   172  	case passing:
   173  		return HealthPassing
   174  	default:
   175  		return HealthPassing
   176  	}
   177  }
   178  
   179  // ServiceEntry is used for the health service endpoint
   180  type ServiceEntry struct {
   181  	Node    *Node
   182  	Service *AgentService
   183  	Checks  HealthChecks
   184  }
   185  
   186  // Health can be used to query the Health endpoints
   187  type Health struct {
   188  	c *Client
   189  }
   190  
   191  // Health returns a handle to the health endpoints
   192  func (c *Client) Health() *Health {
   193  	return &Health{c}
   194  }
   195  
   196  // Node is used to query for checks belonging to a given node
   197  func (h *Health) Node(node string, q *QueryOptions) (HealthChecks, *QueryMeta, error) {
   198  	r := h.c.newRequest("GET", "/v1/health/node/"+node)
   199  	r.setQueryOptions(q)
   200  	rtt, resp, err := requireOK(h.c.doRequest(r))
   201  	if err != nil {
   202  		return nil, nil, err
   203  	}
   204  	defer resp.Body.Close()
   205  
   206  	qm := &QueryMeta{}
   207  	parseQueryMeta(resp, qm)
   208  	qm.RequestTime = rtt
   209  
   210  	var out HealthChecks
   211  	if err := decodeBody(resp, &out); err != nil {
   212  		return nil, nil, err
   213  	}
   214  	return out, qm, nil
   215  }
   216  
   217  // Checks is used to return the checks associated with a service
   218  func (h *Health) Checks(service string, q *QueryOptions) (HealthChecks, *QueryMeta, error) {
   219  	r := h.c.newRequest("GET", "/v1/health/checks/"+service)
   220  	r.setQueryOptions(q)
   221  	rtt, resp, err := requireOK(h.c.doRequest(r))
   222  	if err != nil {
   223  		return nil, nil, err
   224  	}
   225  	defer resp.Body.Close()
   226  
   227  	qm := &QueryMeta{}
   228  	parseQueryMeta(resp, qm)
   229  	qm.RequestTime = rtt
   230  
   231  	var out HealthChecks
   232  	if err := decodeBody(resp, &out); err != nil {
   233  		return nil, nil, err
   234  	}
   235  	return out, qm, nil
   236  }
   237  
   238  // Service is used to query health information along with service info
   239  // for a given service. It can optionally do server-side filtering on a tag
   240  // or nodes with passing health checks only.
   241  func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
   242  	var tags []string
   243  	if tag != "" {
   244  		tags = []string{tag}
   245  	}
   246  	return h.service(service, tags, passingOnly, q, false)
   247  }
   248  
   249  func (h *Health) ServiceMultipleTags(service string, tags []string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
   250  	return h.service(service, tags, passingOnly, q, false)
   251  }
   252  
   253  // Connect is equivalent to Service except that it will only return services
   254  // which are Connect-enabled and will returns the connection address for Connect
   255  // client's to use which may be a proxy in front of the named service. If
   256  // passingOnly is true only instances where both the service and any proxy are
   257  // healthy will be returned.
   258  func (h *Health) Connect(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
   259  	var tags []string
   260  	if tag != "" {
   261  		tags = []string{tag}
   262  	}
   263  	return h.service(service, tags, passingOnly, q, true)
   264  }
   265  
   266  func (h *Health) ConnectMultipleTags(service string, tags []string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
   267  	return h.service(service, tags, passingOnly, q, true)
   268  }
   269  
   270  func (h *Health) service(service string, tags []string, passingOnly bool, q *QueryOptions, connect bool) ([]*ServiceEntry, *QueryMeta, error) {
   271  	path := "/v1/health/service/" + service
   272  	if connect {
   273  		path = "/v1/health/connect/" + service
   274  	}
   275  	r := h.c.newRequest("GET", path)
   276  	r.setQueryOptions(q)
   277  	if len(tags) > 0 {
   278  		for _, tag := range tags {
   279  			r.params.Add("tag", tag)
   280  		}
   281  	}
   282  	if passingOnly {
   283  		r.params.Set(HealthPassing, "1")
   284  	}
   285  	rtt, resp, err := requireOK(h.c.doRequest(r))
   286  	if err != nil {
   287  		return nil, nil, err
   288  	}
   289  	defer resp.Body.Close()
   290  
   291  	qm := &QueryMeta{}
   292  	parseQueryMeta(resp, qm)
   293  	qm.RequestTime = rtt
   294  
   295  	var out []*ServiceEntry
   296  	if err := decodeBody(resp, &out); err != nil {
   297  		return nil, nil, err
   298  	}
   299  	return out, qm, nil
   300  }
   301  
   302  // State is used to retrieve all the checks in a given state.
   303  // The wildcard "any" state can also be used for all checks.
   304  func (h *Health) State(state string, q *QueryOptions) (HealthChecks, *QueryMeta, error) {
   305  	switch state {
   306  	case HealthAny:
   307  	case HealthWarning:
   308  	case HealthCritical:
   309  	case HealthPassing:
   310  	default:
   311  		return nil, nil, fmt.Errorf("Unsupported state: %v", state)
   312  	}
   313  	r := h.c.newRequest("GET", "/v1/health/state/"+state)
   314  	r.setQueryOptions(q)
   315  	rtt, resp, err := requireOK(h.c.doRequest(r))
   316  	if err != nil {
   317  		return nil, nil, err
   318  	}
   319  	defer resp.Body.Close()
   320  
   321  	qm := &QueryMeta{}
   322  	parseQueryMeta(resp, qm)
   323  	qm.RequestTime = rtt
   324  
   325  	var out HealthChecks
   326  	if err := decodeBody(resp, &out); err != nil {
   327  		return nil, nil, err
   328  	}
   329  	return out, qm, nil
   330  }