2023-04-10 15:36:59 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2020-10-02 18:23:30 +00:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-11-05 18:04:18 +00:00
|
|
|
"encoding/json"
|
2020-10-02 18:23:30 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2020-11-05 18:04:18 +00:00
|
|
|
"github.com/hashicorp/nomad/api/internal/testutil"
|
2021-03-23 17:55:34 +00:00
|
|
|
"github.com/mitchellh/mapstructure"
|
2023-01-01 18:57:26 +00:00
|
|
|
"github.com/shoenig/test/must"
|
2020-10-02 18:23:30 +00:00
|
|
|
)
|
|
|
|
|
2022-10-19 12:12:23 +00:00
|
|
|
func TestTopic_String(t *testing.T) {
|
|
|
|
testutil.Parallel(t)
|
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
inputTopic Topic
|
|
|
|
expectedOutput string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
inputTopic: TopicDeployment,
|
|
|
|
expectedOutput: "Deployment",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
inputTopic: TopicEvaluation,
|
|
|
|
expectedOutput: "Evaluation",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
inputTopic: TopicAllocation,
|
|
|
|
expectedOutput: "Allocation",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
inputTopic: TopicJob,
|
|
|
|
expectedOutput: "Job",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
inputTopic: TopicNode,
|
|
|
|
expectedOutput: "Node",
|
|
|
|
},
|
2023-06-06 14:14:47 +00:00
|
|
|
{
|
|
|
|
inputTopic: TopicNodePool,
|
|
|
|
expectedOutput: "NodePool",
|
|
|
|
},
|
2022-10-19 12:12:23 +00:00
|
|
|
{
|
|
|
|
inputTopic: TopicService,
|
|
|
|
expectedOutput: "Service",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
inputTopic: TopicAll,
|
|
|
|
expectedOutput: "*",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.expectedOutput, func(t *testing.T) {
|
|
|
|
actualOutput := tc.inputTopic.String()
|
2023-01-01 18:57:26 +00:00
|
|
|
must.Eq(t, tc.expectedOutput, actualOutput)
|
2022-10-19 12:12:23 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-02 18:23:30 +00:00
|
|
|
func TestEvent_Stream(t *testing.T) {
|
2022-03-17 13:34:57 +00:00
|
|
|
testutil.Parallel(t)
|
2020-10-02 18:23:30 +00:00
|
|
|
|
|
|
|
c, s := makeClient(t, nil, nil)
|
|
|
|
defer s.Stop()
|
|
|
|
|
|
|
|
// register job to generate events
|
|
|
|
jobs := c.Jobs()
|
|
|
|
job := testJob()
|
|
|
|
resp2, _, err := jobs.Register(job, nil)
|
2023-01-01 18:57:26 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
must.NotNil(t, resp2)
|
2020-10-02 18:23:30 +00:00
|
|
|
|
|
|
|
// build event stream request
|
|
|
|
events := c.EventStream()
|
|
|
|
q := &QueryOptions{}
|
|
|
|
topics := map[Topic][]string{
|
2020-12-03 16:48:18 +00:00
|
|
|
TopicEvaluation: {"*"},
|
2020-10-02 18:23:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
2020-10-06 15:08:12 +00:00
|
|
|
streamCh, err := events.Stream(ctx, topics, 0, q)
|
2023-01-01 18:57:26 +00:00
|
|
|
must.NoError(t, err)
|
2020-10-02 18:23:30 +00:00
|
|
|
|
2020-10-08 18:27:52 +00:00
|
|
|
select {
|
|
|
|
case event := <-streamCh:
|
|
|
|
if event.Err != nil {
|
2023-01-01 18:57:26 +00:00
|
|
|
must.Unreachable(t, must.Sprintf("unexpected %v", event.Err))
|
2020-10-02 18:23:30 +00:00
|
|
|
}
|
2023-01-01 18:57:26 +00:00
|
|
|
must.Len(t, 1, event.Events)
|
|
|
|
must.Eq(t, "Evaluation", string(event.Events[0].Topic))
|
2020-10-08 18:27:52 +00:00
|
|
|
case <-time.After(5 * time.Second):
|
2023-01-01 18:57:26 +00:00
|
|
|
must.Unreachable(t, must.Sprint("failed waiting for event stream event"))
|
2020-10-02 18:23:30 +00:00
|
|
|
}
|
|
|
|
}
|
2020-10-06 15:08:12 +00:00
|
|
|
|
|
|
|
func TestEvent_Stream_Err_InvalidQueryParam(t *testing.T) {
|
2022-03-17 13:34:57 +00:00
|
|
|
testutil.Parallel(t)
|
2020-10-06 15:08:12 +00:00
|
|
|
|
|
|
|
c, s := makeClient(t, nil, nil)
|
|
|
|
defer s.Stop()
|
|
|
|
|
|
|
|
// register job to generate events
|
|
|
|
jobs := c.Jobs()
|
|
|
|
job := testJob()
|
|
|
|
resp2, _, err := jobs.Register(job, nil)
|
2023-01-01 18:57:26 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
must.NotNil(t, resp2)
|
2020-10-06 15:08:12 +00:00
|
|
|
|
|
|
|
// build event stream request
|
|
|
|
events := c.EventStream()
|
|
|
|
q := &QueryOptions{}
|
|
|
|
topics := map[Topic][]string{
|
2020-12-03 16:48:18 +00:00
|
|
|
TopicEvaluation: {"::*"},
|
2020-10-06 15:08:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
_, err = events.Stream(ctx, topics, 0, q)
|
2023-01-01 18:57:26 +00:00
|
|
|
must.ErrorContains(t, err, "Invalid key value pair")
|
2020-10-08 18:27:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestEvent_Stream_CloseCtx(t *testing.T) {
|
2022-03-17 13:34:57 +00:00
|
|
|
testutil.Parallel(t)
|
2020-10-08 18:27:52 +00:00
|
|
|
|
|
|
|
c, s := makeClient(t, nil, nil)
|
|
|
|
defer s.Stop()
|
|
|
|
|
|
|
|
// register job to generate events
|
|
|
|
jobs := c.Jobs()
|
|
|
|
job := testJob()
|
|
|
|
resp2, _, err := jobs.Register(job, nil)
|
2023-01-01 18:57:26 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
must.NotNil(t, resp2)
|
2020-10-08 18:27:52 +00:00
|
|
|
|
|
|
|
// build event stream request
|
|
|
|
events := c.EventStream()
|
|
|
|
q := &QueryOptions{}
|
|
|
|
topics := map[Topic][]string{
|
2020-12-03 16:48:18 +00:00
|
|
|
TopicEvaluation: {"*"},
|
2020-10-08 18:27:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
|
|
|
streamCh, err := events.Stream(ctx, topics, 0, q)
|
2023-01-01 18:57:26 +00:00
|
|
|
must.NoError(t, err)
|
2020-10-08 18:27:52 +00:00
|
|
|
|
|
|
|
// cancel the request
|
|
|
|
cancel()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case event, ok := <-streamCh:
|
2023-01-01 18:57:26 +00:00
|
|
|
must.False(t, ok)
|
|
|
|
must.Nil(t, event)
|
2020-10-08 18:27:52 +00:00
|
|
|
case <-time.After(5 * time.Second):
|
2023-01-01 18:57:26 +00:00
|
|
|
must.Unreachable(t, must.Sprint("failed waiting for event stream event"))
|
2020-10-08 18:27:52 +00:00
|
|
|
}
|
2020-10-06 15:08:12 +00:00
|
|
|
}
|
2020-11-05 18:04:18 +00:00
|
|
|
|
|
|
|
func TestEventStream_PayloadValue(t *testing.T) {
|
2022-03-17 13:34:57 +00:00
|
|
|
testutil.Parallel(t)
|
2020-11-05 18:04:18 +00:00
|
|
|
|
|
|
|
c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
|
|
|
|
c.DevMode = true
|
|
|
|
})
|
|
|
|
defer s.Stop()
|
|
|
|
|
|
|
|
// register job to generate events
|
|
|
|
jobs := c.Jobs()
|
|
|
|
job := testJob()
|
|
|
|
resp2, _, err := jobs.Register(job, nil)
|
2023-01-01 18:57:26 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
must.NotNil(t, resp2)
|
2020-11-05 18:04:18 +00:00
|
|
|
|
|
|
|
// build event stream request
|
|
|
|
events := c.EventStream()
|
|
|
|
q := &QueryOptions{}
|
|
|
|
topics := map[Topic][]string{
|
2020-12-03 16:48:18 +00:00
|
|
|
TopicNode: {"*"},
|
2020-11-05 18:04:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
streamCh, err := events.Stream(ctx, topics, 0, q)
|
2023-01-01 18:57:26 +00:00
|
|
|
must.NoError(t, err)
|
2020-11-05 18:04:18 +00:00
|
|
|
|
|
|
|
select {
|
|
|
|
case event := <-streamCh:
|
|
|
|
if event.Err != nil {
|
2023-01-01 18:57:26 +00:00
|
|
|
must.NoError(t, err)
|
2020-11-05 18:04:18 +00:00
|
|
|
}
|
|
|
|
for _, e := range event.Events {
|
2021-03-23 17:55:34 +00:00
|
|
|
// verify that we get a node
|
2020-11-05 18:04:18 +00:00
|
|
|
n, err := e.Node()
|
2023-01-01 18:57:26 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
must.UUIDv4(t, n.ID)
|
2021-03-23 17:55:34 +00:00
|
|
|
|
2021-03-26 11:07:15 +00:00
|
|
|
// perform a raw decoding and look for:
|
2021-03-26 17:03:15 +00:00
|
|
|
// - "ID" to make sure that raw decoding is working correctly
|
|
|
|
// - "SecretID" to make sure it's not present
|
2021-03-23 17:55:34 +00:00
|
|
|
raw := make(map[string]map[string]interface{}, 0)
|
|
|
|
cfg := &mapstructure.DecoderConfig{
|
|
|
|
Result: &raw,
|
|
|
|
}
|
|
|
|
dec, err := mapstructure.NewDecoder(cfg)
|
2023-01-01 18:57:26 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
must.NoError(t, dec.Decode(e.Payload))
|
|
|
|
must.MapContainsKeys(t, raw, []string{"Node"})
|
2021-03-23 17:55:34 +00:00
|
|
|
rawNode := raw["Node"]
|
2023-01-01 18:57:26 +00:00
|
|
|
must.Eq(t, n.ID, rawNode["ID"].(string))
|
|
|
|
must.Eq(t, "", rawNode["SecretID"])
|
2020-11-05 18:04:18 +00:00
|
|
|
}
|
|
|
|
case <-time.After(5 * time.Second):
|
2023-01-01 18:57:26 +00:00
|
|
|
must.Unreachable(t, must.Sprint("failed waiting for event stream event"))
|
2020-11-05 18:04:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestEventStream_PayloadValueHelpers(t *testing.T) {
|
2022-03-17 13:34:57 +00:00
|
|
|
testutil.Parallel(t)
|
2020-11-05 18:04:18 +00:00
|
|
|
|
|
|
|
testCases := []struct {
|
2020-12-03 16:48:18 +00:00
|
|
|
desc string
|
2020-11-05 18:04:18 +00:00
|
|
|
event Event
|
|
|
|
input []byte
|
|
|
|
err string
|
|
|
|
expectFn func(t *testing.T, event Event)
|
|
|
|
}{
|
|
|
|
{
|
2020-12-03 16:48:18 +00:00
|
|
|
desc: "deployment",
|
2020-11-05 18:04:18 +00:00
|
|
|
input: []byte(`{"Topic": "Deployment", "Payload": {"Deployment":{"ID":"some-id","JobID":"some-job-id", "TaskGroups": {"tg1": {"RequireProgressBy": "2020-11-05T11:52:54.370774000-05:00"}}}}}`),
|
|
|
|
expectFn: func(t *testing.T, event Event) {
|
|
|
|
eventTime, err := time.Parse(time.RFC3339, "2020-11-05T11:52:54.370774000-05:00")
|
2023-01-01 18:57:26 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
must.Eq(t, TopicDeployment, event.Topic)
|
2020-11-05 18:04:18 +00:00
|
|
|
|
|
|
|
d, err := event.Deployment()
|
2023-01-01 18:57:26 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
must.Eq(t, &Deployment{
|
2020-11-05 18:04:18 +00:00
|
|
|
ID: "some-id",
|
|
|
|
JobID: "some-job-id",
|
|
|
|
TaskGroups: map[string]*DeploymentState{
|
|
|
|
"tg1": {
|
|
|
|
RequireProgressBy: eventTime,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, d)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2020-12-03 16:48:18 +00:00
|
|
|
desc: "evaluation",
|
|
|
|
input: []byte(`{"Topic": "Evaluation", "Payload": {"Evaluation":{"ID":"some-id","Namespace":"some-namespace-id"}}}`),
|
2020-11-05 18:04:18 +00:00
|
|
|
expectFn: func(t *testing.T, event Event) {
|
2023-01-01 18:57:26 +00:00
|
|
|
must.Eq(t, TopicEvaluation, event.Topic)
|
2020-11-05 18:04:18 +00:00
|
|
|
eval, err := event.Evaluation()
|
2023-01-01 18:57:26 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
must.Eq(t, &Evaluation{
|
2020-11-05 18:04:18 +00:00
|
|
|
ID: "some-id",
|
|
|
|
Namespace: "some-namespace-id",
|
|
|
|
}, eval)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2020-12-03 16:48:18 +00:00
|
|
|
desc: "allocation",
|
|
|
|
input: []byte(`{"Topic": "Allocation", "Payload": {"Allocation":{"ID":"some-id","Namespace":"some-namespace-id"}}}`),
|
2020-11-05 18:04:18 +00:00
|
|
|
expectFn: func(t *testing.T, event Event) {
|
2023-01-01 18:57:26 +00:00
|
|
|
must.Eq(t, TopicAllocation, event.Topic)
|
2020-11-05 18:04:18 +00:00
|
|
|
a, err := event.Allocation()
|
2023-01-01 18:57:26 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
must.Eq(t, &Allocation{
|
2020-11-05 18:04:18 +00:00
|
|
|
ID: "some-id",
|
|
|
|
Namespace: "some-namespace-id",
|
|
|
|
}, a)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
input: []byte(`{"Topic": "Job", "Payload": {"Job":{"ID":"some-id","Namespace":"some-namespace-id"}}}`),
|
|
|
|
expectFn: func(t *testing.T, event Event) {
|
2023-01-01 18:57:26 +00:00
|
|
|
must.Eq(t, TopicJob, event.Topic)
|
2020-11-05 18:04:18 +00:00
|
|
|
j, err := event.Job()
|
2023-01-01 18:57:26 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
must.Eq(t, &Job{
|
2022-08-17 16:26:34 +00:00
|
|
|
ID: pointerOf("some-id"),
|
|
|
|
Namespace: pointerOf("some-namespace-id"),
|
2020-11-05 18:04:18 +00:00
|
|
|
}, j)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2020-12-03 16:48:18 +00:00
|
|
|
desc: "node",
|
2020-11-05 18:04:18 +00:00
|
|
|
input: []byte(`{"Topic": "Node", "Payload": {"Node":{"ID":"some-id","Datacenter":"some-dc-id"}}}`),
|
|
|
|
expectFn: func(t *testing.T, event Event) {
|
2023-01-01 18:57:26 +00:00
|
|
|
must.Eq(t, TopicNode, event.Topic)
|
2020-11-05 18:04:18 +00:00
|
|
|
n, err := event.Node()
|
2023-01-01 18:57:26 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
must.Eq(t, &Node{
|
2020-11-05 18:04:18 +00:00
|
|
|
ID: "some-id",
|
|
|
|
Datacenter: "some-dc-id",
|
|
|
|
}, n)
|
|
|
|
},
|
|
|
|
},
|
2023-06-06 14:14:47 +00:00
|
|
|
{
|
|
|
|
desc: "node_pool",
|
|
|
|
input: []byte(`{"Topic":"NodePool","Payload":{"NodePool":{"Description":"prod pool","Name":"prod"}}}`),
|
|
|
|
expectFn: func(t *testing.T, event Event) {
|
|
|
|
must.Eq(t, TopicNodePool, event.Topic)
|
|
|
|
n, err := event.NodePool()
|
|
|
|
must.NoError(t, err)
|
|
|
|
must.Eq(t, &NodePool{
|
|
|
|
Name: "prod",
|
|
|
|
Description: "prod pool",
|
|
|
|
}, n)
|
|
|
|
},
|
|
|
|
},
|
2022-04-05 07:26:02 +00:00
|
|
|
{
|
|
|
|
desc: "service",
|
|
|
|
input: []byte(`{"Topic": "Service", "Payload": {"Service":{"ID":"some-service-id","Namespace":"some-service-namespace-id","Datacenter":"us-east-1a"}}}`),
|
|
|
|
expectFn: func(t *testing.T, event Event) {
|
2023-01-01 18:57:26 +00:00
|
|
|
must.Eq(t, TopicService, event.Topic)
|
2022-04-05 07:26:02 +00:00
|
|
|
a, err := event.Service()
|
2023-01-01 18:57:26 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
must.Eq(t, "us-east-1a", a.Datacenter)
|
|
|
|
must.Eq(t, "some-service-id", a.ID)
|
|
|
|
must.Eq(t, "some-service-namespace-id", a.Namespace)
|
2022-04-05 07:26:02 +00:00
|
|
|
},
|
|
|
|
},
|
2020-11-05 18:04:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
2020-12-03 16:48:18 +00:00
|
|
|
t.Run(tc.desc, func(t *testing.T) {
|
2020-11-05 18:04:18 +00:00
|
|
|
var out Event
|
|
|
|
err := json.Unmarshal(tc.input, &out)
|
2023-01-01 18:57:26 +00:00
|
|
|
must.NoError(t, err)
|
2020-11-05 18:04:18 +00:00
|
|
|
tc.expectFn(t, out)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|