1 package entrypoint
2
3 import (
4 "context"
5 "fmt"
6 "testing"
7
8 "github.com/stretchr/testify/assert"
9 "github.com/stretchr/testify/require"
10
11 "github.com/datawire/dlib/dgroup"
12 "github.com/datawire/dlib/dlog"
13 amb "github.com/emissary-ingress/emissary/v3/pkg/api/getambassador.io/v3alpha1"
14 "github.com/emissary-ingress/emissary/v3/pkg/consulwatch"
15 "github.com/emissary-ingress/emissary/v3/pkg/kates"
16 snapshotTypes "github.com/emissary-ingress/emissary/v3/pkg/snapshot/v1"
17 )
18
19 const manifests = `
20 ---
21 apiVersion: getambassador.io/v3alpha1
22 kind: ConsulResolver
23 metadata:
24 name: consultest-resolver
25 spec:
26 ambassador_id:
27 - consultest
28 address: consultest-consul:8500
29 datacenter: dc1
30 ---
31 apiVersion: getambassador.io/v3alpha1
32 kind: Mapping
33 name: consultest_k8s_mapping
34 prefix: /consultest_k8s/
35 service: consultest-http-k8s
36 ---
37 apiVersion: getambassador.io/v3alpha1
38 kind: TCPMapping
39 name: consultest_k8s_mapping_tcp
40 port: 3099
41 service: consultest-http-k8s
42 ---
43 apiVersion: getambassador.io/v2
44 kind: KubernetesServiceResolver
45 name: kubernetes-service
46 ---
47 apiVersion: getambassador.io/v2
48 kind: KubernetesEndpointResolver
49 name: endpoint
50 ---
51 apiVersion: getambassador.io/v3alpha1
52 kind: Mapping
53 name: consultest_consul_mapping
54 prefix: /consultest_consul/
55 service: consultest-consul-service
56 # tls: consultest-client-context # this doesn't seem to work... ambassador complains with "no private key in secret ..."
57 resolver: consultest-resolver
58 load_balancer:
59 policy: round_robin
60 ---
61 apiVersion: getambassador.io/v3alpha1
62 kind: TCPMapping
63 name: consultest_consul_mapping_tcp
64 port: 3090
65 service: consultest-consul-service-tcp
66 resolver: consultest-resolver
67 ---
68 apiVersion: getambassador.io/v3alpha1
69 kind: TLSContext
70 name: consultest-client-context
71 secret: consultest-client-cert-secret
72 `
73
74 func TestReconcile(t *testing.T) {
75 ctx, resolvers, mappings, c, tw := setup(t)
76 require.NoError(t, c.reconcile(ctx, resolvers, mappings))
77 tw.Assert(
78 "consultest-resolver.default:consultest-consul-service:watch",
79 "consultest-resolver.default:consultest-consul-service-tcp:watch",
80 )
81 extra := consulMapping{
82 Service: "foo",
83 Resolver: "consultest-resolver",
84 }
85 require.NoError(t, c.reconcile(ctx, resolvers, append(mappings, extra)))
86 tw.Assert(
87 "consultest-resolver.default:foo:watch",
88 )
89 require.NoError(t, c.reconcile(ctx, resolvers, nil))
90 tw.Assert(
91 "consultest-resolver.default:consultest-consul-service-tcp:stop",
92 "consultest-resolver.default:consultest-consul-service:stop",
93 "consultest-resolver.default:foo:stop",
94 )
95 }
96
97 func TestCleanup(t *testing.T) {
98 ctx, resolvers, mappings, c, tw := setup(t)
99 require.NoError(t, c.reconcile(ctx, resolvers, mappings))
100 tw.Assert(
101 "consultest-resolver.default:consultest-consul-service:watch",
102 "consultest-resolver.default:consultest-consul-service-tcp:watch",
103 )
104 require.NoError(t, c.cleanup(ctx))
105 tw.Assert(
106 "consultest-resolver.default:consultest-consul-service:stop",
107 "consultest-resolver.default:consultest-consul-service-tcp:stop",
108 )
109 }
110
111 func TestBootstrap(t *testing.T) {
112 ctx, resolvers, mappings, c, _ := setup(t)
113 assert.False(t, c.isBootstrapped())
114 require.NoError(t, c.reconcile(ctx, resolvers, mappings))
115 assert.False(t, c.isBootstrapped())
116
117
118
119
120 c.endpoints["consultest-consul-service"] = consulwatch.Endpoints{}
121 c.endpoints["consultest-consul-service-tcp"] = consulwatch.Endpoints{}
122 assert.True(t, c.isBootstrapped())
123 }
124
125 func setup(t *testing.T) (ctx context.Context, resolvers []*amb.ConsulResolver, mappings []consulMapping, c *consulWatcher, tw *testWatcher) {
126 var cancel context.CancelFunc
127 ctx, cancel = context.WithCancel(dlog.NewTestContext(t, false))
128 grp := dgroup.NewGroup(ctx, dgroup.GroupConfig{})
129 t.Cleanup(func() {
130 cancel()
131 assert.NoError(t, grp.Wait())
132 })
133
134 parent := &kates.Unstructured{
135 Object: map[string]interface{}{
136 "metadata": map[string]interface{}{
137 "namespace": "default",
138 "annotations": map[string]interface{}{
139 "getambassador.io/config": manifests,
140 },
141 },
142 },
143 }
144
145 objs, err := snapshotTypes.ParseAnnotationResources(parent)
146 require.NoError(t, err)
147
148 for _, obj := range objs {
149 newobj, err := snapshotTypes.ValidateAndConvertObject(ctx, obj)
150 if !assert.NoError(t, err) {
151 continue
152 }
153 switch o := newobj.(type) {
154 case *amb.ConsulResolver:
155 resolvers = append(resolvers, o)
156 case *amb.Mapping:
157 mappings = append(mappings, consulMapping{Service: o.Spec.Service, Resolver: o.Spec.Resolver})
158 case *amb.TCPMapping:
159 mappings = append(mappings, consulMapping{Service: o.Spec.Service, Resolver: o.Spec.Resolver})
160 }
161 }
162
163 assert.Equal(t, 1, len(resolvers))
164 assert.Equal(t, 4, len(mappings))
165
166 tw = &testWatcher{t: t, events: make(map[string]bool)}
167 c = newConsulWatcher(tw.Watch)
168 grp.Go("consul", c.run)
169 tw.Assert()
170
171 return
172 }
173
174 type testWatcher struct {
175 t *testing.T
176 events map[string]bool
177 }
178
179 func (tw *testWatcher) Log(event string) {
180 tw.events[event] = true
181 }
182
183 func (tw *testWatcher) Logf(format string, args ...interface{}) {
184 tw.Log(fmt.Sprintf(format, args...))
185 }
186
187 func (tw *testWatcher) Assert(events ...string) {
188 eventsMap := make(map[string]bool)
189 for _, e := range events {
190 eventsMap[e] = true
191 }
192 assert.Equal(tw.t, eventsMap, tw.events)
193 tw.events = make(map[string]bool)
194 }
195
196 func (tw *testWatcher) Watch(ctx context.Context, resolver *amb.ConsulResolver, svc string, _ chan consulwatch.Endpoints) (Stopper, error) {
197 rname := fmt.Sprintf("%s.%s", resolver.GetName(), resolver.GetNamespace())
198 tw.Logf("%s:%s:watch", rname, svc)
199 return &testStopper{watcher: tw, resolver: rname, service: svc}, nil
200 }
201
202 type testStopper struct {
203 watcher *testWatcher
204 resolver string
205 service string
206 }
207
208 func (ts *testStopper) Stop() {
209 ts.watcher.Logf("%s:%s:stop", ts.resolver, ts.service)
210 }
211
View as plain text