consul/agent/config_endpoint_test.go

821 lines
20 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
[COMPLIANCE] License changes (#18443) * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
2023-08-11 13:12:13 +00:00
// SPDX-License-Identifier: BUSL-1.1
package agent
import (
"bytes"
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/testrpc"
)
func TestConfigEndpointsFailInV2(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, `experiments = ["resource-apis"]`)
checkRequest := func(method, url string) {
t.Run(method+" "+url, func(t *testing.T) {
assertV1CatalogEndpointDoesNotWorkWithV2(t, a, method, url, `{"kind":"service-defaults", "name":"web"}`)
})
}
checkRequest("GET", "/v1/config/service-defaults")
checkRequest("GET", "/v1/config/service-defaults/web")
checkRequest("DELETE", "/v1/config/service-defaults/web")
checkRequest("PUT", "/v1/config")
}
func TestConfig_Get(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
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,
},
},
},
{
Datacenter: "dc1",
Entry: &structs.MeshConfigEntry{
TransparentProxy: structs.TransparentProxyMeshConfig{
MeshDestinationsOnly: true,
},
Meta: map[string]string{
"key1": "value1",
"key2": "value2",
},
},
},
}
for _, req := range reqs {
Centralized Config CLI (#5731) * Add HTTP endpoints for config entry management * Finish implementing decoding in the HTTP Config entry apply endpoint * Add CAS operation to the config entry apply endpoint Also use this for the bootstrapping and move the config entry decoding function into the structs package. * First pass at the API client for the config entries * Fixup some of the ConfigEntry APIs Return a singular response object instead of a list for the ConfigEntry.Get RPC. This gets plumbed through the HTTP API as well. Dont return QueryMeta in the JSON response for the config entry listing HTTP API. Instead just return a list of config entries. * Minor API client fixes * Attempt at some ConfigEntry api client tests These don’t currently work due to weak typing in JSON * Get some of the api client tests passing * Implement reflectwalk magic to correct JSON encoding a ProxyConfigEntry Also added a test for the HTTP endpoint that exposes the problem. However, since the test doesn’t actually do the JSON encode/decode its still failing. * Move MapWalk magic into a binary marshaller instead of JSON. * Add a MapWalk test * Get rid of unused func * Get rid of unused imports * Fixup some tests now that the decoding from msgpack coerces things into json compat types * Stub out most of the central config cli Fully implement the config read command. * Basic config delete command implementation * Implement config write command * Implement config list subcommand Not entirely sure about the output here. Its basically the read output indented with a line specifying the kind/name of each type which is also duplicated in the indented output. * Update command usage * Update some help usage formatting * Add the connect enable helper cli command * Update list command output * Rename the config entry API client methods. * Use renamed apis * Implement config write tests Stub the others with the noTabs tests. * Change list output format Now just simply output 1 line per named config * Add config read tests * Add invalid args write test. * Add config delete tests * Add config list tests * Add connect enable tests * Update some CLI commands to use CAS ops This also modifies the HTTP API for a write op to return a boolean indicating whether the value was written or not. * Fix up the HTTP API CAS tests as I realized they weren’t testing what they should. * Update config entry rpc tests to properly test CAS * Fix up a few more tests * Fix some tests that using ConfigEntries.Apply * Update config_write_test.go * Get rid of unused import
2019-04-30 23:27:16 +00:00
out := false
require.NoError(t, a.RPC(context.Background(), "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)
})
t.Run("get the single mesh config", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/v1/config/mesh/mesh", nil)
resp := httptest.NewRecorder()
obj, err := a.srv.Config(resp, req)
require.NoError(t, err)
ce, ok := obj.(*structs.MeshConfigEntry)
require.True(t, ok, "wrong type %T", obj)
// Set indexes and EnterpriseMeta to expected values for assertions
ce.CreateIndex = 12
ce.ModifyIndex = 13
ce.Hash = 0
ce.EnterpriseMeta = acl.EnterpriseMeta{}
out, err := a.srv.marshalJSON(req, obj)
require.NoError(t, err)
expected := `
{
"Kind": "mesh",
"TransparentProxy": {
"MeshDestinationsOnly": true
},
"Meta":{
"key1": "value1",
"key2": "value2"
},
"CreateIndex": 12,
"ModifyIndex": 13
}
`
require.JSONEq(t, expected, string(out))
})
}
func TestConfig_Delete(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
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",
},
},
}
for _, req := range reqs {
Centralized Config CLI (#5731) * Add HTTP endpoints for config entry management * Finish implementing decoding in the HTTP Config entry apply endpoint * Add CAS operation to the config entry apply endpoint Also use this for the bootstrapping and move the config entry decoding function into the structs package. * First pass at the API client for the config entries * Fixup some of the ConfigEntry APIs Return a singular response object instead of a list for the ConfigEntry.Get RPC. This gets plumbed through the HTTP API as well. Dont return QueryMeta in the JSON response for the config entry listing HTTP API. Instead just return a list of config entries. * Minor API client fixes * Attempt at some ConfigEntry api client tests These don’t currently work due to weak typing in JSON * Get some of the api client tests passing * Implement reflectwalk magic to correct JSON encoding a ProxyConfigEntry Also added a test for the HTTP endpoint that exposes the problem. However, since the test doesn’t actually do the JSON encode/decode its still failing. * Move MapWalk magic into a binary marshaller instead of JSON. * Add a MapWalk test * Get rid of unused func * Get rid of unused imports * Fixup some tests now that the decoding from msgpack coerces things into json compat types * Stub out most of the central config cli Fully implement the config read command. * Basic config delete command implementation * Implement config write command * Implement config list subcommand Not entirely sure about the output here. Its basically the read output indented with a line specifying the kind/name of each type which is also duplicated in the indented output. * Update command usage * Update some help usage formatting * Add the connect enable helper cli command * Update list command output * Rename the config entry API client methods. * Use renamed apis * Implement config write tests Stub the others with the noTabs tests. * Change list output format Now just simply output 1 line per named config * Add config read tests * Add invalid args write test. * Add config delete tests * Add config list tests * Add connect enable tests * Update some CLI commands to use CAS ops This also modifies the HTTP API for a write op to return a boolean indicating whether the value was written or not. * Fix up the HTTP API CAS tests as I realized they weren’t testing what they should. * Update config entry rpc tests to properly test CAS * Fix up a few more tests * Fix some tests that using ConfigEntries.Apply * Update config_write_test.go * Get rid of unused import
2019-04-30 23:27:16 +00:00
out := false
require.NoError(t, a.RPC(context.Background(), "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(t, err)
}
// Get the remaining entry.
{
args := structs.ConfigEntryQuery{
Kind: structs.ServiceDefaults,
Datacenter: "dc1",
}
var out structs.IndexedConfigEntries
require.NoError(t, a.RPC(context.Background(), "ConfigEntry.List", &args, &out))
require.Equal(t, structs.ServiceDefaults, out.Kind)
require.Len(t, out.Entries, 1)
entry := out.Entries[0].(*structs.ServiceConfigEntry)
require.Equal(t, entry.Name, "foo")
}
}
func TestConfig_Delete_CAS(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
a := NewTestAgent(t, "")
defer a.Shutdown()
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
// Create a config entry.
entry := &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "foo",
}
var created bool
require.NoError(t, a.RPC(context.Background(), "ConfigEntry.Apply", &structs.ConfigEntryRequest{
Datacenter: "dc1",
Entry: entry,
}, &created))
require.True(t, created)
// Read it back to get its ModifyIndex.
var out structs.ConfigEntryResponse
require.NoError(t, a.RPC(context.Background(), "ConfigEntry.Get", &structs.ConfigEntryQuery{
Datacenter: "dc1",
Kind: entry.Kind,
Name: entry.Name,
}, &out))
require.NotNil(t, out.Entry)
modifyIndex := out.Entry.GetRaftIndex().ModifyIndex
t.Run("attempt to delete with an invalid index", func(t *testing.T) {
req := httptest.NewRequest(
"DELETE",
fmt.Sprintf("/v1/config/%s/%s?cas=%d", entry.Kind, entry.Name, modifyIndex-1),
nil,
)
rawRsp, err := a.srv.Config(httptest.NewRecorder(), req)
require.NoError(t, err)
deleted, isBool := rawRsp.(bool)
require.True(t, isBool, "response should be a boolean")
require.False(t, deleted, "entry should not have been deleted")
// Verify it was not deleted.
var out structs.ConfigEntryResponse
require.NoError(t, a.RPC(context.Background(), "ConfigEntry.Get", &structs.ConfigEntryQuery{
Datacenter: "dc1",
Kind: entry.Kind,
Name: entry.Name,
}, &out))
require.NotNil(t, out.Entry)
})
t.Run("attempt to delete with a valid index", func(t *testing.T) {
req := httptest.NewRequest(
"DELETE",
fmt.Sprintf("/v1/config/%s/%s?cas=%d", entry.Kind, entry.Name, modifyIndex),
nil,
)
rawRsp, err := a.srv.Config(httptest.NewRecorder(), req)
require.NoError(t, err)
deleted, isBool := rawRsp.(bool)
require.True(t, isBool, "response should be a boolean")
require.True(t, deleted, "entry should have been deleted")
// Verify it was deleted.
var out structs.ConfigEntryResponse
require.NoError(t, a.RPC(context.Background(), "ConfigEntry.Get", &structs.ConfigEntryQuery{
Datacenter: "dc1",
Kind: entry.Kind,
Name: entry.Name,
}, &out))
require.Nil(t, out.Entry)
})
}
func TestConfig_Apply(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
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(t, 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(t, a.RPC(context.Background(), "ConfigEntry.Get", &args, &out))
require.NotNil(t, out.Entry)
entry := out.Entry.(*structs.ServiceConfigEntry)
require.Equal(t, entry.Name, "foo")
}
}
func TestConfig_Apply_TerminatingGateway(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
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(context.Background(), "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",
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
},
{
Name: "api",
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
},
}
require.Equal(t, expect, got.Services)
}
}
func TestConfig_Apply_IngressGateway(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
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(context.Background(), "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
got.Hash = 0
expect := &structs.IngressGatewayConfigEntry{
Name: "ingress",
Kind: structs.IngressGateway,
Listeners: []structs.IngressListener{
{
Port: 8080,
Protocol: "tcp",
Services: []structs.IngressService{
{
Name: "web",
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
},
},
},
},
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
}
require.Equal(t, expect, got)
}
}
func TestConfig_Apply_ProxyDefaultsMeshGateway(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
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(context.Background(), "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) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
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(t, 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(t, a.RPC(context.Background(), "ConfigEntry.Get", &args, out))
require.NotNil(t, out.Entry)
entry := out.Entry.(*structs.ServiceConfigEntry)
Centralized Config CLI (#5731) * Add HTTP endpoints for config entry management * Finish implementing decoding in the HTTP Config entry apply endpoint * Add CAS operation to the config entry apply endpoint Also use this for the bootstrapping and move the config entry decoding function into the structs package. * First pass at the API client for the config entries * Fixup some of the ConfigEntry APIs Return a singular response object instead of a list for the ConfigEntry.Get RPC. This gets plumbed through the HTTP API as well. Dont return QueryMeta in the JSON response for the config entry listing HTTP API. Instead just return a list of config entries. * Minor API client fixes * Attempt at some ConfigEntry api client tests These don’t currently work due to weak typing in JSON * Get some of the api client tests passing * Implement reflectwalk magic to correct JSON encoding a ProxyConfigEntry Also added a test for the HTTP endpoint that exposes the problem. However, since the test doesn’t actually do the JSON encode/decode its still failing. * Move MapWalk magic into a binary marshaller instead of JSON. * Add a MapWalk test * Get rid of unused func * Get rid of unused imports * Fixup some tests now that the decoding from msgpack coerces things into json compat types * Stub out most of the central config cli Fully implement the config read command. * Basic config delete command implementation * Implement config write command * Implement config list subcommand Not entirely sure about the output here. Its basically the read output indented with a line specifying the kind/name of each type which is also duplicated in the indented output. * Update command usage * Update some help usage formatting * Add the connect enable helper cli command * Update list command output * Rename the config entry API client methods. * Use renamed apis * Implement config write tests Stub the others with the noTabs tests. * Change list output format Now just simply output 1 line per named config * Add config read tests * Add invalid args write test. * Add config delete tests * Add config list tests * Add connect enable tests * Update some CLI commands to use CAS ops This also modifies the HTTP API for a write op to return a boolean indicating whether the value was written or not. * Fix up the HTTP API CAS tests as I realized they weren’t testing what they should. * Update config entry rpc tests to properly test CAS * Fix up a few more tests * Fix some tests that using ConfigEntries.Apply * Update config_write_test.go * Get rid of unused import
2019-04-30 23:27:16 +00:00
body = bytes.NewBuffer([]byte(`
{
"Kind": "service-defaults",
"Name": "foo",
"Protocol": "udp"
}
`))
req, _ = http.NewRequest("PUT", "/v1/config?cas=0", body)
resp = httptest.NewRecorder()
Centralized Config CLI (#5731) * Add HTTP endpoints for config entry management * Finish implementing decoding in the HTTP Config entry apply endpoint * Add CAS operation to the config entry apply endpoint Also use this for the bootstrapping and move the config entry decoding function into the structs package. * First pass at the API client for the config entries * Fixup some of the ConfigEntry APIs Return a singular response object instead of a list for the ConfigEntry.Get RPC. This gets plumbed through the HTTP API as well. Dont return QueryMeta in the JSON response for the config entry listing HTTP API. Instead just return a list of config entries. * Minor API client fixes * Attempt at some ConfigEntry api client tests These don’t currently work due to weak typing in JSON * Get some of the api client tests passing * Implement reflectwalk magic to correct JSON encoding a ProxyConfigEntry Also added a test for the HTTP endpoint that exposes the problem. However, since the test doesn’t actually do the JSON encode/decode its still failing. * Move MapWalk magic into a binary marshaller instead of JSON. * Add a MapWalk test * Get rid of unused func * Get rid of unused imports * Fixup some tests now that the decoding from msgpack coerces things into json compat types * Stub out most of the central config cli Fully implement the config read command. * Basic config delete command implementation * Implement config write command * Implement config list subcommand Not entirely sure about the output here. Its basically the read output indented with a line specifying the kind/name of each type which is also duplicated in the indented output. * Update command usage * Update some help usage formatting * Add the connect enable helper cli command * Update list command output * Rename the config entry API client methods. * Use renamed apis * Implement config write tests Stub the others with the noTabs tests. * Change list output format Now just simply output 1 line per named config * Add config read tests * Add invalid args write test. * Add config delete tests * Add config list tests * Add connect enable tests * Update some CLI commands to use CAS ops This also modifies the HTTP API for a write op to return a boolean indicating whether the value was written or not. * Fix up the HTTP API CAS tests as I realized they weren’t testing what they should. * Update config entry rpc tests to properly test CAS * Fix up a few more tests * Fix some tests that using ConfigEntries.Apply * Update config_write_test.go * Get rid of unused import
2019-04-30 23:27:16 +00:00
writtenRaw, err := a.srv.ConfigApply(resp, req)
require.NoError(t, err)
Centralized Config CLI (#5731) * Add HTTP endpoints for config entry management * Finish implementing decoding in the HTTP Config entry apply endpoint * Add CAS operation to the config entry apply endpoint Also use this for the bootstrapping and move the config entry decoding function into the structs package. * First pass at the API client for the config entries * Fixup some of the ConfigEntry APIs Return a singular response object instead of a list for the ConfigEntry.Get RPC. This gets plumbed through the HTTP API as well. Dont return QueryMeta in the JSON response for the config entry listing HTTP API. Instead just return a list of config entries. * Minor API client fixes * Attempt at some ConfigEntry api client tests These don’t currently work due to weak typing in JSON * Get some of the api client tests passing * Implement reflectwalk magic to correct JSON encoding a ProxyConfigEntry Also added a test for the HTTP endpoint that exposes the problem. However, since the test doesn’t actually do the JSON encode/decode its still failing. * Move MapWalk magic into a binary marshaller instead of JSON. * Add a MapWalk test * Get rid of unused func * Get rid of unused imports * Fixup some tests now that the decoding from msgpack coerces things into json compat types * Stub out most of the central config cli Fully implement the config read command. * Basic config delete command implementation * Implement config write command * Implement config list subcommand Not entirely sure about the output here. Its basically the read output indented with a line specifying the kind/name of each type which is also duplicated in the indented output. * Update command usage * Update some help usage formatting * Add the connect enable helper cli command * Update list command output * Rename the config entry API client methods. * Use renamed apis * Implement config write tests Stub the others with the noTabs tests. * Change list output format Now just simply output 1 line per named config * Add config read tests * Add invalid args write test. * Add config delete tests * Add config list tests * Add connect enable tests * Update some CLI commands to use CAS ops This also modifies the HTTP API for a write op to return a boolean indicating whether the value was written or not. * Fix up the HTTP API CAS tests as I realized they weren’t testing what they should. * Update config entry rpc tests to properly test CAS * Fix up a few more tests * Fix some tests that using ConfigEntries.Apply * Update config_write_test.go * Get rid of unused import
2019-04-30 23:27:16 +00:00
written, ok := writtenRaw.(bool)
require.True(t, ok)
require.False(t, written)
require.EqualValues(t, 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()
Centralized Config CLI (#5731) * Add HTTP endpoints for config entry management * Finish implementing decoding in the HTTP Config entry apply endpoint * Add CAS operation to the config entry apply endpoint Also use this for the bootstrapping and move the config entry decoding function into the structs package. * First pass at the API client for the config entries * Fixup some of the ConfigEntry APIs Return a singular response object instead of a list for the ConfigEntry.Get RPC. This gets plumbed through the HTTP API as well. Dont return QueryMeta in the JSON response for the config entry listing HTTP API. Instead just return a list of config entries. * Minor API client fixes * Attempt at some ConfigEntry api client tests These don’t currently work due to weak typing in JSON * Get some of the api client tests passing * Implement reflectwalk magic to correct JSON encoding a ProxyConfigEntry Also added a test for the HTTP endpoint that exposes the problem. However, since the test doesn’t actually do the JSON encode/decode its still failing. * Move MapWalk magic into a binary marshaller instead of JSON. * Add a MapWalk test * Get rid of unused func * Get rid of unused imports * Fixup some tests now that the decoding from msgpack coerces things into json compat types * Stub out most of the central config cli Fully implement the config read command. * Basic config delete command implementation * Implement config write command * Implement config list subcommand Not entirely sure about the output here. Its basically the read output indented with a line specifying the kind/name of each type which is also duplicated in the indented output. * Update command usage * Update some help usage formatting * Add the connect enable helper cli command * Update list command output * Rename the config entry API client methods. * Use renamed apis * Implement config write tests Stub the others with the noTabs tests. * Change list output format Now just simply output 1 line per named config * Add config read tests * Add invalid args write test. * Add config delete tests * Add config list tests * Add connect enable tests * Update some CLI commands to use CAS ops This also modifies the HTTP API for a write op to return a boolean indicating whether the value was written or not. * Fix up the HTTP API CAS tests as I realized they weren’t testing what they should. * Update config entry rpc tests to properly test CAS * Fix up a few more tests * Fix some tests that using ConfigEntries.Apply * Update config_write_test.go * Get rid of unused import
2019-04-30 23:27:16 +00:00
writtenRaw, err = a.srv.ConfigApply(resp, req)
require.NoError(t, err)
Centralized Config CLI (#5731) * Add HTTP endpoints for config entry management * Finish implementing decoding in the HTTP Config entry apply endpoint * Add CAS operation to the config entry apply endpoint Also use this for the bootstrapping and move the config entry decoding function into the structs package. * First pass at the API client for the config entries * Fixup some of the ConfigEntry APIs Return a singular response object instead of a list for the ConfigEntry.Get RPC. This gets plumbed through the HTTP API as well. Dont return QueryMeta in the JSON response for the config entry listing HTTP API. Instead just return a list of config entries. * Minor API client fixes * Attempt at some ConfigEntry api client tests These don’t currently work due to weak typing in JSON * Get some of the api client tests passing * Implement reflectwalk magic to correct JSON encoding a ProxyConfigEntry Also added a test for the HTTP endpoint that exposes the problem. However, since the test doesn’t actually do the JSON encode/decode its still failing. * Move MapWalk magic into a binary marshaller instead of JSON. * Add a MapWalk test * Get rid of unused func * Get rid of unused imports * Fixup some tests now that the decoding from msgpack coerces things into json compat types * Stub out most of the central config cli Fully implement the config read command. * Basic config delete command implementation * Implement config write command * Implement config list subcommand Not entirely sure about the output here. Its basically the read output indented with a line specifying the kind/name of each type which is also duplicated in the indented output. * Update command usage * Update some help usage formatting * Add the connect enable helper cli command * Update list command output * Rename the config entry API client methods. * Use renamed apis * Implement config write tests Stub the others with the noTabs tests. * Change list output format Now just simply output 1 line per named config * Add config read tests * Add invalid args write test. * Add config delete tests * Add config list tests * Add connect enable tests * Update some CLI commands to use CAS ops This also modifies the HTTP API for a write op to return a boolean indicating whether the value was written or not. * Fix up the HTTP API CAS tests as I realized they weren’t testing what they should. * Update config entry rpc tests to properly test CAS * Fix up a few more tests * Fix some tests that using ConfigEntries.Apply * Update config_write_test.go * Get rid of unused import
2019-04-30 23:27:16 +00:00
written, ok = writtenRaw.(bool)
require.True(t, ok)
require.True(t, written)
require.EqualValues(t, 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(t, a.RPC(context.Background(), "ConfigEntry.Get", &args, out))
require.NotNil(t, out.Entry)
newEntry := out.Entry.(*structs.ServiceConfigEntry)
require.NotEqual(t, entry.GetRaftIndex(), newEntry.GetRaftIndex())
}
func TestConfig_Apply_Decoding(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
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)
require.True(t, isHTTPBadRequest(err))
require.Equal(t, "Request decoding failed: Payload does not contain a kind/Kind key at the top level", err.Error())
})
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)
require.True(t, isHTTPBadRequest(err))
require.Equal(t, "Request decoding failed: Kind value in payload is not a string", err.Error())
})
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(context.Background(), "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) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
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(context.Background(), "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)
}
}
func TestConfig_Exported_Services(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
a := NewTestAgent(t, "")
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
defer a.Shutdown()
{
// Register exported services
args := &structs.ExportedServicesConfigEntry{
Name: "default",
Services: []structs.ExportedService{
{
Name: "api",
Consumers: []structs.ServiceConsumer{
{
Peer: "east",
},
{
Peer: "west",
},
},
},
{
Name: "db",
Consumers: []structs.ServiceConsumer{
{
Peer: "east",
},
},
},
},
}
req := structs.ConfigEntryRequest{
Datacenter: "dc1",
Entry: args,
}
var configOutput bool
require.NoError(t, a.RPC(context.Background(), "ConfigEntry.Apply", &req, &configOutput))
require.True(t, configOutput)
}
t.Run("exported services", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/v1/exported-services", nil)
resp := httptest.NewRecorder()
raw, err := a.srv.ExportedServices(resp, req)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code)
services, ok := raw.([]api.ResolvedExportedService)
require.True(t, ok)
require.Len(t, services, 2)
assertIndex(t, resp)
entMeta := acl.DefaultEnterpriseMeta()
expected := []api.ResolvedExportedService{
{
Service: "api",
Partition: entMeta.PartitionOrEmpty(),
Namespace: entMeta.NamespaceOrEmpty(),
Consumers: api.ResolvedConsumers{
Peers: []string{"east", "west"},
},
},
{
Service: "db",
Partition: entMeta.PartitionOrEmpty(),
Namespace: entMeta.NamespaceOrEmpty(),
Consumers: api.ResolvedConsumers{
Peers: []string{"east"},
},
},
}
require.Equal(t, expected, services)
})
}