Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/operator/operator_test.go
4094 views
1
//go:build !nonetwork && !nodocker && !race
2
3
package operator
4
5
import (
6
"context"
7
"fmt"
8
"os"
9
"path/filepath"
10
"sync"
11
"testing"
12
"time"
13
14
"github.com/go-kit/log"
15
"github.com/grafana/agent/pkg/operator/logutil"
16
"github.com/grafana/agent/pkg/util"
17
"github.com/grafana/agent/pkg/util/k8s"
18
"github.com/grafana/agent/pkg/util/subset"
19
"github.com/stretchr/testify/require"
20
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
21
"sigs.k8s.io/controller-runtime/pkg/client"
22
"sigs.k8s.io/yaml"
23
)
24
25
// TestMetricsInstance deploys a basic MetricsInstance and validates expected
26
// resources were applied.
27
func TestMetricsInstance(t *testing.T) {
28
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
29
defer cancel()
30
31
inFile := "./testdata/test-metrics-instance.in.yaml"
32
outFile := "./testdata/test-metrics-instance.out.yaml"
33
ReconcileTest(ctx, t, inFile, outFile)
34
}
35
36
func TestCustomMounts(t *testing.T) {
37
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
38
defer cancel()
39
40
inFile := "./testdata/test-custom-mounts.in.yaml"
41
outFile := "./testdata/test-custom-mounts.out.yaml"
42
ReconcileTest(ctx, t, inFile, outFile)
43
}
44
45
func TestIntegrations(t *testing.T) {
46
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
47
defer cancel()
48
49
inFile := "./testdata/test-integrations.in.yaml"
50
outFile := "./testdata/test-integrations.out.yaml"
51
ReconcileTest(ctx, t, inFile, outFile)
52
}
53
54
// ReconcileTest deploys a cluster and runs the operator against it locally. It
55
// then does the following:
56
//
57
// 1. Deploys all resources in inFile, assuming a Reconcile will retrigger from
58
// them
59
//
60
// 2. Loads the resources specified by outFile and checks if the equivalent
61
// existing resources in the cluster are subsets of the loaded outFile
62
// resources.
63
//
64
// The second step will run in a loop until the test passes or ctx is canceled.
65
//
66
// ReconcileTest cannot be used to check that the data of a Secret or a
67
// ConfigMap is a subset of expected data.
68
func ReconcileTest(ctx context.Context, t *testing.T, inFile, outFile string) {
69
t.Helper()
70
71
var wg sync.WaitGroup
72
defer wg.Wait()
73
74
ctx, cancel := context.WithCancel(ctx)
75
defer cancel()
76
77
l := util.TestLogger(t)
78
cluster := NewTestCluster(ctx, t, l)
79
80
cfg := NewTestConfig(t, cluster)
81
op, err := New(l, cfg)
82
require.NoError(t, err)
83
84
// Deploy input resources
85
resources := k8s.NewResourceSet(l, cluster)
86
defer resources.Stop()
87
require.NoError(t, resources.AddFile(ctx, inFile))
88
89
// Start the operator.
90
wg.Add(1)
91
go func() {
92
defer wg.Done()
93
err := op.Start(ctx)
94
require.NoError(t, err)
95
}()
96
97
// Load our expected resources, and then get the real resource for each and
98
// ensure that it overlaps with our expected object.
99
expectedFile, err := os.Open(outFile)
100
require.NoError(t, err)
101
defer expectedFile.Close()
102
103
expectedSet, err := k8s.ReadUnstructuredObjects(expectedFile)
104
require.NoError(t, err)
105
106
for _, expected := range expectedSet {
107
err := k8s.Wait(ctx, l, func() error {
108
var actual unstructured.Unstructured
109
actual.SetGroupVersionKind(expected.GroupVersionKind())
110
111
objKey := client.ObjectKeyFromObject(expected)
112
113
err := cluster.Client().Get(ctx, objKey, &actual)
114
if err != nil {
115
return fmt.Errorf("failed to get resource: %w", err)
116
}
117
118
expectedBytes, err := yaml.Marshal(expected)
119
if err != nil {
120
return fmt.Errorf("failed to marshal expected: %w", err)
121
}
122
123
actualBytes, err := yaml.Marshal(&actual)
124
if err != nil {
125
return fmt.Errorf("failed to marshal actual: %w", err)
126
}
127
128
err = subset.YAMLAssert(expectedBytes, actualBytes)
129
if err != nil {
130
return fmt.Errorf("assert failed for %s: %w", objKey, err)
131
}
132
return nil
133
})
134
135
require.NoError(t, err)
136
}
137
}
138
139
// NewTestCluster creates a new testing cluster. The cluster will be removed
140
// when the test completes.
141
func NewTestCluster(ctx context.Context, t *testing.T, l log.Logger) *k8s.Cluster {
142
t.Helper()
143
144
cluster, err := k8s.NewCluster(ctx, k8s.Options{})
145
require.NoError(t, err)
146
t.Cleanup(cluster.Stop)
147
148
// Apply CRDs to cluster
149
crds := k8s.NewResourceSet(l, cluster)
150
t.Cleanup(crds.Stop)
151
152
crdPaths, err := filepath.Glob("../../production/operator/crds/*.yaml")
153
require.NoError(t, err)
154
155
for _, crd := range crdPaths {
156
err := crds.AddFile(ctx, crd)
157
require.NoError(t, err)
158
}
159
160
require.NoError(t, crds.Wait(ctx), "CRDs did not get created successfully")
161
return cluster
162
}
163
164
// NewTestConfig generates a new base operator Config used for tests.
165
func NewTestConfig(t *testing.T, cluster *k8s.Cluster) *Config {
166
t.Helper()
167
168
cfg, err := NewConfig(nil)
169
require.NoError(t, err)
170
171
cfg.RestConfig = cluster.GetConfig()
172
cfg.Controller.Logger = logutil.Wrap(util.TestLogger(t))
173
174
// Listen on any port for testing purposes
175
cfg.Controller.Port = 0
176
cfg.Controller.MetricsBindAddress = "127.0.0.1:0"
177
cfg.Controller.HealthProbeBindAddress = "127.0.0.1:0"
178
179
return cfg
180
}
181
182