/
flush.go
357 lines (303 loc) · 11.9 KB
/
flush.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package compliance
import (
"context"
"testing"
"github.com/openconfig/gribigo/chk"
"github.com/openconfig/gribigo/constants"
"github.com/openconfig/gribigo/fluent"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"
spb "github.com/openconfig/gribi/v1/proto/service"
)
// FlushFromMasterDefaultNI programs a chain of entries into the default NI with the
// specified wantACK mode and then issues a Flush towards the server using the current
// master's election ID. It validates that no entries remain in the default network
// instance using the Get RPC.
func FlushFromMasterDefaultNI(c *fluent.GRIBIClient, wantACK fluent.ProgrammingResult, t testing.TB, _ ...TestOpt) {
defer flushServer(c, t)
addFlushEntriesToNI(c, defaultNetworkInstanceName, wantACK, t)
// addFlushEntriesToNI increments the election ID so to check with the current value,
// we need to subtract one from the current election ID.
curID := electionID.Load() - 1
ctx := context.Background()
c.Start(ctx, t)
defer c.Stop(t)
fr, err := c.Flush().
WithElectionID(curID, 0).
WithAllNetworkInstances().
Send()
switch {
case err != nil:
t.Fatalf("got unexpected error from flush, got: %v", err)
case fr.GetResult() != spb.FlushResponse_OK:
t.Fatalf("unexpected response from flush, got: %v, want: %v", fr.GetResult().String(), spb.FlushResponse_OK.String())
}
checkNIHasNEntries(ctx, c, defaultNetworkInstanceName, 0, t)
}
// FlushFromOverrideDefaultNI programs a chain of entries into the default NI with the
// specified wantACK mode, and then issues a Flush towards the server specifying that
// the election ID should be overridden and ensures that entries are removed using the
// Get RPC.
func FlushFromOverrideDefaultNI(c *fluent.GRIBIClient, wantACK fluent.ProgrammingResult, t testing.TB, _ ...TestOpt) {
defer flushServer(c, t)
addFlushEntriesToNI(c, defaultNetworkInstanceName, wantACK, t)
ctx := context.Background()
c.Start(ctx, t)
defer c.Stop(t)
fr, err := c.Flush().
WithElectionOverride().
WithAllNetworkInstances().
Send()
switch {
case err != nil:
t.Fatalf("got unexpected error from flush, got: %v", err)
case fr.GetResult() != spb.FlushResponse_OK:
t.Fatalf("unexpected response from flush, got: %v, want: %v", fr.GetResult().String(), spb.FlushResponse_OK.String())
}
checkNIHasNEntries(ctx, c, defaultNetworkInstanceName, 0, t)
}
// FlushFromNonMasterDefaultNI sends a Flush to the server using an old election ID
// and validates that the programmed chain of entries are not removed form the
// default NI using the Get RPC.
func FlushFromNonMasterDefaultNI(c *fluent.GRIBIClient, wantACK fluent.ProgrammingResult, t testing.TB, _ ...TestOpt) {
defer flushServer(c, t)
addFlushEntriesToNI(c, defaultNetworkInstanceName, wantACK, t)
// addFlushEntriesToNI increments the election ID so to check with the current value,
// we need to subtract two to ensure that we are not the current master.
curID := electionID.Load() - 2
ctx := context.Background()
c.Start(ctx, t)
defer c.Stop(t)
_, flushErr := c.Flush().
WithElectionID(curID, 0).
WithAllNetworkInstances().
Send()
if flushErr == nil {
t.Fatalf("did not get expected error from flush, got: %v", flushErr)
}
s, ok := status.FromError(flushErr)
if !ok {
t.Fatalf("received invalid error from server, got: %v", flushErr)
}
if got, want := s.Code(), codes.FailedPrecondition; got != want {
t.Fatalf("did not get expected error from server, got code: %s (error: %v), want: %s", got, flushErr, want)
}
checkNIHasNEntries(ctx, c, defaultNetworkInstanceName, 3, t)
}
// FlushNIUnspecified sends a Flush to the server without specifying the network instsance,
// and then validates that the programmed chain of entries are not removed.
func FlushNIUnspecified(c *fluent.GRIBIClient, wantACK fluent.ProgrammingResult, t testing.TB, _ ...TestOpt) {
defer flushServer(c, t)
addFlushEntriesToNI(c, defaultNetworkInstanceName, wantACK, t)
// addFlushEntriesToNI increments the election ID so to check with the current value,
// we need to subtract one from the current election ID.
curID := electionID.Load() - 1
ctx := context.Background()
c.Start(ctx, t)
defer c.Stop(t)
_, flushErr := c.Flush().
WithElectionID(curID, 0).
Send()
if flushErr == nil {
t.Fatalf("did not get expected error from flush, got: %v", flushErr)
}
s, ok := status.FromError(flushErr)
if !ok {
t.Fatalf("received invalid error from server, got: %v", flushErr)
}
if got, want := s.Code(), codes.InvalidArgument; got != want {
t.Fatalf("did not get the expected canonical error code from server, got code: %s (error: %v), want: %s", got, flushErr, want)
}
if len(s.Details()) != 1 {
t.Fatalf("did not get 1 error detail, got: %v", s.Details())
}
gotD, ok := s.Details()[0].(*spb.FlushResponseError)
if !ok {
t.Fatalf("did not get the expected error details type, got: %T, want: *spb.FlushResponseError", s.Details()[0])
}
wantD := &spb.FlushResponseError{
Status: spb.FlushResponseError_UNSPECIFIED_NETWORK_INSTANCE,
}
if !proto.Equal(gotD, wantD) {
t.Fatalf("did not get the exact error details, got: %s, want: %s", prototext.Format(gotD), prototext.Format(wantD))
}
checkNIHasNEntries(ctx, c, defaultNetworkInstanceName, 3, t)
}
// FlushOfSpecificNI programs entries into two network-instances, the default and a named
// L3VRF. It subsequently issues a Flush RPC and ensures that entries that are within the
// flushed network instance (the default) are removed, but the others are preserved.
func FlushOfSpecificNI(c *fluent.GRIBIClient, wantACK fluent.ProgrammingResult, t testing.TB, _ ...TestOpt) {
defer flushServer(c, t)
addFlushEntriesToNI(c, defaultNetworkInstanceName, wantACK, t)
addFlushEntriesToNI(c, vrfName, wantACK, t)
// addFlushEntriesToNI increments the election ID so to check with the current value,
// we need to subtract one from the current election ID.
curID := electionID.Load() - 1
ctx := context.Background()
c.Start(ctx, t)
defer c.Stop(t)
fr, err := c.Flush().
WithElectionID(curID, 0).
WithNetworkInstance(defaultNetworkInstanceName).
Send()
switch {
case err != nil:
t.Fatalf("got unexpected error from flush, got: %v", err)
case fr.GetResult() != spb.FlushResponse_OK:
t.Fatalf("unexpected response from flush, got: %v, want: %v", fr.GetResult().String(), spb.FlushResponse_OK.String())
}
checkNIHasNEntries(ctx, c, defaultNetworkInstanceName, 0, t)
checkNIHasNEntries(ctx, c, vrfName, 3, t)
}
// FlushOfAllNIs programs entries in two network instances - the default and a
// named VRF and ensures that entries are removed from both when the Flush specifies
// that all network instances are to be removed.
func FlushOfAllNIs(c *fluent.GRIBIClient, wantACK fluent.ProgrammingResult, t testing.TB, _ ...TestOpt) {
// TODO(robjs): we need to initialise the server with >1 network instance.
t.Skip()
defer flushServer(c, t)
vrfName := "TEST-VRF"
addFlushEntriesToNI(c, defaultNetworkInstanceName, wantACK, t)
addFlushEntriesToNI(c, vrfName, wantACK, t)
// addFlushEntriesToNI increments the election ID so to check with the current value,
// we need to subtract one from the current election ID.
curID := electionID.Load() - 1
ctx := context.Background()
c.Start(ctx, t)
defer c.Stop(t)
fr, err := c.Flush().
WithElectionID(curID, 0).
WithAllNetworkInstances().
Send()
switch {
case err != nil:
t.Fatalf("got unexpected error from flush, got: %v", err)
case fr.GetResult() != spb.FlushResponse_OK:
t.Fatalf("unexpected response from flush, got: %v, want: %v", fr.GetResult().String(), spb.FlushResponse_OK.String())
}
checkNIHasNEntries(ctx, c, defaultNetworkInstanceName, 0, t)
checkNIHasNEntries(ctx, c, vrfName, 0, t)
}
// FlushPreservesDefaultNI programs entries in the default network-instance and flushes
// the non-default VRF, then ensures that entries in the default is still preserved. This
// is a weaker version of FlushOfSpecificNI.
func FlushPreservesDefaultNI(c *fluent.GRIBIClient, wantACK fluent.ProgrammingResult, t testing.TB, _ ...TestOpt) {
defer flushServer(c, t)
addFlushEntriesToNI(c, defaultNetworkInstanceName, wantACK, t)
// addFlushEntriesToNI increments the election ID so to check with the current value,
// we need to subtract one from the current election ID.
curID := electionID.Load() - 1
ctx := context.Background()
c.Start(ctx, t)
defer c.Stop(t)
fr, err := c.Flush().
WithElectionID(curID, 0).
WithNetworkInstance(vrfName).
Send()
switch {
case err != nil:
t.Fatalf("got unexpected error from flush, got: %v", err)
case fr.GetResult() != spb.FlushResponse_OK:
t.Fatalf("unexpected response from flush, got: %v, want: %v", fr.GetResult().String(), spb.FlushResponse_OK.String())
}
checkNIHasNEntries(ctx, c, defaultNetworkInstanceName, 3, t)
}
// FlushServer flushes all the state on the server, but does not validate it
// specifically. It can be called from tests that need to clean up
// a server between test cases.
func flushServer(c *fluent.GRIBIClient, t testing.TB) {
ctx := context.Background()
c.Start(ctx, t)
defer c.Stop(t)
if _, err := c.Flush().
WithElectionOverride().
WithAllNetworkInstances().
Send(); err != nil {
t.Fatalf("could not remove all entries from server, got: %v", err)
}
}
// addFlushEntriesToNI is a helper that programs a chain of IPv4->NHG->NH entries
// into the specified niName network-instance, with the specified wantACK behaviour.
// It validates that the entries were installed from the returned Modify results.
func addFlushEntriesToNI(c *fluent.GRIBIClient, niName string, wantACK fluent.ProgrammingResult, t testing.TB) {
t.Helper()
ops := []func(){
func() {
c.Modify().AddEntry(t,
fluent.NextHopEntry().
WithNetworkInstance(niName).
WithIndex(1).
WithIPAddress("192.0.2.3"))
},
func() {
c.Modify().AddEntry(t,
fluent.NextHopGroupEntry().
WithNetworkInstance(niName).
WithID(1).
AddNextHop(1, 1))
},
func() {
c.Modify().AddEntry(t,
fluent.IPv4Entry().
WithNetworkInstance(niName).
WithNextHopGroup(1).
WithPrefix("42.42.42.42/32"))
},
}
res := DoModifyOps(c, t, ops, wantACK, false)
// validate that our entries were installed correctly.
chk.HasResult(t, res,
fluent.OperationResult().
WithNextHopOperation(1).
WithOperationType(constants.Add).
WithProgrammingResult(wantACK).
AsResult(),
chk.IgnoreOperationID(),
)
chk.HasResult(t, res,
fluent.OperationResult().
WithNextHopGroupOperation(1).
WithOperationType(constants.Add).
WithProgrammingResult(wantACK).
AsResult(),
chk.IgnoreOperationID(),
)
chk.HasResult(t, res,
fluent.OperationResult().
WithIPv4Operation("42.42.42.42/32").
WithOperationType(constants.Add).
WithProgrammingResult(wantACK).
AsResult(),
chk.IgnoreOperationID(),
)
}
// checkNIHasNEntries uses the Get RPC to validate that the network instance named ni
// contains want (an integer) entries.
func checkNIHasNEntries(ctx context.Context, c *fluent.GRIBIClient, ni string, want int, t testing.TB) {
t.Helper()
gr, err := c.Get().
WithNetworkInstance(ni).
WithAFT(fluent.AllAFTs).
Send()
if err != nil {
t.Fatalf("got unexpected error from get, got: %v", err)
}
if got := len(gr.GetEntry()); got != want {
t.Fatalf("network instance %s has entries, got: %d, wanted:%d\nentries:\n%v", ni, got, want, gr)
}
}