...

Source file src/github.com/datawire/ambassador/v2/cmd/entrypoint/testutil_fake_hello_test.go

Documentation: github.com/datawire/ambassador/v2/cmd/entrypoint

     1  package entrypoint_test
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"sort"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/datawire/ambassador/v2/cmd/entrypoint"
    14  	"github.com/datawire/ambassador/v2/pkg/ambex"
    15  	v3bootstrap "github.com/datawire/ambassador/v2/pkg/api/envoy/config/bootstrap/v3"
    16  	v3cluster "github.com/datawire/ambassador/v2/pkg/api/envoy/config/cluster/v3"
    17  	"github.com/datawire/ambassador/v2/pkg/kates"
    18  	"github.com/datawire/ambassador/v2/pkg/snapshot/v1"
    19  	"github.com/datawire/dlib/dlog"
    20  )
    21  
    22  // The IR layer produces a set of "feature" data describing things in use in the system.
    23  // For this test, we only care about the count of invalid secrets.
    24  type FakeFeatures struct {
    25  	Invalid map[string]int `json:"invalid_counts"`
    26  }
    27  
    28  // The Fake struct is a test harness for edgestack. It spins up the key portions of the edgestack
    29  // control plane that contain the bulk of its business logic, but instead of requiring tests to feed
    30  // the business logic inputs via a real kubernetes or real consul deployment, inputs can be fed
    31  // directly into the business logic via the harness APIs. This is not only several orders of
    32  // magnitute faster, this also provides the author of the test perfect control over the ordering of
    33  // events.
    34  //
    35  // By default the Fake struct only invokes the first part of the pipeline that forms the control
    36  // plane. If you use the EnvoyConfig option you can run the rest of the control plane. There is also
    37  // a Timeout option that controls how long the harness waits for the desired Snapshot and/or
    38  // EnvoyConfig to come along.
    39  //
    40  // Note that this test depends on diagd being in your path. If diagd is not available, the test will
    41  // be skipped.
    42  
    43  // TestFakeHello is a basic "Hello, world!" style of test in that it demonstrates some things about
    44  // the Fake test harness, but it does actually do valid testing of the system as well.
    45  func TestFakeHello(t *testing.T) {
    46  	// You can use os.Setenv to set environment variables, and they will affect the test harness.
    47  	// Here, we'll force secret validation, so that invalid Secrets won't get passed all the way
    48  	// to Envoy.
    49  	os.Setenv("AMBASSADOR_FORCE_SECRET_VALIDATION", "true")
    50  
    51  	// Use RunFake() to spin up the ambassador control plane with its inputs wired up to the Fake
    52  	// APIs. This will automatically invoke the Setup() method for the Fake and also register the
    53  	// Teardown() method with the Cleanup() hook of the supplied testing.T object.
    54  	//
    55  	// Note that we _must_ set EnvoyConfig true to allow checking IR Features later, even though
    56  	// we don't actually do any checking of the Envoy config in this test.
    57  	f := entrypoint.RunFake(t, entrypoint.FakeConfig{EnvoyConfig: true}, nil)
    58  
    59  	// The Fake harness has a store for both kubernetes resources and consul endpoint data. We can
    60  	// use the UpsertFile() to method to load as many resources as we would like. This is much like
    61  	// doing a `kubectl apply` to a real kubernetes API server, however apply uses fancy merge
    62  	// logic, whereas UpsertFile() does a simple Upsert operation. The `testdata/FakeHello.yaml`
    63  	// file has a single mapping named "hello".
    64  	assert.NoError(t, f.UpsertFile("testdata/FakeHello.yaml"))
    65  
    66  	// Note, also, that we needn't limit ourselves to a single input file. Here, we'll upsert
    67  	// a second file containing a broken TLS certificate, and we'll trust autoflush to do the
    68  	// right thing for us.
    69  	assert.NoError(t, f.UpsertFile("testdata/BrokenSecret.yaml"))
    70  
    71  	// Initially the Fake harness is paused. This means we can make as many method calls as we want
    72  	// to in order to set up our initial conditions, and no inputs will be fed into the control
    73  	// plane. To feed inputs to the control plane, we can choose to either manually invoke the
    74  	// Flush() method whenever we want to send the control plane inputs, or for convenience we can
    75  	// enable AutoFlush so that inputs are set whenever we modify data that the control plane is
    76  	// watching.
    77  	//
    78  	// XXX Well... that's the way it should work. For now, though, we need to manually flush a
    79  	// single time for both files, because something else strange is going on.
    80  	f.Flush()
    81  	// f.AutoFlush(true)
    82  
    83  	// Once the control plane has started processing inputs, we need some way to observe its
    84  	// computation. The Fake harness provides two ways to do this. The GetSnapshot() method allows
    85  	// us to observe the snapshots assembled by the watchers for further processing. The
    86  	// GetEnvoyConfig() method allows us to observe the envoy configuration produced from a
    87  	// snapshot. Both these methods take a predicate so the can search for a snapshot that satisifes
    88  	// whatever conditions are being tested. This allows the test to verify that the correct
    89  	// computation is occurring without being overly prescriptive about the exact number of
    90  	// snapshots and/or envoy configs that are produce to achieve a certain result.
    91  	snap, err := f.GetSnapshot(func(snap *snapshot.Snapshot) bool {
    92  		hasMappings := len(snap.Kubernetes.Mappings) > 0
    93  		hasSecrets := len(snap.Kubernetes.Secrets) > 0
    94  		hasInvalid := len(snap.Invalid) > 0
    95  
    96  		return hasMappings && hasSecrets && hasInvalid
    97  	})
    98  	require.NoError(t, err)
    99  
   100  	// Check that the snapshot contains the mapping from the file.
   101  	assert.Equal(t, "hello", snap.Kubernetes.Mappings[0].Name)
   102  
   103  	// This snapshot also needs to have one good secret...
   104  	assert.Equal(t, 1, len(snap.Kubernetes.Secrets))
   105  	assert.Equal(t, "tls-cert", snap.Kubernetes.Secrets[0].Name)
   106  
   107  	// ...and one invalid secret.
   108  	assert.Equal(t, 1, len(snap.Invalid))
   109  	assert.Equal(t, "tls-broken-cert", snap.Invalid[0].GetName())
   110  
   111  	// Finally, we need to see a single invalid Secret in the IR Features.
   112  	var features FakeFeatures
   113  	err = f.GetFeatures(dlog.NewTestContext(t, false), &features)
   114  	require.NoError(t, err)
   115  
   116  	assert.Equal(t, 1, features.Invalid["Secret"])
   117  }
   118  
   119  // TestFakeHelloNoSecretValidation will cover the exact same paths as TestFakeHello, but with
   120  // the AMBASSADOR_FORCE_SECRET_VALIDATION environment variable disabled. We expect the number
   121  // of secrets to be different.
   122  func TestFakeHelloNoSecretValidation(t *testing.T) {
   123  	// There are many fewer comments here, because this function so closely mirrors
   124  	// TestFakeHello. Read the comments there!
   125  	//
   126  	// We explicitly force secret validation off, so that broken secrets will not get dropped.
   127  	// They will still appear in the Invalid list, and in the IR Features invalid_counts dict.
   128  	os.Setenv("AMBASSADOR_FORCE_SECRET_VALIDATION", "false")
   129  
   130  	// Note that we _must_ set EnvoyConfig true to allow checking IR Features later.
   131  	f := entrypoint.RunFake(t, entrypoint.FakeConfig{EnvoyConfig: true}, nil)
   132  
   133  	// Once again, we'll use both FakeHello.yaml and BrokenSecret.yaml... and once again, we'll
   134  	// manually flush a single time.
   135  	assert.NoError(t, f.UpsertFile("testdata/FakeHello.yaml"))
   136  	assert.NoError(t, f.UpsertFile("testdata/BrokenSecret.yaml"))
   137  	f.Flush()
   138  
   139  	// We'll use the same predicate as TestFakeHello to grab a snapshot with some mappings,
   140  	// some secrets, and some invalid objects.
   141  	snap, err := f.GetSnapshot(func(snap *snapshot.Snapshot) bool {
   142  		hasMappings := len(snap.Kubernetes.Mappings) > 0
   143  		hasSecrets := len(snap.Kubernetes.Secrets) > 0
   144  		hasInvalid := len(snap.Invalid) > 0
   145  
   146  		return hasMappings && hasSecrets && hasInvalid
   147  	})
   148  	require.NoError(t, err)
   149  
   150  	// This snapshot needs to have the correct Mapping...
   151  	assert.Equal(t, "hello", snap.Kubernetes.Mappings[0].Name)
   152  
   153  	// ...but it'll claim to have two good Secrets (even though one is really broken).
   154  	assert.Equal(t, 2, len(snap.Kubernetes.Secrets))
   155  	secretNames := []string{snap.Kubernetes.Secrets[0].Name, snap.Kubernetes.Secrets[1].Name}
   156  	assert.Contains(t, secretNames, "tls-broken-cert")
   157  	assert.Contains(t, secretNames, "tls-cert")
   158  
   159  	// Even though the broken cert is in our "valid" list above, it should stil show
   160  	// up in the Invalid objects list...
   161  	assert.Equal(t, 1, len(snap.Invalid))
   162  	assert.Equal(t, "tls-broken-cert", snap.Invalid[0].GetName())
   163  
   164  	// ...and in our invalid_counts in the IR Features.
   165  	var features FakeFeatures
   166  	err = f.GetFeatures(dlog.NewTestContext(t, false), &features)
   167  	require.NoError(t, err)
   168  
   169  	assert.Equal(t, 1, features.Invalid["Secret"])
   170  }
   171  
   172  // TestFakeHelloEC will cover mTLS Secret validation with EC (Elliptic Curve) Private Keys. Once again,
   173  // it closely mirrors TestFakeHello (with secret validation on), just using different secrets.
   174  func TestFakeHelloEC(t *testing.T) {
   175  	// There are many fewer comments here, because this function so closely mirrors
   176  	// TestFakeHello. Read the comments there!
   177  	//
   178  	// Make sure secret validation is on (so broken secrets won't show up in the "good" list).
   179  	os.Setenv("AMBASSADOR_FORCE_SECRET_VALIDATION", "true")
   180  
   181  	// Note that we _must_ set EnvoyConfig true to allow checking IR Features later.
   182  	f := entrypoint.RunFake(t, entrypoint.FakeConfig{EnvoyConfig: true}, nil)
   183  
   184  	// AutoFlush will work fine here, with just the one file.
   185  	f.AutoFlush(true)
   186  
   187  	// FakeHelloEC.yaml contains good secrets and invalid secrets, so we just need the one file.
   188  	assert.NoError(t, f.UpsertFile("testdata/FakeHelloEC.yaml"))
   189  
   190  	// Once again, we can use the same predicate as TestFakeHello to grab a snapshot with some mappings,
   191  	// some secrets, and some invalid objects.
   192  	snap, err := f.GetSnapshot(func(snap *snapshot.Snapshot) bool {
   193  		hasMappings := len(snap.Kubernetes.Mappings) > 0
   194  		hasSecrets := len(snap.Kubernetes.Secrets) > 0
   195  		hasInvalid := len(snap.Invalid) > 0
   196  
   197  		return hasMappings && hasSecrets && hasInvalid
   198  	})
   199  	require.NoError(t, err)
   200  
   201  	// This snapshot needs to have the correct Mapping...
   202  	assert.Equal(t, "hello-elliptic-curve", snap.Kubernetes.Mappings[0].Name)
   203  
   204  	// ...and it also needs two good Secrets. Note that neither of these is the broken one...
   205  	assert.Equal(t, 2, len(snap.Kubernetes.Secrets))
   206  	secretNames := []string{snap.Kubernetes.Secrets[0].Name, snap.Kubernetes.Secrets[1].Name}
   207  	assert.Contains(t, secretNames, "hello-elliptic-curve-client")
   208  	assert.Contains(t, secretNames, "tls-cert")
   209  
   210  	// ...since the broken cert shows up only in the invalid list (and the IR Features, of
   211  	// course).
   212  	assert.Equal(t, 1, len(snap.Invalid))
   213  	assert.Equal(t, "hello-elliptic-curve-broken-server", snap.Invalid[0].GetName())
   214  
   215  	var features FakeFeatures
   216  	err = f.GetFeatures(dlog.NewTestContext(t, false), &features)
   217  	require.NoError(t, err)
   218  
   219  	assert.Equal(t, 1, features.Invalid["Secret"])
   220  }
   221  
   222  // TestFakeHelloWithEnvoyConfig is a Hello-World style test that also checks the Envoy configuration.
   223  func TestFakeHelloWithEnvoyConfig(t *testing.T) {
   224  	// Use the FakeConfig parameter to conigure the Fake harness. In this case we want to inspect
   225  	// the EnvoyConfig that is produced from the inputs we feed the control plane.
   226  	f := entrypoint.RunFake(t, entrypoint.FakeConfig{EnvoyConfig: true}, nil)
   227  
   228  	// We will use the same inputs we used in TestFakeHello. A single mapping named "hello".
   229  	assert.NoError(t, f.UpsertFile("testdata/FakeHello.yaml"))
   230  	// Instead of using AutoFlush(true) we will manually Flush() when we want to feed inputs to the
   231  	// control plane.
   232  	f.Flush()
   233  
   234  	// Grab the next snapshot that has mappings. The v3bootstrap logic should actually gaurantee this
   235  	// is also the first mapping, but we aren't trying to test that here.
   236  	snap, err := f.GetSnapshot(func(snap *snapshot.Snapshot) bool {
   237  		return len(snap.Kubernetes.Mappings) > 0
   238  	})
   239  	require.NoError(t, err)
   240  	// The first snapshot should contain the one and only mapping we have supplied the control
   241  	// plane.x
   242  	assert.Equal(t, "hello", snap.Kubernetes.Mappings[0].Name)
   243  
   244  	// Create a predicate that will recognize the cluster we care about. The surjection from
   245  	// Mappings to clusters is a bit opaque, so we just look for a cluster that contains the name
   246  	// hello.
   247  	isHelloCluster := func(c *v3cluster.Cluster) bool {
   248  		return strings.Contains(c.Name, "hello")
   249  	}
   250  
   251  	// Grab the next envoy config that satisfies our predicate.
   252  	envoyConfig, err := f.GetEnvoyConfig(func(envoy *v3bootstrap.Bootstrap) bool {
   253  		return FindCluster(envoy, isHelloCluster) != nil
   254  	})
   255  	require.NoError(t, err)
   256  
   257  	// Now let's dig into the envoy configuration and check that the correct target endpoint is
   258  	// present.
   259  	//
   260  	// Note: This is admittedly quite verbose as envoy configuration is very dense. I expect we will
   261  	// introduce an API that will provide a more abstract and convenient way of navigating envoy
   262  	// configuration, however that will be covered in a future PR. The core of that logic is already
   263  	// developing inside ambex since ambex slices and dices the envoy config in order to implement
   264  	// RDS and enpdoint routing.
   265  	cluster := FindCluster(envoyConfig, isHelloCluster)
   266  	endpoints := cluster.LoadAssignment.Endpoints
   267  	require.NotEmpty(t, endpoints)
   268  	lbEndpoints := endpoints[0].LbEndpoints
   269  	require.NotEmpty(t, lbEndpoints)
   270  	endpoint := lbEndpoints[0].GetEndpoint()
   271  	address := endpoint.Address.GetSocketAddress().Address
   272  	assert.Equal(t, "hello", address)
   273  
   274  	// Since there are no broken secrets here, we should have no entries in the IR Features.
   275  	var features FakeFeatures
   276  	err = f.GetFeatures(dlog.NewTestContext(t, false), &features)
   277  	require.NoError(t, err)
   278  
   279  	assert.Equal(t, 0, features.Invalid["Secret"])
   280  }
   281  
   282  func FindCluster(envoyConfig *v3bootstrap.Bootstrap, predicate func(*v3cluster.Cluster) bool) *v3cluster.Cluster {
   283  	for _, cluster := range envoyConfig.StaticResources.Clusters {
   284  		if predicate(cluster) {
   285  			return cluster
   286  		}
   287  	}
   288  
   289  	return nil
   290  }
   291  
   292  func deltaSummary(t *testing.T, snaps ...*snapshot.Snapshot) []string {
   293  	summary := []string{}
   294  
   295  	var typestr string
   296  
   297  	for _, snap := range snaps {
   298  		for _, delta := range snap.Deltas {
   299  			switch delta.DeltaType {
   300  			case kates.ObjectAdd:
   301  				typestr = "add"
   302  			case kates.ObjectUpdate:
   303  				typestr = "update"
   304  			case kates.ObjectDelete:
   305  				typestr = "delete"
   306  			default:
   307  				// Bug because the programmer needs to add another case here.
   308  				t.Fatalf("missing case for DeltaType enum: %#v", delta)
   309  			}
   310  
   311  			summary = append(summary, fmt.Sprintf("%s %s %s", typestr, delta.Kind, delta.Name))
   312  		}
   313  	}
   314  
   315  	sort.Strings(summary)
   316  
   317  	return summary
   318  }
   319  
   320  // getSnapshots is like f.GetSnapshot, but returns the list of every snapshot evaluated (rather than
   321  // discarding the snapshots from before predicate returns true).  This is particularly important if
   322  // you're looking at deltas; you don't want to discard any deltas just because two snapshots didn't
   323  // get coalesced.
   324  func getSnapshots(f *entrypoint.Fake, predicate func(*snapshot.Snapshot) bool) ([]*snapshot.Snapshot, error) {
   325  	var ret []*snapshot.Snapshot
   326  	for {
   327  		snap, err := f.GetSnapshot(func(_ *snapshot.Snapshot) bool {
   328  			return true
   329  		})
   330  		if err != nil {
   331  			return nil, err
   332  		}
   333  		ret = append(ret, snap)
   334  		if predicate(snap) {
   335  			break
   336  		}
   337  	}
   338  	return ret, nil
   339  }
   340  
   341  // This test will cover how to exercise the consul portion of the control plane. In principal it is
   342  // the same as supplying kubernetes resources, however it uses the ConsulEndpoint() method to
   343  // provide consul data.
   344  func TestFakeHelloConsul(t *testing.T) {
   345  	os.Setenv("CONSULPORT", "8500")
   346  	os.Setenv("CONSULHOST", "consul-1")
   347  
   348  	// Create our Fake harness and tell it to produce envoy configuration.
   349  	f := entrypoint.RunFake(t, entrypoint.FakeConfig{EnvoyConfig: true}, nil)
   350  
   351  	// Feed the control plane the kubernetes resources supplied in the referenced file. In this case
   352  	// that includes a consul resolver and a mapping that uses that consul resolver.
   353  	assert.NoError(t, f.UpsertFile("testdata/FakeHelloConsul.yaml"))
   354  	// This test is a bit more interesting for the control plane from a v3bootstrapping perspective,
   355  	// so we invoke Flush() manually rather than using AutoFlush(true). The control plane needs to
   356  	// figure out that there is a mapping that depends on consul endpoint data, and it needs to wait
   357  	// until that data is available before producing the first snapshot.
   358  	f.Flush()
   359  
   360  	// In prior tests we have only examined the snapshots that were ready to be processed, but the
   361  	// watcher doesn't process every snapshot it constructs, it can discard various snapshots for
   362  	// different reasons. Using GetSnapshotEntry() we can pull entries from the full log of
   363  	// snapshots considered as opposed to just the skipping straight to the ones that are ready to
   364  	// be processed.
   365  	//
   366  	// In this case the snapshot is considered incomplete until we supply enough consul endpoint
   367  	// data for edgestack to construct an envoy config that won't send requests to our hello mapping
   368  	// into a black hole.
   369  	entry, err := f.GetSnapshotEntry(func(entry entrypoint.SnapshotEntry) bool {
   370  		return entry.Disposition == entrypoint.SnapshotIncomplete && len(entry.Snapshot.Kubernetes.Mappings) > 0
   371  	})
   372  	require.NoError(t, err)
   373  	// Check that the snapshot contains the mapping from the file.
   374  	assert.Equal(t, "hello", entry.Snapshot.Kubernetes.Mappings[0].Name)
   375  	// ..and the TCPMapping as well
   376  	assert.Equal(t, "hello-tcp", entry.Snapshot.Kubernetes.TCPMappings[0].Name)
   377  
   378  	// Now let's supply the endpoint data for the hello service referenced by our hello mapping.
   379  	f.ConsulEndpoint("dc1", "hello", "1.2.3.4", 8080)
   380  	// And also supply the endpoint data for the hello-tcp service referenced by our hello mapping.
   381  	f.ConsulEndpoint("dc1", "hello-tcp", "5.6.7.8", 3099)
   382  	f.Flush()
   383  
   384  	// The Fake harness also tracks endpoints that get sent to ambex. We can use the GetEndpoints()
   385  	// method to access them and check to see that the endpoint we supplied got delivered to ambex.
   386  	endpoints, err := f.GetEndpoints(func(endpoints *ambex.Endpoints) bool {
   387  		_, ok := endpoints.Entries["consul/dc1/hello"]
   388  		if ok {
   389  			_, okTcp := endpoints.Entries["consul/dc1/hello-tcp"]
   390  			return okTcp
   391  		}
   392  		return false
   393  	})
   394  	require.NoError(t, err)
   395  	assert.Len(t, endpoints.Entries, 2)
   396  	assert.Equal(t, "1.2.3.4", endpoints.Entries["consul/dc1/hello"][0].Ip)
   397  
   398  	// Grab the next snapshot that has both mappings, tcpmappings, and a Consul resolver. The v3bootstrap logic
   399  	// should actually guarantee this is also the first mapping, but we aren't trying to test
   400  	// that here.
   401  	snap, err := f.GetSnapshot(func(snap *snapshot.Snapshot) bool {
   402  		return (len(snap.Kubernetes.Mappings) > 0) && (len(snap.Kubernetes.TCPMappings) > 0) && (len(snap.Kubernetes.ConsulResolvers) > 0)
   403  	})
   404  	require.NoError(t, err)
   405  	// The first snapshot should contain both the mapping and tcpmapping we have supplied the control
   406  	// plane.
   407  	assert.Equal(t, "hello", snap.Kubernetes.Mappings[0].Name)
   408  	assert.Equal(t, "hello-tcp", snap.Kubernetes.TCPMappings[0].Name)
   409  
   410  	// It should also contain one ConsulResolver with a Spec.Address of
   411  	// "consul-server.default:8500" (where the 8500 came from an environment variable).
   412  	assert.Equal(t, "consul-server.default:8500", snap.Kubernetes.ConsulResolvers[0].Spec.Address)
   413  
   414  	// Check that our deltas are what we expect.
   415  	assert.Equal(t, []string{"add ConsulResolver consul-dc1", "add Mapping hello", "add TCPMapping hello-tcp"}, deltaSummary(t, snap))
   416  
   417  	// Create a predicate that will recognize the cluster we care about. The surjection from
   418  	// Mappings to clusters is a bit opaque, so we just look for a cluster that contains the name
   419  	// hello.
   420  	isHelloTCPCluster := func(c *v3cluster.Cluster) bool {
   421  		return strings.Contains(c.Name, "hello_tcp")
   422  	}
   423  	isHelloCluster := func(c *v3cluster.Cluster) bool {
   424  		return strings.Contains(c.Name, "hello") && !isHelloTCPCluster(c)
   425  	}
   426  
   427  	// Grab the next envoy config that satisfies our predicate.
   428  	envoyConfig, err := f.GetEnvoyConfig(func(envoy *v3bootstrap.Bootstrap) bool {
   429  		return FindCluster(envoy, isHelloCluster) != nil
   430  	})
   431  	require.NoError(t, err)
   432  
   433  	// Now let's check that the cluster produced properly references the endpoints that have already
   434  	// arrived at ambex.
   435  	cluster := FindCluster(envoyConfig, isHelloCluster)
   436  	assert.NotNil(t, cluster)
   437  	// It uses the consul resolver, so it should not embed the load assignment directly.
   438  	assert.Nil(t, cluster.LoadAssignment)
   439  	// It *should* have an EdsConfig.
   440  	edsConfig := cluster.GetEdsClusterConfig()
   441  	require.NotNil(t, edsConfig)
   442  	// The EdsConfig *should* reference an endpoint.
   443  	eps := endpoints.Entries[edsConfig.ServiceName]
   444  	require.Len(t, eps, 1)
   445  	// The endpoint it references *should* have our supplied ip address.
   446  	assert.Equal(t, "1.2.3.4", eps[0].Ip)
   447  
   448  	// Finally, let's check that the TCP cluster is OK too.
   449  	cluster = FindCluster(envoyConfig, isHelloTCPCluster)
   450  	assert.NotNil(t, cluster)
   451  	// It uses the consul resolver, so it should not embed the load assignment directly.
   452  	assert.Nil(t, cluster.LoadAssignment)
   453  	// It *should* have an EdsConfig.
   454  	edsConfig = cluster.GetEdsClusterConfig()
   455  	require.NotNil(t, edsConfig)
   456  	// The EdsConfig *should* reference an endpoint.
   457  	eps = endpoints.Entries[edsConfig.ServiceName]
   458  	require.Len(t, eps, 1)
   459  	// The endpoint it references *should* have our supplied ip address.
   460  	assert.Equal(t, "5.6.7.8", eps[0].Ip)
   461  
   462  	// Next up, change the Consul resolver definition.
   463  	assert.NoError(t, f.UpsertYAML(`
   464  ---
   465  apiVersion: getambassador.io/v3alpha1
   466  kind: ConsulResolver
   467  metadata:
   468    name: consul-dc1
   469  spec:
   470    address: $CONSULHOST:$CONSULPORT
   471    datacenter: dc1
   472  `))
   473  	f.Flush()
   474  
   475  	// Repeat the snapshot checks. We must have mappings and consulresolvers...
   476  	snap, err = f.GetSnapshot(func(snap *snapshot.Snapshot) bool {
   477  		return (len(snap.Kubernetes.Mappings) > 0) && (len(snap.Kubernetes.TCPMappings) > 0) && (len(snap.Kubernetes.ConsulResolvers) > 0)
   478  	})
   479  	require.NoError(t, err)
   480  
   481  	// ...with one delta, namely the ConsulResolver...
   482  	assert.Equal(t, []string{"update ConsulResolver consul-dc1"}, deltaSummary(t, snap))
   483  
   484  	// ...where the mapping name hasn't changed...
   485  	assert.Equal(t, "hello", snap.Kubernetes.Mappings[0].Name)
   486  	assert.Equal(t, "hello-tcp", snap.Kubernetes.TCPMappings[0].Name)
   487  
   488  	// ...but the Consul server address has.
   489  	assert.Equal(t, "consul-1:8500", snap.Kubernetes.ConsulResolvers[0].Spec.Address)
   490  
   491  	// Finally, delete the Consul resolver, then replace it. This is mostly just testing that
   492  	// things don't crash.
   493  
   494  	assert.NoError(t, f.Delete("ConsulResolver", "default", "consul-dc1"))
   495  	f.Flush()
   496  
   497  	assert.NoError(t, f.UpsertYAML(`
   498  ---
   499  apiVersion: getambassador.io/v3alpha1
   500  kind: ConsulResolver
   501  metadata:
   502    name: consul-dc1
   503  spec:
   504    address: $CONSULHOST:9999
   505    datacenter: dc1
   506  `))
   507  	f.Flush()
   508  
   509  	// Repeat all the checks.
   510  	snaps, err := getSnapshots(f, func(snap *snapshot.Snapshot) bool {
   511  		return (len(snap.Kubernetes.Mappings) > 0) && (len(snap.Kubernetes.TCPMappings) > 0) && (len(snap.Kubernetes.ConsulResolvers) > 0)
   512  	})
   513  	require.NoError(t, err)
   514  	require.Greater(t, len(snaps), 0)
   515  	snap = snaps[len(snaps)-1]
   516  
   517  	// Two deltas here since we've deleted and re-added without a check in between.
   518  	// (They appear out of order here because of string sorting. Don't panic.)
   519  	assert.Equal(t, []string{"add ConsulResolver consul-dc1", "delete ConsulResolver consul-dc1"}, deltaSummary(t, snaps...))
   520  
   521  	// ...one mapping...
   522  	assert.Equal(t, "hello", snap.Kubernetes.Mappings[0].Name)
   523  	assert.Equal(t, "hello-tcp", snap.Kubernetes.TCPMappings[0].Name)
   524  
   525  	// ...and one ConsulResolver.
   526  	assert.Equal(t, "consul-1:9999", snap.Kubernetes.ConsulResolvers[0].Spec.Address)
   527  }
   528  

View as plain text