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.
390 lines
8.6 KiB
390 lines
8.6 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: BUSL-1.1 |
|
|
|
package agent |
|
|
|
import ( |
|
"bytes" |
|
"errors" |
|
"fmt" |
|
"net/http" |
|
"net/http/httptest" |
|
"testing" |
|
"time" |
|
|
|
"github.com/stretchr/testify/require" |
|
|
|
"github.com/hashicorp/consul/acl" |
|
"github.com/hashicorp/consul/sdk/testutil/retry" |
|
"github.com/hashicorp/consul/testrpc" |
|
) |
|
|
|
func TestEventFire(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") |
|
|
|
body := bytes.NewBuffer([]byte("test")) |
|
url := "/v1/event/fire/test?node=Node&service=foo&tag=bar" |
|
req, _ := http.NewRequest("PUT", url, body) |
|
resp := httptest.NewRecorder() |
|
obj, err := a.srv.EventFire(resp, req) |
|
if err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
|
|
event, ok := obj.(*UserEvent) |
|
if !ok { |
|
t.Fatalf("bad: %#v", obj) |
|
} |
|
|
|
if event.ID == "" { |
|
t.Fatalf("bad: %#v", event) |
|
} |
|
if event.Name != "test" { |
|
t.Fatalf("bad: %#v", event) |
|
} |
|
if string(event.Payload) != "test" { |
|
t.Fatalf("bad: %#v", event) |
|
} |
|
if event.NodeFilter != "Node" { |
|
t.Fatalf("bad: %#v", event) |
|
} |
|
if event.ServiceFilter != "foo" { |
|
t.Fatalf("bad: %#v", event) |
|
} |
|
if event.TagFilter != "bar" { |
|
t.Fatalf("bad: %#v", event) |
|
} |
|
} |
|
|
|
func TestEventFire_token(t *testing.T) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
t.Parallel() |
|
a := NewTestAgent(t, TestACLConfig()+` |
|
acl_default_policy = "deny" |
|
`) |
|
defer a.Shutdown() |
|
testrpc.WaitForLeader(t, a.RPC, "dc1") |
|
|
|
token := createToken(t, a, testEventPolicy) |
|
|
|
type tcase struct { |
|
event string |
|
allowed bool |
|
} |
|
tcases := []tcase{ |
|
{"foo", false}, |
|
{"bar", false}, |
|
{"baz", true}, |
|
} |
|
for _, c := range tcases { |
|
// Try to fire the event over the HTTP interface |
|
url := fmt.Sprintf("/v1/event/fire/%s", c.event) |
|
req, _ := http.NewRequest("PUT", url, nil) |
|
req.Header.Add("X-Consul-Token", token) |
|
resp := httptest.NewRecorder() |
|
_, err := a.srv.EventFire(resp, req) |
|
|
|
// Check the result |
|
if c.allowed { |
|
body := resp.Body.String() |
|
if acl.IsErrPermissionDenied(errors.New(body)) { |
|
t.Fatalf("bad: %s", body) |
|
} |
|
if resp.Code != 200 { |
|
t.Fatalf("bad: %d", resp.Code) |
|
} |
|
} else { |
|
if !acl.IsErrPermissionDenied(err) { |
|
t.Fatalf("bad: %s", err.Error()) |
|
} |
|
if err, ok := err.(HTTPError); ok { |
|
if err.StatusCode != 403 { |
|
t.Fatalf("Expected 403 but got %d", err.StatusCode) |
|
} |
|
} else { |
|
t.Fatalf("Expected HTTP Error %v", err) |
|
} |
|
} |
|
} |
|
} |
|
|
|
func TestEventList(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") |
|
|
|
p := &UserEvent{Name: "test"} |
|
if err := a.UserEvent("dc1", "root", p); err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
|
|
retry.Run(t, func(r *retry.R) { |
|
req, _ := http.NewRequest("GET", "/v1/event/list", nil) |
|
resp := httptest.NewRecorder() |
|
obj, err := a.srv.EventList(resp, req) |
|
if err != nil { |
|
r.Fatal(err) |
|
} |
|
|
|
list, ok := obj.([]*UserEvent) |
|
if !ok { |
|
r.Fatalf("bad: %#v", obj) |
|
} |
|
if len(list) != 1 || list[0].Name != "test" { |
|
r.Fatalf("bad: %#v", list) |
|
} |
|
header := resp.Header().Get("X-Consul-Index") |
|
if header == "" || header == "0" { |
|
r.Fatalf("bad: %#v", header) |
|
} |
|
}) |
|
} |
|
|
|
func TestEventList_Filter(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") |
|
|
|
p := &UserEvent{Name: "test"} |
|
if err := a.UserEvent("dc1", "root", p); err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
|
|
p = &UserEvent{Name: "foo"} |
|
if err := a.UserEvent("dc1", "root", p); err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
|
|
retry.Run(t, func(r *retry.R) { |
|
req, _ := http.NewRequest("GET", "/v1/event/list?name=foo", nil) |
|
resp := httptest.NewRecorder() |
|
obj, err := a.srv.EventList(resp, req) |
|
if err != nil { |
|
r.Fatal(err) |
|
} |
|
|
|
list, ok := obj.([]*UserEvent) |
|
if !ok { |
|
r.Fatalf("bad: %#v", obj) |
|
} |
|
if len(list) != 1 || list[0].Name != "foo" { |
|
r.Fatalf("bad: %#v", list) |
|
} |
|
header := resp.Header().Get("X-Consul-Index") |
|
if header == "" || header == "0" { |
|
r.Fatalf("bad: %#v", header) |
|
} |
|
}) |
|
} |
|
|
|
func TestEventList_ACLFilter(t *testing.T) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
t.Parallel() |
|
a := NewTestAgent(t, TestACLConfig()) |
|
defer a.Shutdown() |
|
testrpc.WaitForLeader(t, a.RPC, "dc1") |
|
|
|
// Fire some events. |
|
events := []*UserEvent{ |
|
{Name: "foo"}, |
|
{Name: "bar"}, |
|
} |
|
for _, e := range events { |
|
err := a.UserEvent("dc1", "root", e) |
|
require.NoError(t, err) |
|
} |
|
|
|
t.Run("no token", func(t *testing.T) { |
|
retry.Run(t, func(r *retry.R) { |
|
req := httptest.NewRequest("GET", "/v1/event/list", nil) |
|
resp := httptest.NewRecorder() |
|
|
|
obj, err := a.srv.EventList(resp, req) |
|
require.NoError(r, err) |
|
|
|
list, ok := obj.([]*UserEvent) |
|
require.True(r, ok) |
|
require.Empty(r, list) |
|
require.Empty(r, resp.Header().Get("X-Consul-Results-Filtered-By-ACLs")) |
|
}) |
|
}) |
|
|
|
t.Run("token with access to one event type", func(t *testing.T) { |
|
retry.Run(t, func(r *retry.R) { |
|
token := testCreateToken(t, a, ` |
|
event "foo" { |
|
policy = "read" |
|
} |
|
`) |
|
|
|
req := httptest.NewRequest("GET", "/v1/event/list", nil) |
|
req.Header.Add("X-Consul-Token", token) |
|
resp := httptest.NewRecorder() |
|
|
|
obj, err := a.srv.EventList(resp, req) |
|
require.NoError(r, err) |
|
|
|
list, ok := obj.([]*UserEvent) |
|
require.True(r, ok) |
|
require.Len(r, list, 1) |
|
require.Equal(r, "foo", list[0].Name) |
|
require.NotEmpty(r, resp.Header().Get("X-Consul-Results-Filtered-By-ACLs")) |
|
}) |
|
}) |
|
|
|
t.Run("root token", func(t *testing.T) { |
|
retry.Run(t, func(r *retry.R) { |
|
req := httptest.NewRequest("GET", "/v1/event/list", nil) |
|
req.Header.Add("X-Consul-Token", "root") |
|
resp := httptest.NewRecorder() |
|
|
|
obj, err := a.srv.EventList(resp, req) |
|
require.NoError(r, err) |
|
|
|
list, ok := obj.([]*UserEvent) |
|
require.True(r, ok) |
|
require.Len(r, list, 2) |
|
|
|
var names []string |
|
for _, e := range list { |
|
names = append(names, e.Name) |
|
} |
|
require.ElementsMatch(r, []string{"foo", "bar"}, names) |
|
|
|
require.Empty(r, resp.Header().Get("X-Consul-Results-Filtered-By-ACLs")) |
|
}) |
|
}) |
|
} |
|
|
|
func TestEventList_Blocking(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") |
|
|
|
p := &UserEvent{Name: "test"} |
|
if err := a.UserEvent("dc1", "root", p); err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
|
|
var index string |
|
retry.Run(t, func(r *retry.R) { |
|
req, _ := http.NewRequest("GET", "/v1/event/list", nil) |
|
resp := httptest.NewRecorder() |
|
if _, err := a.srv.EventList(resp, req); err != nil { |
|
r.Fatal(err) |
|
} |
|
header := resp.Header().Get("X-Consul-Index") |
|
if header == "" || header == "0" { |
|
r.Fatalf("bad: %#v", header) |
|
} |
|
index = header |
|
}) |
|
|
|
go func() { |
|
time.Sleep(50 * time.Millisecond) |
|
p := &UserEvent{Name: "second"} |
|
if err := a.UserEvent("dc1", "root", p); err != nil { |
|
t.Errorf("err: %v", err) |
|
} |
|
}() |
|
|
|
retry.Run(t, func(r *retry.R) { |
|
url := "/v1/event/list?index=" + index |
|
req, _ := http.NewRequest("GET", url, nil) |
|
resp := httptest.NewRecorder() |
|
obj, err := a.srv.EventList(resp, req) |
|
if err != nil { |
|
r.Fatal(err) |
|
} |
|
|
|
list, ok := obj.([]*UserEvent) |
|
if !ok { |
|
r.Fatalf("bad: %#v", obj) |
|
} |
|
if len(list) != 2 || list[1].Name != "second" { |
|
r.Fatalf("bad: %#v", list) |
|
} |
|
}) |
|
} |
|
|
|
func TestEventList_EventBufOrder(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") |
|
|
|
// Fire some events in a non-sequential order |
|
expected := &UserEvent{Name: "foo"} |
|
|
|
for _, e := range []*UserEvent{ |
|
{Name: "foo"}, |
|
{Name: "bar"}, |
|
{Name: "foo"}, |
|
expected, |
|
{Name: "bar"}, |
|
} { |
|
if err := a.UserEvent("dc1", "root", e); err != nil { |
|
t.Fatalf("err: %v", err) |
|
} |
|
} |
|
// Test that the event order is preserved when name |
|
// filtering on a list of > 1 matching event. |
|
retry.Run(t, func(r *retry.R) { |
|
url := "/v1/event/list?name=foo" |
|
req, _ := http.NewRequest("GET", url, nil) |
|
resp := httptest.NewRecorder() |
|
obj, err := a.srv.EventList(resp, req) |
|
if err != nil { |
|
r.Fatal(err) |
|
} |
|
list, ok := obj.([]*UserEvent) |
|
if !ok { |
|
r.Fatalf("bad: %#v", obj) |
|
} |
|
if len(list) != 3 || list[2].ID != expected.ID { |
|
r.Fatalf("bad: %#v", list) |
|
} |
|
}) |
|
} |
|
|
|
func TestUUIDToUint64(t *testing.T) { |
|
t.Parallel() |
|
inp := "cb9a81ad-fff6-52ac-92a7-5f70687805ec" |
|
|
|
// Output value was computed using python |
|
if uuidToUint64(inp) != 6430540886266763072 { |
|
t.Fatalf("bad") |
|
} |
|
}
|
|
|