2022-03-07 17:47:14 +00:00
|
|
|
package proxycfg
|
|
|
|
|
|
|
|
import (
|
|
|
|
"math"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/mitchellh/go-testing-interface"
|
2022-06-06 19:20:41 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2022-03-07 17:47:14 +00:00
|
|
|
|
2022-06-06 19:20:41 +00:00
|
|
|
"github.com/hashicorp/consul/agent/connect"
|
|
|
|
"github.com/hashicorp/consul/agent/consul/discoverychain"
|
2022-03-07 17:47:14 +00:00
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
|
|
)
|
|
|
|
|
2022-05-20 14:47:40 +00:00
|
|
|
func TestConfigSnapshotMeshGateway(t testing.T, variant string, nsFn func(ns *structs.NodeService), extraUpdates []UpdateEvent) *ConfigSnapshot {
|
2022-03-07 17:47:14 +00:00
|
|
|
roots, _ := TestCerts(t)
|
|
|
|
|
|
|
|
var (
|
|
|
|
populateServices = true
|
|
|
|
useFederationStates = false
|
|
|
|
deleteCrossDCEntry = false
|
|
|
|
)
|
|
|
|
|
|
|
|
switch variant {
|
|
|
|
case "default":
|
2022-06-06 19:20:41 +00:00
|
|
|
case "peered-services":
|
|
|
|
var (
|
|
|
|
fooSN = structs.NewServiceName("foo", nil)
|
|
|
|
barSN = structs.NewServiceName("bar", nil)
|
|
|
|
girSN = structs.NewServiceName("gir", nil)
|
|
|
|
|
|
|
|
fooChain = discoverychain.TestCompileConfigEntries(t, "foo", "default", "default", "dc1", connect.TestClusterID+".consul", nil)
|
|
|
|
barChain = discoverychain.TestCompileConfigEntries(t, "bar", "default", "default", "dc1", connect.TestClusterID+".consul", nil)
|
|
|
|
girChain = discoverychain.TestCompileConfigEntries(t, "gir", "default", "default", "dc1", connect.TestClusterID+".consul", nil)
|
|
|
|
)
|
|
|
|
|
|
|
|
assert.True(t, fooChain.Default)
|
|
|
|
assert.True(t, barChain.Default)
|
|
|
|
assert.True(t, girChain.Default)
|
|
|
|
|
|
|
|
extraUpdates = append(extraUpdates,
|
|
|
|
UpdateEvent{
|
|
|
|
CorrelationID: exportedServiceListWatchID,
|
|
|
|
Result: &structs.IndexedExportedServiceList{
|
|
|
|
Services: map[string]structs.ServiceList{
|
|
|
|
"peer1": []structs.ServiceName{fooSN, barSN},
|
|
|
|
"peer2": []structs.ServiceName{girSN},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
UpdateEvent{
|
|
|
|
CorrelationID: "discovery-chain:" + fooSN.String(),
|
|
|
|
Result: &structs.DiscoveryChainResponse{
|
|
|
|
Chain: fooChain,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
UpdateEvent{
|
|
|
|
CorrelationID: "discovery-chain:" + barSN.String(),
|
|
|
|
Result: &structs.DiscoveryChainResponse{
|
|
|
|
Chain: barChain,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
UpdateEvent{
|
|
|
|
CorrelationID: "discovery-chain:" + girSN.String(),
|
|
|
|
Result: &structs.DiscoveryChainResponse{
|
|
|
|
Chain: girChain,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
2022-03-07 17:47:14 +00:00
|
|
|
case "federation-states":
|
|
|
|
populateServices = true
|
|
|
|
useFederationStates = true
|
|
|
|
deleteCrossDCEntry = true
|
|
|
|
case "newer-info-in-federation-states":
|
|
|
|
populateServices = true
|
|
|
|
useFederationStates = true
|
|
|
|
deleteCrossDCEntry = false
|
|
|
|
case "older-info-in-federation-states":
|
|
|
|
populateServices = true
|
|
|
|
useFederationStates = true
|
|
|
|
deleteCrossDCEntry = false
|
|
|
|
case "no-services":
|
|
|
|
populateServices = false
|
|
|
|
useFederationStates = false
|
|
|
|
deleteCrossDCEntry = false
|
|
|
|
case "service-subsets":
|
2022-05-20 14:47:40 +00:00
|
|
|
extraUpdates = append(extraUpdates, UpdateEvent{
|
2022-03-07 17:47:14 +00:00
|
|
|
CorrelationID: serviceResolversWatchID,
|
|
|
|
Result: &structs.IndexedConfigEntries{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Entries: []structs.ConfigEntry{
|
|
|
|
&structs.ServiceResolverConfigEntry{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Name: "bar",
|
|
|
|
Subsets: map[string]structs.ServiceResolverSubset{
|
|
|
|
"v1": {
|
|
|
|
Filter: "Service.Meta.Version == 1",
|
|
|
|
},
|
|
|
|
"v2": {
|
|
|
|
Filter: "Service.Meta.Version == 2",
|
|
|
|
OnlyPassing: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
case "service-subsets2": // TODO(rb): make this merge with 'service-subsets'
|
2022-05-20 14:47:40 +00:00
|
|
|
extraUpdates = append(extraUpdates, UpdateEvent{
|
2022-03-07 17:47:14 +00:00
|
|
|
CorrelationID: serviceResolversWatchID,
|
|
|
|
Result: &structs.IndexedConfigEntries{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Entries: []structs.ConfigEntry{
|
|
|
|
&structs.ServiceResolverConfigEntry{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Name: "bar",
|
|
|
|
Subsets: map[string]structs.ServiceResolverSubset{
|
|
|
|
"v1": {
|
|
|
|
Filter: "Service.Meta.version == 1",
|
|
|
|
},
|
|
|
|
"v2": {
|
|
|
|
Filter: "Service.Meta.version == 2",
|
|
|
|
OnlyPassing: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&structs.ServiceResolverConfigEntry{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Name: "foo",
|
|
|
|
Subsets: map[string]structs.ServiceResolverSubset{
|
|
|
|
"v1": {
|
|
|
|
Filter: "Service.Meta.version == 1",
|
|
|
|
},
|
|
|
|
"v2": {
|
|
|
|
Filter: "Service.Meta.version == 2",
|
|
|
|
OnlyPassing: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
case "default-service-subsets2": // TODO(rb): rename to strip the 2 when the prior is merged with 'service-subsets'
|
2022-05-20 14:47:40 +00:00
|
|
|
extraUpdates = append(extraUpdates, UpdateEvent{
|
2022-03-07 17:47:14 +00:00
|
|
|
CorrelationID: serviceResolversWatchID,
|
|
|
|
Result: &structs.IndexedConfigEntries{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Entries: []structs.ConfigEntry{
|
|
|
|
&structs.ServiceResolverConfigEntry{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Name: "bar",
|
|
|
|
DefaultSubset: "v2",
|
|
|
|
Subsets: map[string]structs.ServiceResolverSubset{
|
|
|
|
"v1": {
|
|
|
|
Filter: "Service.Meta.version == 1",
|
|
|
|
},
|
|
|
|
"v2": {
|
|
|
|
Filter: "Service.Meta.version == 2",
|
|
|
|
OnlyPassing: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&structs.ServiceResolverConfigEntry{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Name: "foo",
|
|
|
|
DefaultSubset: "v2",
|
|
|
|
Subsets: map[string]structs.ServiceResolverSubset{
|
|
|
|
"v1": {
|
|
|
|
Filter: "Service.Meta.version == 1",
|
|
|
|
},
|
|
|
|
"v2": {
|
|
|
|
Filter: "Service.Meta.version == 2",
|
|
|
|
OnlyPassing: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
case "ignore-extra-resolvers":
|
2022-05-20 14:47:40 +00:00
|
|
|
extraUpdates = append(extraUpdates, UpdateEvent{
|
2022-03-07 17:47:14 +00:00
|
|
|
CorrelationID: serviceResolversWatchID,
|
|
|
|
Result: &structs.IndexedConfigEntries{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Entries: []structs.ConfigEntry{
|
|
|
|
&structs.ServiceResolverConfigEntry{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Name: "bar",
|
|
|
|
DefaultSubset: "v2",
|
|
|
|
Subsets: map[string]structs.ServiceResolverSubset{
|
|
|
|
"v1": {
|
|
|
|
Filter: "Service.Meta.Version == 1",
|
|
|
|
},
|
|
|
|
"v2": {
|
|
|
|
Filter: "Service.Meta.Version == 2",
|
|
|
|
OnlyPassing: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&structs.ServiceResolverConfigEntry{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Name: "notfound",
|
|
|
|
DefaultSubset: "v2",
|
|
|
|
Subsets: map[string]structs.ServiceResolverSubset{
|
|
|
|
"v1": {
|
|
|
|
Filter: "Service.Meta.Version == 1",
|
|
|
|
},
|
|
|
|
"v2": {
|
|
|
|
Filter: "Service.Meta.Version == 2",
|
|
|
|
OnlyPassing: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
case "service-timeouts":
|
2022-05-20 14:47:40 +00:00
|
|
|
extraUpdates = append(extraUpdates, UpdateEvent{
|
2022-03-07 17:47:14 +00:00
|
|
|
CorrelationID: serviceResolversWatchID,
|
|
|
|
Result: &structs.IndexedConfigEntries{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Entries: []structs.ConfigEntry{
|
|
|
|
&structs.ServiceResolverConfigEntry{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Name: "bar",
|
|
|
|
ConnectTimeout: 10 * time.Second,
|
|
|
|
Subsets: map[string]structs.ServiceResolverSubset{
|
|
|
|
"v1": {
|
|
|
|
Filter: "Service.Meta.Version == 1",
|
|
|
|
},
|
|
|
|
"v2": {
|
|
|
|
Filter: "Service.Meta.Version == 2",
|
|
|
|
OnlyPassing: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
case "non-hash-lb-injected":
|
2022-05-20 14:47:40 +00:00
|
|
|
extraUpdates = append(extraUpdates, UpdateEvent{
|
2022-03-07 17:47:14 +00:00
|
|
|
CorrelationID: "service-resolvers", // serviceResolversWatchID
|
|
|
|
Result: &structs.IndexedConfigEntries{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Entries: []structs.ConfigEntry{
|
|
|
|
&structs.ServiceResolverConfigEntry{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Name: "bar",
|
|
|
|
Subsets: map[string]structs.ServiceResolverSubset{
|
|
|
|
"v1": {
|
|
|
|
Filter: "Service.Meta.Version == 1",
|
|
|
|
},
|
|
|
|
"v2": {
|
|
|
|
Filter: "Service.Meta.Version == 2",
|
|
|
|
OnlyPassing: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
LoadBalancer: &structs.LoadBalancer{
|
|
|
|
Policy: "least_request",
|
|
|
|
LeastRequestConfig: &structs.LeastRequestConfig{
|
|
|
|
ChoiceCount: 5,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
case "hash-lb-ignored":
|
2022-05-20 14:47:40 +00:00
|
|
|
extraUpdates = append(extraUpdates, UpdateEvent{
|
2022-03-07 17:47:14 +00:00
|
|
|
CorrelationID: "service-resolvers", // serviceResolversWatchID
|
|
|
|
Result: &structs.IndexedConfigEntries{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Entries: []structs.ConfigEntry{
|
|
|
|
&structs.ServiceResolverConfigEntry{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Name: "bar",
|
|
|
|
Subsets: map[string]structs.ServiceResolverSubset{
|
|
|
|
"v1": {
|
|
|
|
Filter: "Service.Meta.Version == 1",
|
|
|
|
},
|
|
|
|
"v2": {
|
|
|
|
Filter: "Service.Meta.Version == 2",
|
|
|
|
OnlyPassing: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
LoadBalancer: &structs.LoadBalancer{
|
|
|
|
Policy: "ring_hash",
|
|
|
|
RingHashConfig: &structs.RingHashConfig{
|
|
|
|
MinimumRingSize: 20,
|
|
|
|
MaximumRingSize: 50,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
default:
|
|
|
|
t.Fatalf("unknown variant: %s", variant)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-05-20 14:47:40 +00:00
|
|
|
baseEvents := []UpdateEvent{
|
2022-03-07 17:47:14 +00:00
|
|
|
{
|
|
|
|
CorrelationID: rootsWatchID,
|
|
|
|
Result: roots,
|
|
|
|
},
|
2022-06-06 19:20:41 +00:00
|
|
|
{
|
|
|
|
CorrelationID: exportedServiceListWatchID,
|
|
|
|
Result: &structs.IndexedExportedServiceList{
|
|
|
|
Services: nil,
|
|
|
|
},
|
|
|
|
},
|
2022-03-07 17:47:14 +00:00
|
|
|
{
|
|
|
|
CorrelationID: serviceListWatchID,
|
|
|
|
Result: &structs.IndexedServiceList{
|
|
|
|
Services: nil,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
CorrelationID: serviceResolversWatchID,
|
|
|
|
Result: &structs.IndexedConfigEntries{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Entries: nil,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
CorrelationID: datacentersWatchID,
|
|
|
|
Result: &[]string{"dc1"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
if populateServices || useFederationStates {
|
2022-05-20 14:47:40 +00:00
|
|
|
baseEvents = testSpliceEvents(baseEvents, []UpdateEvent{
|
2022-03-07 17:47:14 +00:00
|
|
|
{
|
|
|
|
CorrelationID: datacentersWatchID,
|
|
|
|
Result: &[]string{"dc1", "dc2", "dc4", "dc6"},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if populateServices {
|
|
|
|
var (
|
|
|
|
foo = structs.NewServiceName("foo", nil)
|
|
|
|
bar = structs.NewServiceName("bar", nil)
|
|
|
|
)
|
2022-05-20 14:47:40 +00:00
|
|
|
baseEvents = testSpliceEvents(baseEvents, []UpdateEvent{
|
2022-03-07 17:47:14 +00:00
|
|
|
{
|
|
|
|
CorrelationID: "mesh-gateway:dc2",
|
|
|
|
Result: &structs.IndexedNodesWithGateways{
|
|
|
|
Nodes: TestGatewayNodesDC2(t),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
CorrelationID: "mesh-gateway:dc4",
|
|
|
|
Result: &structs.IndexedNodesWithGateways{
|
|
|
|
Nodes: TestGatewayNodesDC4Hostname(t),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
CorrelationID: "mesh-gateway:dc6",
|
|
|
|
Result: &structs.IndexedNodesWithGateways{
|
|
|
|
Nodes: TestGatewayNodesDC6Hostname(t),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
CorrelationID: serviceListWatchID,
|
|
|
|
Result: &structs.IndexedServiceList{
|
|
|
|
Services: []structs.ServiceName{
|
|
|
|
foo,
|
|
|
|
bar,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
CorrelationID: "connect-service:" + foo.String(),
|
|
|
|
Result: &structs.IndexedCheckServiceNodes{
|
|
|
|
Nodes: TestGatewayServiceGroupFooDC1(t),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
CorrelationID: "connect-service:" + bar.String(),
|
|
|
|
Result: &structs.IndexedCheckServiceNodes{
|
|
|
|
Nodes: TestGatewayServiceGroupBarDC1(t),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
CorrelationID: serviceResolversWatchID,
|
|
|
|
Result: &structs.IndexedConfigEntries{
|
|
|
|
Kind: structs.ServiceResolver,
|
|
|
|
Entries: []structs.ConfigEntry{
|
|
|
|
//
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if useFederationStates {
|
|
|
|
nsFn = testSpliceNodeServiceFunc(nsFn, func(ns *structs.NodeService) {
|
|
|
|
ns.Meta[structs.MetaWANFederationKey] = "1"
|
|
|
|
})
|
|
|
|
|
|
|
|
if deleteCrossDCEntry {
|
2022-05-20 14:47:40 +00:00
|
|
|
baseEvents = testSpliceEvents(baseEvents, []UpdateEvent{
|
2022-03-07 17:47:14 +00:00
|
|
|
{
|
|
|
|
// Have the cross-dc query mechanism not work for dc2 so
|
|
|
|
// fedstates will infill.
|
|
|
|
CorrelationID: "mesh-gateway:dc2",
|
|
|
|
Result: &structs.IndexedNodesWithGateways{
|
|
|
|
Nodes: nil,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
dc2Nodes := TestGatewayNodesDC2(t)
|
|
|
|
switch variant {
|
|
|
|
case "newer-info-in-federation-states":
|
|
|
|
// Create a duplicate entry in FedStateGateways, with a high ModifyIndex, to
|
|
|
|
// verify that fresh data in the federation state is preferred over stale data
|
|
|
|
// in GatewayGroups.
|
|
|
|
svc := structs.TestNodeServiceMeshGatewayWithAddrs(t,
|
|
|
|
"10.0.1.3", 8443,
|
|
|
|
structs.ServiceAddress{Address: "10.0.1.3", Port: 8443},
|
|
|
|
structs.ServiceAddress{Address: "198.18.1.3", Port: 443},
|
|
|
|
)
|
|
|
|
svc.RaftIndex.ModifyIndex = math.MaxUint64
|
|
|
|
|
|
|
|
dc2Nodes = structs.CheckServiceNodes{
|
|
|
|
{
|
|
|
|
Node: dc2Nodes[0].Node,
|
|
|
|
Service: svc,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
case "older-info-in-federation-states":
|
|
|
|
// Create a duplicate entry in FedStateGateways, with a low ModifyIndex, to
|
|
|
|
// verify that stale data in the federation state is ignored in favor of the
|
|
|
|
// fresher data in GatewayGroups.
|
|
|
|
svc := structs.TestNodeServiceMeshGatewayWithAddrs(t,
|
|
|
|
"10.0.1.3", 8443,
|
|
|
|
structs.ServiceAddress{Address: "10.0.1.3", Port: 8443},
|
|
|
|
structs.ServiceAddress{Address: "198.18.1.3", Port: 443},
|
|
|
|
)
|
|
|
|
svc.RaftIndex.ModifyIndex = 0
|
|
|
|
|
|
|
|
dc2Nodes = structs.CheckServiceNodes{
|
|
|
|
{
|
|
|
|
Node: dc2Nodes[0].Node,
|
|
|
|
Service: svc,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-20 14:47:40 +00:00
|
|
|
baseEvents = testSpliceEvents(baseEvents, []UpdateEvent{
|
2022-03-07 17:47:14 +00:00
|
|
|
{
|
|
|
|
CorrelationID: federationStateListGatewaysWatchID,
|
|
|
|
Result: &structs.DatacenterIndexedCheckServiceNodes{
|
|
|
|
DatacenterNodes: map[string]structs.CheckServiceNodes{
|
|
|
|
"dc2": dc2Nodes,
|
|
|
|
"dc4": TestGatewayNodesDC4Hostname(t),
|
|
|
|
"dc6": TestGatewayNodesDC6Hostname(t),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
CorrelationID: consulServerListWatchID,
|
|
|
|
Result: &structs.IndexedCheckServiceNodes{
|
|
|
|
Nodes: nil, // TODO
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return testConfigSnapshotFixture(t, &structs.NodeService{
|
|
|
|
Kind: structs.ServiceKindMeshGateway,
|
|
|
|
Service: "mesh-gateway",
|
|
|
|
Address: "1.2.3.4",
|
|
|
|
Port: 8443,
|
|
|
|
Proxy: structs.ConnectProxyConfig{
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
},
|
|
|
|
Meta: make(map[string]string),
|
|
|
|
TaggedAddresses: map[string]structs.ServiceAddress{
|
|
|
|
structs.TaggedAddressLAN: {
|
|
|
|
Address: "1.2.3.4",
|
|
|
|
Port: 8443,
|
|
|
|
},
|
|
|
|
structs.TaggedAddressWAN: {
|
|
|
|
Address: "198.18.0.1",
|
|
|
|
Port: 443,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, nsFn, nil, testSpliceEvents(baseEvents, extraUpdates))
|
|
|
|
}
|