Add Connect agent, catalog and health endpoints to api Client

This commit is contained in:
Paul Banks 2018-03-26 16:51:43 +01:00 committed by Mitchell Hashimoto
parent 1985655dff
commit 894ee3c5b0
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
7 changed files with 262 additions and 3 deletions

View File

@ -464,7 +464,7 @@ type ServiceKind string
const ( const (
// ServiceKindTypical is a typical, classic Consul service. This is // ServiceKindTypical is a typical, classic Consul service. This is
// represented by the absense of a value. This was chosen for ease of // represented by the absence of a value. This was chosen for ease of
// backwards compatibility: existing services in the catalog would // backwards compatibility: existing services in the catalog would
// default to the typical service. // default to the typical service.
ServiceKindTypical ServiceKind = "" ServiceKindTypical ServiceKind = ""

View File

@ -5,6 +5,22 @@ import (
"fmt" "fmt"
) )
// ServiceKind is the kind of service being registered.
type ServiceKind string
const (
// ServiceKindTypical is a typical, classic Consul service. This is
// represented by the absence of a value. This was chosen for ease of
// backwards compatibility: existing services in the catalog would
// default to the typical service.
ServiceKindTypical ServiceKind = ""
// ServiceKindConnectProxy is a proxy for the Connect feature. This
// service proxies another service within Consul and speaks the connect
// protocol.
ServiceKindConnectProxy ServiceKind = "connect-proxy"
)
// AgentCheck represents a check known to the agent // AgentCheck represents a check known to the agent
type AgentCheck struct { type AgentCheck struct {
Node string Node string
@ -20,6 +36,7 @@ type AgentCheck struct {
// AgentService represents a service known to the agent // AgentService represents a service known to the agent
type AgentService struct { type AgentService struct {
Kind ServiceKind
ID string ID string
Service string Service string
Tags []string Tags []string
@ -29,6 +46,7 @@ type AgentService struct {
EnableTagOverride bool EnableTagOverride bool
CreateIndex uint64 CreateIndex uint64
ModifyIndex uint64 ModifyIndex uint64
ProxyDestination string
} }
// AgentMember represents a cluster member known to the agent // AgentMember represents a cluster member known to the agent
@ -61,6 +79,7 @@ type MembersOpts struct {
// AgentServiceRegistration is used to register a new service // AgentServiceRegistration is used to register a new service
type AgentServiceRegistration struct { type AgentServiceRegistration struct {
Kind ServiceKind `json:",omitempty"`
ID string `json:",omitempty"` ID string `json:",omitempty"`
Name string `json:",omitempty"` Name string `json:",omitempty"`
Tags []string `json:",omitempty"` Tags []string `json:",omitempty"`
@ -70,6 +89,7 @@ type AgentServiceRegistration struct {
Meta map[string]string `json:",omitempty"` Meta map[string]string `json:",omitempty"`
Check *AgentServiceCheck Check *AgentServiceCheck
Checks AgentServiceChecks Checks AgentServiceChecks
ProxyDestination string `json:",omitempty"`
} }
// AgentCheckRegistration is used to register a new check // AgentCheckRegistration is used to register a new check

View File

@ -185,6 +185,51 @@ func TestAPI_AgentServices(t *testing.T) {
} }
} }
func TestAPI_AgentServices_ConnectProxy(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
agent := c.Agent()
// Register service
reg := &AgentServiceRegistration{
Name: "foo",
Port: 8000,
}
if err := agent.ServiceRegister(reg); err != nil {
t.Fatalf("err: %v", err)
}
// Register proxy
reg = &AgentServiceRegistration{
Kind: ServiceKindConnectProxy,
Name: "foo-proxy",
Port: 8001,
ProxyDestination: "foo",
}
if err := agent.ServiceRegister(reg); err != nil {
t.Fatalf("err: %v", err)
}
services, err := agent.Services()
if err != nil {
t.Fatalf("err: %v", err)
}
if _, ok := services["foo"]; !ok {
t.Fatalf("missing service: %v", services)
}
if _, ok := services["foo-proxy"]; !ok {
t.Fatalf("missing proxy service: %v", services)
}
if err := agent.ServiceDeregister("foo"); err != nil {
t.Fatalf("err: %v", err)
}
if err := agent.ServiceDeregister("foo-proxy"); err != nil {
t.Fatalf("err: %v", err)
}
}
func TestAPI_AgentServices_CheckPassing(t *testing.T) { func TestAPI_AgentServices_CheckPassing(t *testing.T) {
t.Parallel() t.Parallel()
c, s := makeClient(t) c, s := makeClient(t)

View File

@ -156,7 +156,20 @@ func (c *Catalog) Services(q *QueryOptions) (map[string][]string, *QueryMeta, er
// Service is used to query catalog entries for a given service // Service is used to query catalog entries for a given service
func (c *Catalog) Service(service, tag string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) { func (c *Catalog) Service(service, tag string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
r := c.c.newRequest("GET", "/v1/catalog/service/"+service) return c.service(service, tag, q, false)
}
// Connect is used to query catalog entries for a given Connect-enabled service
func (c *Catalog) Connect(service, tag string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
return c.service(service, tag, q, true)
}
func (c *Catalog) service(service, tag string, q *QueryOptions, connect bool) ([]*CatalogService, *QueryMeta, error) {
path := "/v1/catalog/service/" + service
if connect {
path = "/v1/catalog/connect/" + service
}
r := c.c.newRequest("GET", path)
r.setQueryOptions(q) r.setQueryOptions(q)
if tag != "" { if tag != "" {
r.params.Set("tag", tag) r.params.Set("tag", tag)

View File

@ -186,6 +186,7 @@ func TestAPI_CatalogService(t *testing.T) {
defer s.Stop() defer s.Stop()
catalog := c.Catalog() catalog := c.Catalog()
retry.Run(t, func(r *retry.R) { retry.Run(t, func(r *retry.R) {
services, meta, err := catalog.Service("consul", "", nil) services, meta, err := catalog.Service("consul", "", nil)
if err != nil { if err != nil {
@ -235,6 +236,80 @@ func TestAPI_CatalogService_NodeMetaFilter(t *testing.T) {
}) })
} }
func TestAPI_CatalogConnect(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
catalog := c.Catalog()
// Register service and proxy instances to test against.
service := &AgentService{
ID: "redis1",
Service: "redis",
Port: 8000,
}
proxy := &AgentService{
Kind: ServiceKindConnectProxy,
ProxyDestination: "redis",
ID: "redis-proxy1",
Service: "redis-proxy",
Port: 8001,
}
check := &AgentCheck{
Node: "foobar",
CheckID: "service:redis1",
Name: "Redis health check",
Notes: "Script based health check",
Status: HealthPassing,
ServiceID: "redis1",
}
reg := &CatalogRegistration{
Datacenter: "dc1",
Node: "foobar",
Address: "192.168.10.10",
Service: service,
Check: check,
}
proxyReg := &CatalogRegistration{
Datacenter: "dc1",
Node: "foobar",
Address: "192.168.10.10",
Service: proxy,
}
retry.Run(t, func(r *retry.R) {
if _, err := catalog.Register(reg, nil); err != nil {
r.Fatal(err)
}
if _, err := catalog.Register(proxyReg, nil); err != nil {
r.Fatal(err)
}
services, meta, err := catalog.Connect("redis", "", nil)
if err != nil {
r.Fatal(err)
}
if meta.LastIndex == 0 {
r.Fatalf("Bad: %v", meta)
}
if len(services) == 0 {
r.Fatalf("Bad: %v", services)
}
if services[0].Datacenter != "dc1" {
r.Fatalf("Bad datacenter: %v", services[0])
}
if services[0].ServicePort != proxy.Port {
r.Fatalf("Returned port should be for proxy: %v", services[0])
}
})
}
func TestAPI_CatalogNode(t *testing.T) { func TestAPI_CatalogNode(t *testing.T) {
t.Parallel() t.Parallel()
c, s := makeClient(t) c, s := makeClient(t)
@ -297,10 +372,28 @@ func TestAPI_CatalogRegistration(t *testing.T) {
Service: service, Service: service,
Check: check, Check: check,
} }
// Register a connect proxy for that service too
proxy := &AgentService{
ID: "redis-proxy1",
Service: "redis-proxy",
Port: 8001,
Kind: ServiceKindConnectProxy,
ProxyDestination: service.ID,
}
proxyReg := &CatalogRegistration{
Datacenter: "dc1",
Node: "foobar",
Address: "192.168.10.10",
NodeMeta: map[string]string{"somekey": "somevalue"},
Service: proxy,
}
retry.Run(t, func(r *retry.R) { retry.Run(t, func(r *retry.R) {
if _, err := catalog.Register(reg, nil); err != nil { if _, err := catalog.Register(reg, nil); err != nil {
r.Fatal(err) r.Fatal(err)
} }
if _, err := catalog.Register(proxyReg, nil); err != nil {
r.Fatal(err)
}
node, _, err := catalog.Node("foobar", nil) node, _, err := catalog.Node("foobar", nil)
if err != nil { if err != nil {
@ -311,6 +404,10 @@ func TestAPI_CatalogRegistration(t *testing.T) {
r.Fatal("missing service: redis1") r.Fatal("missing service: redis1")
} }
if _, ok := node.Services["redis-proxy1"]; !ok {
r.Fatal("missing service: redis-proxy1")
}
health, _, err := c.Health().Node("foobar", nil) health, _, err := c.Health().Node("foobar", nil)
if err != nil { if err != nil {
r.Fatal(err) r.Fatal(err)
@ -333,10 +430,22 @@ func TestAPI_CatalogRegistration(t *testing.T) {
ServiceID: "redis1", ServiceID: "redis1",
} }
// ... and proxy
deregProxy := &CatalogDeregistration{
Datacenter: "dc1",
Node: "foobar",
Address: "192.168.10.10",
ServiceID: "redis-proxy1",
}
if _, err := catalog.Deregister(dereg, nil); err != nil { if _, err := catalog.Deregister(dereg, nil); err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
if _, err := catalog.Deregister(deregProxy, nil); err != nil {
t.Fatalf("err: %v", err)
}
retry.Run(t, func(r *retry.R) { retry.Run(t, func(r *retry.R) {
node, _, err := catalog.Node("foobar", nil) node, _, err := catalog.Node("foobar", nil)
if err != nil { if err != nil {
@ -346,6 +455,10 @@ func TestAPI_CatalogRegistration(t *testing.T) {
if _, ok := node.Services["redis1"]; ok { if _, ok := node.Services["redis1"]; ok {
r.Fatal("ServiceID:redis1 is not deregistered") r.Fatal("ServiceID:redis1 is not deregistered")
} }
if _, ok := node.Services["redis-proxy1"]; ok {
r.Fatal("ServiceID:redis-proxy1 is not deregistered")
}
}) })
// Test deregistration of the previously registered check // Test deregistration of the previously registered check

View File

@ -159,7 +159,24 @@ func (h *Health) Checks(service string, q *QueryOptions) (HealthChecks, *QueryMe
// for a given service. It can optionally do server-side filtering on a tag // for a given service. It can optionally do server-side filtering on a tag
// or nodes with passing health checks only. // or nodes with passing health checks only.
func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) { func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
r := h.c.newRequest("GET", "/v1/health/service/"+service) return h.service(service, tag, passingOnly, q, false)
}
// Connect is equivalent to Service except that it will only return services
// which are Connect-enabled and will returns the connection address for Connect
// client's to use which may be a proxy in front of the named service. TODO: If
// passingOnly is true only instances where both the service and any proxy are
// healthy will be returned.
func (h *Health) Connect(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
return h.service(service, tag, passingOnly, q, true)
}
func (h *Health) service(service, tag string, passingOnly bool, q *QueryOptions, connect bool) ([]*ServiceEntry, *QueryMeta, error) {
path := "/v1/health/service/" + service
if connect {
path = "/v1/health/connect/" + service
}
r := h.c.newRequest("GET", path)
r.setQueryOptions(q) r.setQueryOptions(q)
if tag != "" { if tag != "" {
r.params.Set("tag", tag) r.params.Set("tag", tag)

View File

@ -7,6 +7,7 @@ import (
"github.com/hashicorp/consul/testutil" "github.com/hashicorp/consul/testutil"
"github.com/hashicorp/consul/testutil/retry" "github.com/hashicorp/consul/testutil/retry"
"github.com/pascaldekloe/goe/verify" "github.com/pascaldekloe/goe/verify"
"github.com/stretchr/testify/require"
) )
func TestAPI_HealthNode(t *testing.T) { func TestAPI_HealthNode(t *testing.T) {
@ -282,6 +283,56 @@ func TestAPI_HealthService(t *testing.T) {
}) })
} }
func TestAPI_HealthConnect(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
agent := c.Agent()
health := c.Health()
// Make a service with a proxy
reg := &AgentServiceRegistration{
Name: "foo",
Port: 8000,
}
err := agent.ServiceRegister(reg)
require.Nil(t, err)
defer agent.ServiceDeregister("foo")
// Register the proxy
proxyReg := &AgentServiceRegistration{
Name: "foo-proxy",
Port: 8001,
Kind: ServiceKindConnectProxy,
ProxyDestination: "foo",
}
err = agent.ServiceRegister(proxyReg)
require.Nil(t, err)
defer agent.ServiceDeregister("foo-proxy")
retry.Run(t, func(r *retry.R) {
services, meta, err := health.Connect("foo", "", true, nil)
if err != nil {
r.Fatal(err)
}
if meta.LastIndex == 0 {
r.Fatalf("bad: %v", meta)
}
// Should be exactly 1 service - the original shouldn't show up as a connect
// endpoint, only it's proxy.
if len(services) != 1 {
r.Fatalf("Bad: %v", services)
}
if services[0].Node.Datacenter != "dc1" {
r.Fatalf("Bad datacenter: %v", services[0].Node)
}
if services[0].Service.Port != proxyReg.Port {
r.Fatalf("Bad port: %v", services[0])
}
})
}
func TestAPI_HealthService_NodeMetaFilter(t *testing.T) { func TestAPI_HealthService_NodeMetaFilter(t *testing.T) {
t.Parallel() t.Parallel()
meta := map[string]string{"somekey": "somevalue"} meta := map[string]string{"somekey": "somevalue"}