github.com/criteo-forks/consul@v1.4.5-criteonogrpc/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.sharedBlockingQuery( 142 args, 143 reply, 144 &args.QueryOptions, 145 &reply.QueryMeta, 146 func(ws memdb.WatchSet, state *state.Store) (uint64, func(uint64, interface{}) error, error) { 147 index, nodes, err := f(ws, state, args) 148 if err != nil { 149 return 0, nil, err 150 } 151 152 return index, func(index uint64, rawReply interface{}) error { 153 reply := rawReply.(*structs.IndexedCheckServiceNodes) 154 // copy the slice to ensure requests filtering / sorting dont affect each others 155 replyNodes := make(structs.CheckServiceNodes, len(nodes)) 156 copy(replyNodes, nodes) 157 reply.Index, reply.Nodes = index, replyNodes 158 return nil 159 }, nil 160 }) 161 if err != nil { 162 return err 163 } 164 165 h.srv.setQueryMeta(&reply.QueryMeta) 166 167 if len(args.NodeMetaFilters) > 0 { 168 reply.Nodes = nodeMetaFilter(args.NodeMetaFilters, reply.Nodes) 169 } 170 if err := h.srv.filterACL(args.Token, reply); err != nil { 171 return err 172 } 173 err = h.srv.sortNodesByDistanceFrom(args.Source, reply.Nodes) 174 if err != nil { 175 return err 176 } 177 178 // Provide some metrics 179 // For metrics, we separate Connect-based lookups from non-Connect 180 key := "service" 181 if args.Connect { 182 key = "connect" 183 } 184 185 metrics.IncrCounterWithLabels([]string{"health", key, "query"}, 1, 186 []metrics.Label{{Name: "service", Value: args.ServiceName}}) 187 if args.ServiceTag != "" { 188 metrics.IncrCounterWithLabels([]string{"health", key, "query-tag"}, 1, 189 []metrics.Label{{Name: "service", Value: args.ServiceName}, {Name: "tag", Value: args.ServiceTag}}) 190 } 191 if len(args.ServiceTags) > 0 { 192 // Sort tags so that the metric is the same even if the request 193 // tags are in a different order 194 sort.Strings(args.ServiceTags) 195 196 labels := []metrics.Label{{Name: "service", Value: args.ServiceName}} 197 for _, tag := range args.ServiceTags { 198 labels = append(labels, metrics.Label{Name: "tag", Value: tag}) 199 } 200 metrics.IncrCounterWithLabels([]string{"health", key, "query-tags"}, 1, labels) 201 } 202 if len(reply.Nodes) == 0 { 203 metrics.IncrCounterWithLabels([]string{"health", key, "not-found"}, 1, 204 []metrics.Label{{Name: "service", Value: args.ServiceName}}) 205 } 206 return nil 207 } 208 209 // The serviceNodes* functions below are the various lookup methods that 210 // can be used by the ServiceNodes endpoint. 211 212 func (h *Health) serviceNodesConnect(ws memdb.WatchSet, s *state.Store, args *structs.ServiceSpecificRequest) (uint64, structs.CheckServiceNodes, error) { 213 return s.CheckConnectServiceNodes(ws, args.ServiceName) 214 } 215 216 func (h *Health) serviceNodesTagFilter(ws memdb.WatchSet, s *state.Store, args *structs.ServiceSpecificRequest) (uint64, structs.CheckServiceNodes, error) { 217 // DEPRECATED (singular-service-tag) - remove this when backwards RPC compat 218 // with 1.2.x is not required. 219 // Agents < v1.3.0 populate the ServiceTag field. In this case, 220 // use ServiceTag instead of the ServiceTags field. 221 if args.ServiceTag != "" { 222 return s.CheckServiceTagNodes(ws, args.ServiceName, []string{args.ServiceTag}) 223 } 224 return s.CheckServiceTagNodes(ws, args.ServiceName, args.ServiceTags) 225 } 226 227 func (h *Health) serviceNodesDefault(ws memdb.WatchSet, s *state.Store, args *structs.ServiceSpecificRequest) (uint64, structs.CheckServiceNodes, error) { 228 return s.CheckServiceNodes(ws, args.ServiceName) 229 }