mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
552 lines
13 KiB
552 lines
13 KiB
package agent |
|
|
|
import ( |
|
"bytes" |
|
"fmt" |
|
"net/http" |
|
"net/http/httptest" |
|
"testing" |
|
|
|
"github.com/hashicorp/consul/agent/structs" |
|
"github.com/hashicorp/consul/testrpc" |
|
"github.com/pkg/errors" |
|
"github.com/stretchr/testify/require" |
|
) |
|
|
|
func TestConfig_Get(t *testing.T) { |
|
t.Parallel() |
|
|
|
a := NewTestAgent(t, "") |
|
defer a.Shutdown() |
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1") |
|
|
|
// Create some config entries. |
|
reqs := []structs.ConfigEntryRequest{ |
|
{ |
|
Datacenter: "dc1", |
|
Entry: &structs.ServiceConfigEntry{ |
|
Name: "foo", |
|
}, |
|
}, |
|
{ |
|
Datacenter: "dc1", |
|
Entry: &structs.ServiceConfigEntry{ |
|
Name: "bar", |
|
}, |
|
}, |
|
{ |
|
Datacenter: "dc1", |
|
Entry: &structs.ProxyConfigEntry{ |
|
Name: structs.ProxyConfigGlobal, |
|
Config: map[string]interface{}{ |
|
"foo": "bar", |
|
"bar": 1, |
|
}, |
|
}, |
|
}, |
|
} |
|
for _, req := range reqs { |
|
out := false |
|
require.NoError(t, a.RPC("ConfigEntry.Apply", &req, &out)) |
|
} |
|
|
|
t.Run("get a single service entry", func(t *testing.T) { |
|
req, _ := http.NewRequest("GET", "/v1/config/service-defaults/foo", nil) |
|
resp := httptest.NewRecorder() |
|
obj, err := a.srv.Config(resp, req) |
|
require.NoError(t, err) |
|
|
|
value := obj.(structs.ConfigEntry) |
|
require.Equal(t, structs.ServiceDefaults, value.GetKind()) |
|
entry := value.(*structs.ServiceConfigEntry) |
|
require.Equal(t, entry.Name, "foo") |
|
}) |
|
t.Run("list both service entries", func(t *testing.T) { |
|
req, _ := http.NewRequest("GET", "/v1/config/service-defaults", nil) |
|
resp := httptest.NewRecorder() |
|
obj, err := a.srv.Config(resp, req) |
|
require.NoError(t, err) |
|
|
|
value := obj.([]structs.ConfigEntry) |
|
require.Len(t, value, 2) |
|
require.Equal(t, value[0].(*structs.ServiceConfigEntry).Name, "bar") |
|
require.Equal(t, value[1].(*structs.ServiceConfigEntry).Name, "foo") |
|
}) |
|
t.Run("get global proxy config", func(t *testing.T) { |
|
req, _ := http.NewRequest("GET", "/v1/config/proxy-defaults/global", nil) |
|
resp := httptest.NewRecorder() |
|
obj, err := a.srv.Config(resp, req) |
|
require.NoError(t, err) |
|
|
|
value := obj.(structs.ConfigEntry) |
|
require.Equal(t, value.GetKind(), structs.ProxyDefaults) |
|
entry := value.(*structs.ProxyConfigEntry) |
|
require.Equal(t, structs.ProxyConfigGlobal, entry.Name) |
|
require.Contains(t, entry.Config, "foo") |
|
require.Equal(t, "bar", entry.Config["foo"]) |
|
}) |
|
t.Run("error on no arguments", func(t *testing.T) { |
|
req, _ := http.NewRequest("GET", "/v1/config/", nil) |
|
resp := httptest.NewRecorder() |
|
_, err := a.srv.Config(resp, req) |
|
require.Error(t, errors.New("Must provide either a kind or both kind and name"), err) |
|
}) |
|
} |
|
|
|
func TestConfig_Delete(t *testing.T) { |
|
t.Parallel() |
|
|
|
require := require.New(t) |
|
a := NewTestAgent(t, "") |
|
defer a.Shutdown() |
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1") |
|
|
|
// Create some config entries. |
|
reqs := []structs.ConfigEntryRequest{ |
|
{ |
|
Datacenter: "dc1", |
|
Entry: &structs.ServiceConfigEntry{ |
|
Name: "foo", |
|
}, |
|
}, |
|
{ |
|
Datacenter: "dc1", |
|
Entry: &structs.ServiceConfigEntry{ |
|
Name: "bar", |
|
}, |
|
}, |
|
} |
|
for _, req := range reqs { |
|
out := false |
|
require.NoError(a.RPC("ConfigEntry.Apply", &req, &out)) |
|
} |
|
|
|
// Delete an entry. |
|
{ |
|
req, _ := http.NewRequest("DELETE", "/v1/config/service-defaults/bar", nil) |
|
resp := httptest.NewRecorder() |
|
_, err := a.srv.Config(resp, req) |
|
require.NoError(err) |
|
} |
|
// Get the remaining entry. |
|
{ |
|
args := structs.ConfigEntryQuery{ |
|
Kind: structs.ServiceDefaults, |
|
Datacenter: "dc1", |
|
} |
|
var out structs.IndexedConfigEntries |
|
require.NoError(a.RPC("ConfigEntry.List", &args, &out)) |
|
require.Equal(structs.ServiceDefaults, out.Kind) |
|
require.Len(out.Entries, 1) |
|
entry := out.Entries[0].(*structs.ServiceConfigEntry) |
|
require.Equal(entry.Name, "foo") |
|
} |
|
} |
|
|
|
func TestConfig_Apply(t *testing.T) { |
|
t.Parallel() |
|
|
|
require := require.New(t) |
|
a := NewTestAgent(t, "") |
|
defer a.Shutdown() |
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1") |
|
|
|
// Create some config entries. |
|
body := bytes.NewBuffer([]byte(` |
|
{ |
|
"Kind": "service-defaults", |
|
"Name": "foo", |
|
"Protocol": "tcp" |
|
}`)) |
|
|
|
req, _ := http.NewRequest("PUT", "/v1/config", body) |
|
resp := httptest.NewRecorder() |
|
_, err := a.srv.ConfigApply(resp, req) |
|
require.NoError(err) |
|
if resp.Code != 200 { |
|
t.Fatalf(resp.Body.String()) |
|
} |
|
|
|
// Get the remaining entry. |
|
{ |
|
args := structs.ConfigEntryQuery{ |
|
Kind: structs.ServiceDefaults, |
|
Name: "foo", |
|
Datacenter: "dc1", |
|
} |
|
var out structs.ConfigEntryResponse |
|
require.NoError(a.RPC("ConfigEntry.Get", &args, &out)) |
|
require.NotNil(out.Entry) |
|
entry := out.Entry.(*structs.ServiceConfigEntry) |
|
require.Equal(entry.Name, "foo") |
|
} |
|
} |
|
|
|
func TestConfig_Apply_TerminatingGateway(t *testing.T) { |
|
t.Parallel() |
|
|
|
a := NewTestAgent(t, "") |
|
defer a.Shutdown() |
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1") |
|
|
|
// Create some config entries. |
|
body := bytes.NewBuffer([]byte(` |
|
{ |
|
"Kind": "terminating-gateway", |
|
"Name": "west-gw-01", |
|
"Services": [ |
|
{ |
|
"Name": "web", |
|
"CAFile": "/etc/web/ca.crt", |
|
"CertFile": "/etc/web/client.crt", |
|
"KeyFile": "/etc/web/tls.key" |
|
}, |
|
{ |
|
"Name": "api" |
|
} |
|
] |
|
}`)) |
|
|
|
req, _ := http.NewRequest("PUT", "/v1/config", body) |
|
resp := httptest.NewRecorder() |
|
_, err := a.srv.ConfigApply(resp, req) |
|
require.NoError(t, err) |
|
require.Equal(t, 200, resp.Code, "!200 Response Code: %s", resp.Body.String()) |
|
|
|
// List all entries, there should only be one |
|
{ |
|
args := structs.ConfigEntryQuery{ |
|
Kind: structs.TerminatingGateway, |
|
Datacenter: "dc1", |
|
} |
|
var out structs.IndexedConfigEntries |
|
require.NoError(t, a.RPC("ConfigEntry.List", &args, &out)) |
|
require.NotNil(t, out) |
|
require.Len(t, out.Entries, 1) |
|
|
|
got := out.Entries[0].(*structs.TerminatingGatewayConfigEntry) |
|
expect := []structs.LinkedService{ |
|
{ |
|
Name: "web", |
|
CAFile: "/etc/web/ca.crt", |
|
CertFile: "/etc/web/client.crt", |
|
KeyFile: "/etc/web/tls.key", |
|
}, |
|
{ |
|
Name: "api", |
|
}, |
|
} |
|
require.Equal(t, expect, got.Services) |
|
} |
|
} |
|
|
|
func TestConfig_Apply_IngressGateway(t *testing.T) { |
|
t.Parallel() |
|
|
|
a := NewTestAgent(t, "") |
|
defer a.Shutdown() |
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1") |
|
|
|
// Create some config entries. |
|
body := bytes.NewBuffer([]byte(` |
|
{ |
|
"Kind": "ingress-gateway", |
|
"Name": "ingress", |
|
"Listeners": [ |
|
{ |
|
"Port": 8080, |
|
"Services": [ |
|
{ "Name": "web" } |
|
] |
|
} |
|
] |
|
}`)) |
|
|
|
req, _ := http.NewRequest("PUT", "/v1/config", body) |
|
resp := httptest.NewRecorder() |
|
_, err := a.srv.ConfigApply(resp, req) |
|
require.NoError(t, err) |
|
require.Equal(t, 200, resp.Code, "!200 Response Code: %s", resp.Body.String()) |
|
|
|
// List all entries, there should only be one |
|
{ |
|
args := structs.ConfigEntryQuery{ |
|
Kind: structs.IngressGateway, |
|
Datacenter: "dc1", |
|
} |
|
var out structs.IndexedConfigEntries |
|
require.NoError(t, a.RPC("ConfigEntry.List", &args, &out)) |
|
require.NotNil(t, out) |
|
require.Len(t, out.Entries, 1) |
|
|
|
got := out.Entries[0].(*structs.IngressGatewayConfigEntry) |
|
// Ignore create and modify indices |
|
got.CreateIndex = 0 |
|
got.ModifyIndex = 0 |
|
|
|
expect := &structs.IngressGatewayConfigEntry{ |
|
Name: "ingress", |
|
Kind: structs.IngressGateway, |
|
Listeners: []structs.IngressListener{ |
|
{ |
|
Port: 8080, |
|
Protocol: "tcp", |
|
Services: []structs.IngressService{ |
|
{Name: "web"}, |
|
}, |
|
}, |
|
}, |
|
} |
|
require.Equal(t, expect, got) |
|
} |
|
} |
|
|
|
func TestConfig_Apply_ProxyDefaultsMeshGateway(t *testing.T) { |
|
t.Parallel() |
|
|
|
a := NewTestAgent(t, "") |
|
defer a.Shutdown() |
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1") |
|
|
|
// Create some config entries. |
|
body := bytes.NewBuffer([]byte(` |
|
{ |
|
"Kind": "proxy-defaults", |
|
"Name": "global", |
|
"MeshGateway": { |
|
"Mode": "local" |
|
} |
|
}`)) |
|
|
|
req, _ := http.NewRequest("PUT", "/v1/config", body) |
|
resp := httptest.NewRecorder() |
|
_, err := a.srv.ConfigApply(resp, req) |
|
require.NoError(t, err) |
|
require.Equal(t, 200, resp.Code, "!200 Response Code: %s", resp.Body.String()) |
|
|
|
// Get the remaining entry. |
|
{ |
|
args := structs.ConfigEntryQuery{ |
|
Kind: structs.ProxyDefaults, |
|
Name: "global", |
|
Datacenter: "dc1", |
|
} |
|
var out structs.ConfigEntryResponse |
|
require.NoError(t, a.RPC("ConfigEntry.Get", &args, &out)) |
|
require.NotNil(t, out.Entry) |
|
entry := out.Entry.(*structs.ProxyConfigEntry) |
|
require.Equal(t, structs.MeshGatewayModeLocal, entry.MeshGateway.Mode) |
|
} |
|
} |
|
|
|
func TestConfig_Apply_CAS(t *testing.T) { |
|
t.Parallel() |
|
|
|
require := require.New(t) |
|
a := NewTestAgent(t, "") |
|
defer a.Shutdown() |
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1") |
|
|
|
// Create some config entries. |
|
body := bytes.NewBuffer([]byte(` |
|
{ |
|
"Kind": "service-defaults", |
|
"Name": "foo", |
|
"Protocol": "tcp" |
|
}`)) |
|
|
|
req, _ := http.NewRequest("PUT", "/v1/config", body) |
|
resp := httptest.NewRecorder() |
|
_, err := a.srv.ConfigApply(resp, req) |
|
require.NoError(err) |
|
if resp.Code != 200 { |
|
t.Fatalf(resp.Body.String()) |
|
} |
|
|
|
// Get the entry remaining entry. |
|
args := structs.ConfigEntryQuery{ |
|
Kind: structs.ServiceDefaults, |
|
Name: "foo", |
|
Datacenter: "dc1", |
|
} |
|
|
|
out := &structs.ConfigEntryResponse{} |
|
require.NoError(a.RPC("ConfigEntry.Get", &args, out)) |
|
require.NotNil(out.Entry) |
|
entry := out.Entry.(*structs.ServiceConfigEntry) |
|
|
|
body = bytes.NewBuffer([]byte(` |
|
{ |
|
"Kind": "service-defaults", |
|
"Name": "foo", |
|
"Protocol": "udp" |
|
} |
|
`)) |
|
req, _ = http.NewRequest("PUT", "/v1/config?cas=0", body) |
|
resp = httptest.NewRecorder() |
|
writtenRaw, err := a.srv.ConfigApply(resp, req) |
|
require.NoError(err) |
|
written, ok := writtenRaw.(bool) |
|
require.True(ok) |
|
require.False(written) |
|
require.EqualValues(200, resp.Code, resp.Body.String()) |
|
|
|
body = bytes.NewBuffer([]byte(` |
|
{ |
|
"Kind": "service-defaults", |
|
"Name": "foo", |
|
"Protocol": "udp" |
|
} |
|
`)) |
|
req, _ = http.NewRequest("PUT", fmt.Sprintf("/v1/config?cas=%d", entry.GetRaftIndex().ModifyIndex), body) |
|
resp = httptest.NewRecorder() |
|
writtenRaw, err = a.srv.ConfigApply(resp, req) |
|
require.NoError(err) |
|
written, ok = writtenRaw.(bool) |
|
require.True(ok) |
|
require.True(written) |
|
require.EqualValues(200, resp.Code, resp.Body.String()) |
|
|
|
// Get the entry remaining entry. |
|
args = structs.ConfigEntryQuery{ |
|
Kind: structs.ServiceDefaults, |
|
Name: "foo", |
|
Datacenter: "dc1", |
|
} |
|
|
|
out = &structs.ConfigEntryResponse{} |
|
require.NoError(a.RPC("ConfigEntry.Get", &args, out)) |
|
require.NotNil(out.Entry) |
|
newEntry := out.Entry.(*structs.ServiceConfigEntry) |
|
require.NotEqual(entry.GetRaftIndex(), newEntry.GetRaftIndex()) |
|
} |
|
|
|
func TestConfig_Apply_Decoding(t *testing.T) { |
|
t.Parallel() |
|
|
|
a := NewTestAgent(t, "") |
|
defer a.Shutdown() |
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1") |
|
|
|
t.Run("No Kind", func(t *testing.T) { |
|
body := bytes.NewBuffer([]byte( |
|
`{ |
|
"Name": "foo", |
|
"Protocol": "tcp" |
|
}`)) |
|
|
|
req, _ := http.NewRequest("PUT", "/v1/config", body) |
|
resp := httptest.NewRecorder() |
|
|
|
_, err := a.srv.ConfigApply(resp, req) |
|
require.Error(t, err) |
|
badReq, ok := err.(BadRequestError) |
|
require.True(t, ok) |
|
require.Equal(t, "Request decoding failed: Payload does not contain a kind/Kind key at the top level", badReq.Reason) |
|
}) |
|
|
|
t.Run("Kind Not String", func(t *testing.T) { |
|
body := bytes.NewBuffer([]byte( |
|
`{ |
|
"Kind": 123, |
|
"Name": "foo", |
|
"Protocol": "tcp" |
|
}`)) |
|
|
|
req, _ := http.NewRequest("PUT", "/v1/config", body) |
|
resp := httptest.NewRecorder() |
|
|
|
_, err := a.srv.ConfigApply(resp, req) |
|
require.Error(t, err) |
|
badReq, ok := err.(BadRequestError) |
|
require.True(t, ok) |
|
require.Equal(t, "Request decoding failed: Kind value in payload is not a string", badReq.Reason) |
|
}) |
|
|
|
t.Run("Lowercase kind", func(t *testing.T) { |
|
body := bytes.NewBuffer([]byte( |
|
`{ |
|
"kind": "service-defaults", |
|
"Name": "foo", |
|
"Protocol": "tcp" |
|
}`)) |
|
|
|
req, _ := http.NewRequest("PUT", "/v1/config", body) |
|
resp := httptest.NewRecorder() |
|
_, err := a.srv.ConfigApply(resp, req) |
|
require.NoError(t, err) |
|
require.EqualValues(t, 200, resp.Code, resp.Body.String()) |
|
|
|
// Get the remaining entry. |
|
{ |
|
args := structs.ConfigEntryQuery{ |
|
Kind: structs.ServiceDefaults, |
|
Name: "foo", |
|
Datacenter: "dc1", |
|
} |
|
var out structs.ConfigEntryResponse |
|
require.NoError(t, a.RPC("ConfigEntry.Get", &args, &out)) |
|
require.NotNil(t, out.Entry) |
|
entry := out.Entry.(*structs.ServiceConfigEntry) |
|
require.Equal(t, entry.Name, "foo") |
|
} |
|
}) |
|
} |
|
|
|
func TestConfig_Apply_ProxyDefaultsExpose(t *testing.T) { |
|
t.Parallel() |
|
|
|
a := NewTestAgent(t, "") |
|
defer a.Shutdown() |
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1") |
|
|
|
// Create some config entries. |
|
body := bytes.NewBuffer([]byte(` |
|
{ |
|
"Kind": "proxy-defaults", |
|
"Name": "global", |
|
"Expose": { |
|
"Checks": true, |
|
"Paths": [ |
|
{ |
|
"LocalPathPort": 8080, |
|
"ListenerPort": 21500, |
|
"Path": "/healthz", |
|
"Protocol": "http2" |
|
} |
|
] |
|
} |
|
}`)) |
|
|
|
req, _ := http.NewRequest("PUT", "/v1/config", body) |
|
resp := httptest.NewRecorder() |
|
_, err := a.srv.ConfigApply(resp, req) |
|
require.NoError(t, err) |
|
require.Equal(t, 200, resp.Code, "!200 Response Code: %s", resp.Body.String()) |
|
|
|
// Get the remaining entry. |
|
{ |
|
args := structs.ConfigEntryQuery{ |
|
Kind: structs.ProxyDefaults, |
|
Name: "global", |
|
Datacenter: "dc1", |
|
} |
|
var out structs.ConfigEntryResponse |
|
require.NoError(t, a.RPC("ConfigEntry.Get", &args, &out)) |
|
require.NotNil(t, out.Entry) |
|
entry := out.Entry.(*structs.ProxyConfigEntry) |
|
|
|
expose := structs.ExposeConfig{ |
|
Checks: true, |
|
Paths: []structs.ExposePath{ |
|
{ |
|
LocalPathPort: 8080, |
|
ListenerPort: 21500, |
|
Path: "/healthz", |
|
Protocol: "http2", |
|
}, |
|
}, |
|
} |
|
require.Equal(t, expose, entry.Expose) |
|
} |
|
}
|
|
|