Add Connect agent, catalog and health endpoints to api Client
This commit is contained in:
parent
1985655dff
commit
894ee3c5b0
|
@ -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 = ""
|
||||||
|
|
20
api/agent.go
20
api/agent.go
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"}
|
||||||
|
|
Loading…
Reference in New Issue