github.imxd.top/hashicorp/consul@v1.4.5/agent/consul/health_endpoint.go (about) 1 package consul 2 3 import ( 4 "fmt" 5 "sort" 6 7 "github.com/armon/go-metrics" 8 "github.com/hashicorp/consul/agent/consul/state" 9 "github.com/hashicorp/consul/agent/structs" 10 "github.com/hashicorp/go-memdb" 11 ) 12 13 // Health endpoint is used to query the health information 14 type Health struct { 15 srv *Server 16 } 17 18 // ChecksInState is used to get all the checks in a given state 19 func (h *Health) ChecksInState(args *structs.ChecksInStateRequest, 20 reply *structs.IndexedHealthChecks) error { 21 if done, err := h.srv.forward("Health.ChecksInState", args, args, reply); done { 22 return err 23 } 24 25 return h.srv.blockingQuery( 26 &args.QueryOptions, 27 &reply.QueryMeta, 28 func(ws memdb.WatchSet, state *state.Store) error { 29 var index uint64 30 var checks structs.HealthChecks 31 var err error 32 if len(args.NodeMetaFilters) > 0 { 33 index, checks, err = state.ChecksInStateByNodeMeta(ws, args.State, args.NodeMetaFilters) 34 } else { 35 index, checks, err = state.ChecksInState(ws, args.State) 36 } 37 if err != nil { 38 return err 39 } 40 reply.Index, reply.HealthChecks = index, checks 41 if err := h.srv.filterACL(args.Token, reply); err != nil { 42 return err 43 } 44 return h.srv.sortNodesByDistanceFrom(args.Source, reply.HealthChecks) 45 }) 46 } 47 48 // NodeChecks is used to get all the checks for a node 49 func (h *Health) NodeChecks(args *structs.NodeSpecificRequest, 50 reply *structs.IndexedHealthChecks) error { 51 if done, err := h.srv.forward("Health.NodeChecks", args, args, reply); done { 52 return err 53 } 54 55 return h.srv.blockingQuery( 56 &args.QueryOptions, 57 &reply.QueryMeta, 58 func(ws memdb.WatchSet, state *state.Store) error { 59 index, checks, err := state.NodeChecks(ws, args.Node) 60 if err != nil { 61 return err 62 } 63 reply.Index, reply.HealthChecks = index, checks 64 return h.srv.filterACL(args.Token, reply) 65 }) 66 } 67 68 // ServiceChecks is used to get all the checks for a service 69 func (h *Health) ServiceChecks(args *structs.ServiceSpecificRequest, 70 reply *structs.IndexedHealthChecks) error { 71 // Reject if tag filtering is on 72 if args.TagFilter { 73 return fmt.Errorf("Tag filtering is not supported") 74 } 75 76 // Potentially forward 77 if done, err := h.srv.forward("Health.ServiceChecks", args, args, reply); done { 78 return err 79 } 80 81 return h.srv.blockingQuery( 82 &args.QueryOptions, 83 &reply.QueryMeta, 84 func(ws memdb.WatchSet, state *state.Store) error { 85 var index uint64 86 var checks structs.HealthChecks 87 var err error 88 if len(args.NodeMetaFilters) > 0 { 89 index, checks, err = state.ServiceChecksByNodeMeta(ws, args.ServiceName, args.NodeMetaFilters) 90 } else { 91 index, checks, err = state.ServiceChecks(ws, args.ServiceName) 92 } 93 if err != nil { 94 return err 95 } 96 reply.Index, reply.HealthChecks = index, checks 97 if err := h.srv.filterACL(args.Token, reply); err != nil { 98 return err 99 } 100 return h.srv.sortNodesByDistanceFrom(args.Source, reply.HealthChecks) 101 }) 102 } 103 104 // ServiceNodes returns all the nodes registered as part of a service including health info 105 func (h *Health) ServiceNodes(args *structs.ServiceSpecificRequest, reply *structs.IndexedCheckServiceNodes) error { 106 if done, err := h.srv.forward("Health.ServiceNodes", args, args, reply); done { 107 return err 108 } 109 110 // Verify the arguments 111 if args.ServiceName == "" { 112 return fmt.Errorf("Must provide service name") 113 } 114 115 // Determine the function we'll call 116 var f func(memdb.WatchSet, *state.Store, *structs.ServiceSpecificRequest) (uint64, structs.CheckServiceNodes, error) 117 switch { 118 case args.Connect: 119 f = h.serviceNodesConnect 120 case args.TagFilter: 121 f = h.serviceNodesTagFilter 122 default: 123 f = h.serviceNodesDefault 124 } 125 126 // If we're doing a connect query, we need read access to the service 127 // we're trying to find proxies for, so check that. 128 if args.Connect { 129 // Fetch the ACL token, if any. 130 rule, err := h.srv.ResolveToken(args.Token) 131 if err != nil { 132 return err 133 } 134 135 if rule != nil && !rule.ServiceRead(args.ServiceName) { 136 // Just return nil, which will return an empty response (tested) 137 return nil 138 } 139 } 140 141 err := h.srv.blockingQuery( 142 &args.QueryOptions, 143 &reply.QueryMeta, 144 func(ws memdb.WatchSet, state *state.Store) error { 145 index, nodes, err := f(ws, state, args) 146 if err != nil { 147 return err 148 } 149 150 reply.Index, reply.Nodes = index, nodes 151 if len(args.NodeMetaFilters) > 0 { 152 reply.Nodes = nodeMetaFilter(args.NodeMetaFilters, reply.Nodes) 153 } 154 if err := h.srv.filterACL(args.Token, reply); err != nil { 155 return err 156 } 157 return h.srv.sortNodesByDistanceFrom(args.Source, reply.Nodes) 158 }) 159 160 // Provide some metrics 161 if err == nil { 162 // For metrics, we separate Connect-based lookups from non-Connect 163 key := "service" 164 if args.Connect { 165 key = "connect" 166 } 167 168 metrics.IncrCounterWithLabels([]string{"health", key, "query"}, 1, 169 []metrics.Label{{Name: "service", Value: args.ServiceName}}) 170 // DEPRECATED (singular-service-tag) - remove this when backwards RPC compat 171 // with 1.2.x is not required. 172 if args.ServiceTag != "" { 173 metrics.IncrCounterWithLabels([]string{"health", key, "query-tag"}, 1, 174 []metrics.Label{{Name: "service", Value: args.ServiceName}, {Name: "tag", Value: args.ServiceTag}}) 175 } 176 if len(args.ServiceTags) > 0 { 177 // Sort tags so that the metric is the same even if the request 178 // tags are in a different order 179 sort.Strings(args.ServiceTags) 180 181 labels := []metrics.Label{{Name: "service", Value: args.ServiceName}} 182 for _, tag := range args.ServiceTags { 183 labels = append(labels, metrics.Label{Name: "tag", Value: tag}) 184 } 185 metrics.IncrCounterWithLabels([]string{"health", key, "query-tags"}, 1, labels) 186 } 187 if len(reply.Nodes) == 0 { 188 metrics.IncrCounterWithLabels([]string{"health", key, "not-found"}, 1, 189 []metrics.Label{{Name: "service", Value: args.ServiceName}}) 190 } 191 } 192 return err 193 } 194 195 // The serviceNodes* functions below are the various lookup methods that 196 // can be used by the ServiceNodes endpoint. 197 198 func (h *Health) serviceNodesConnect(ws memdb.WatchSet, s *state.Store, args *structs.ServiceSpecificRequest) (uint64, structs.CheckServiceNodes, error) { 199 return s.CheckConnectServiceNodes(ws, args.ServiceName) 200 } 201 202 func (h *Health) serviceNodesTagFilter(ws memdb.WatchSet, s *state.Store, args *structs.ServiceSpecificRequest) (uint64, structs.CheckServiceNodes, error) { 203 // DEPRECATED (singular-service-tag) - remove this when backwards RPC compat 204 // with 1.2.x is not required. 205 // Agents < v1.3.0 populate the ServiceTag field. In this case, 206 // use ServiceTag instead of the ServiceTags field. 207 if args.ServiceTag != "" { 208 return s.CheckServiceTagNodes(ws, args.ServiceName, []string{args.ServiceTag}) 209 } 210 return s.CheckServiceTagNodes(ws, args.ServiceName, args.ServiceTags) 211 } 212 213 func (h *Health) serviceNodesDefault(ws memdb.WatchSet, s *state.Store, args *structs.ServiceSpecificRequest) (uint64, structs.CheckServiceNodes, error) { 214 return s.CheckServiceNodes(ws, args.ServiceName) 215 }