github.com/sl1pm4t/consul@v1.4.5-0.20190325224627-74c31c540f9c/agent/router/router_test.go (about)

     1  package router
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"net"
     7  	"os"
     8  	"reflect"
     9  	"sort"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/hashicorp/consul/agent/structs"
    15  	"github.com/hashicorp/consul/lib"
    16  	"github.com/hashicorp/consul/types"
    17  	"github.com/hashicorp/serf/coordinate"
    18  	"github.com/hashicorp/serf/serf"
    19  )
    20  
    21  type mockCluster struct {
    22  	self    string
    23  	members []serf.Member
    24  	coords  map[string]*coordinate.Coordinate
    25  	addr    int
    26  }
    27  
    28  func newMockCluster(self string) *mockCluster {
    29  	return &mockCluster{
    30  		self:   self,
    31  		coords: make(map[string]*coordinate.Coordinate),
    32  		addr:   1,
    33  	}
    34  }
    35  
    36  func (m *mockCluster) NumNodes() int {
    37  	return len(m.members)
    38  }
    39  
    40  func (m *mockCluster) Members() []serf.Member {
    41  	return m.members
    42  }
    43  
    44  func (m *mockCluster) GetCoordinate() (*coordinate.Coordinate, error) {
    45  	return m.coords[m.self], nil
    46  }
    47  
    48  func (m *mockCluster) GetCachedCoordinate(name string) (*coordinate.Coordinate, bool) {
    49  	coord, ok := m.coords[name]
    50  	return coord, ok
    51  }
    52  
    53  func (m *mockCluster) AddMember(dc string, name string, coord *coordinate.Coordinate) {
    54  	member := serf.Member{
    55  		Name: fmt.Sprintf("%s.%s", name, dc),
    56  		Addr: net.ParseIP(fmt.Sprintf("127.0.0.%d", m.addr)),
    57  		Port: 8300,
    58  		Tags: map[string]string{
    59  			"dc":    dc,
    60  			"role":  "consul",
    61  			"port":  "8300",
    62  			"build": "0.8.0",
    63  			"vsn":   "3",
    64  		},
    65  	}
    66  	m.members = append(m.members, member)
    67  	if coord != nil {
    68  		m.coords[member.Name] = coord
    69  	}
    70  	m.addr++
    71  }
    72  
    73  // testCluster is used to generate a single WAN-like area with a known set of
    74  // member and RTT topology.
    75  //
    76  // Here's the layout of the nodes:
    77  //
    78  //            /----   dc1         ----\         /-  dc2  -\ /-  dc0  -\
    79  //             node2 node1       node3             node1       node0
    80  //   |     |     |     |     |     |     |     |     |     |     |
    81  //   0     1     2     3     4     5     6     7     8     9     10  (ms)
    82  //
    83  // We also include a node4 in dc1 with no known coordinate, as well as a
    84  // mysterious dcX with no nodes with known coordinates.
    85  func testCluster(self string) *mockCluster {
    86  	c := newMockCluster(self)
    87  	c.AddMember("dc0", "node0", lib.GenerateCoordinate(10*time.Millisecond))
    88  	c.AddMember("dc1", "node1", lib.GenerateCoordinate(3*time.Millisecond))
    89  	c.AddMember("dc1", "node2", lib.GenerateCoordinate(2*time.Millisecond))
    90  	c.AddMember("dc1", "node3", lib.GenerateCoordinate(5*time.Millisecond))
    91  	c.AddMember("dc1", "node4", nil)
    92  	c.AddMember("dc2", "node1", lib.GenerateCoordinate(8*time.Millisecond))
    93  	c.AddMember("dcX", "node1", nil)
    94  	return c
    95  }
    96  
    97  func testRouter(dc string) *Router {
    98  	logger := log.New(os.Stderr, "", log.LstdFlags)
    99  	return NewRouter(logger, dc)
   100  }
   101  
   102  func TestRouter_Shutdown(t *testing.T) {
   103  	r := testRouter("dc0")
   104  
   105  	// Create a WAN-looking area.
   106  	self := "node0.dc0"
   107  	wan := testCluster(self)
   108  	if err := r.AddArea(types.AreaWAN, wan, &fauxConnPool{}, false); err != nil {
   109  		t.Fatalf("err: %v", err)
   110  	}
   111  
   112  	// Add another area.
   113  	otherID := types.AreaID("other")
   114  	other := newMockCluster(self)
   115  	other.AddMember("dcY", "node1", nil)
   116  	if err := r.AddArea(otherID, other, &fauxConnPool{}, false); err != nil {
   117  		t.Fatalf("err: %v", err)
   118  	}
   119  	_, _, ok := r.FindRoute("dcY")
   120  	if !ok {
   121  		t.Fatalf("bad")
   122  	}
   123  
   124  	// Shutdown and make sure we can't see any routes from before.
   125  	r.Shutdown()
   126  	_, _, ok = r.FindRoute("dcY")
   127  	if ok {
   128  		t.Fatalf("bad")
   129  	}
   130  
   131  	// You can't add areas once the router is shut down.
   132  	err := r.AddArea(otherID, other, &fauxConnPool{}, false)
   133  	if err == nil || !strings.Contains(err.Error(), "router is shut down") {
   134  		t.Fatalf("err: %v", err)
   135  	}
   136  }
   137  
   138  func TestRouter_Routing(t *testing.T) {
   139  	r := testRouter("dc0")
   140  
   141  	// Create a WAN-looking area.
   142  	self := "node0.dc0"
   143  	wan := testCluster(self)
   144  	if err := r.AddArea(types.AreaWAN, wan, &fauxConnPool{}, false); err != nil {
   145  		t.Fatalf("err: %v", err)
   146  	}
   147  
   148  	// Adding the area should enable all the routes right away.
   149  	if _, _, ok := r.FindRoute("dc0"); !ok {
   150  		t.Fatalf("bad")
   151  	}
   152  	if _, _, ok := r.FindRoute("dc1"); !ok {
   153  		t.Fatalf("bad")
   154  	}
   155  	if _, _, ok := r.FindRoute("dc2"); !ok {
   156  		t.Fatalf("bad")
   157  	}
   158  	if _, _, ok := r.FindRoute("dcX"); !ok {
   159  		t.Fatalf("bad")
   160  	}
   161  
   162  	// This hasn't been added yet.
   163  	if _, _, ok := r.FindRoute("dcY"); ok {
   164  		t.Fatalf("bad")
   165  	}
   166  
   167  	// Add another area.
   168  	otherID := types.AreaID("other")
   169  	other := newMockCluster(self)
   170  	other.AddMember("dc0", "node0", nil)
   171  	other.AddMember("dc1", "node1", nil)
   172  	other.AddMember("dcY", "node1", nil)
   173  	if err := r.AddArea(otherID, other, &fauxConnPool{}, false); err != nil {
   174  		t.Fatalf("err: %v", err)
   175  	}
   176  
   177  	// Now we should have a route to every DC.
   178  	if _, _, ok := r.FindRoute("dc0"); !ok {
   179  		t.Fatalf("bad")
   180  	}
   181  	if _, _, ok := r.FindRoute("dc1"); !ok {
   182  		t.Fatalf("bad")
   183  	}
   184  	if _, _, ok := r.FindRoute("dc2"); !ok {
   185  		t.Fatalf("bad")
   186  	}
   187  	if _, _, ok := r.FindRoute("dcX"); !ok {
   188  		t.Fatalf("bad")
   189  	}
   190  	if _, _, ok := r.FindRoute("dcY"); !ok {
   191  		t.Fatalf("bad")
   192  	}
   193  
   194  	// Get the route for dcY and then fail the server. This will still
   195  	// give the server back since we have no other choice.
   196  	_, s, ok := r.FindRoute("dcY")
   197  	if !ok {
   198  		t.Fatalf("bad")
   199  	}
   200  	if err := r.FailServer(otherID, s); err != nil {
   201  		t.Fatalf("err: %v", err)
   202  	}
   203  	if _, _, ok := r.FindRoute("dcY"); !ok {
   204  		t.Fatalf("bad")
   205  	}
   206  
   207  	// But if we remove the server we won't get a route.
   208  	if err := r.RemoveServer(otherID, s); err != nil {
   209  		t.Fatalf("err: %v", err)
   210  	}
   211  	if _, _, ok := r.FindRoute("dcY"); ok {
   212  		t.Fatalf("bad")
   213  	}
   214  
   215  	// Make sure the dcY manager also got removed from the area and from
   216  	// the index we use for routing.
   217  	func() {
   218  		r.RLock()
   219  		defer r.RUnlock()
   220  
   221  		area, ok := r.areas[otherID]
   222  		if !ok {
   223  			t.Fatalf("bad")
   224  		}
   225  
   226  		if _, ok := area.managers["dcY"]; ok {
   227  			t.Fatalf("bad")
   228  		}
   229  
   230  		if _, ok := r.managers["dcY"]; ok {
   231  			t.Fatalf("bad")
   232  		}
   233  	}()
   234  
   235  	// Do similar for dc0, which will take two removes because the dc0 is
   236  	// reachable from two different areas.
   237  	_, s, ok = r.FindRoute("dc0")
   238  	if !ok {
   239  		t.Fatalf("bad")
   240  	}
   241  	if err := r.RemoveServer(types.AreaWAN, s); err != nil {
   242  		t.Fatalf("err: %v", err)
   243  	}
   244  	if _, _, ok = r.FindRoute("dc0"); !ok {
   245  		t.Fatalf("bad")
   246  	}
   247  	if err := r.RemoveServer(otherID, s); err != nil {
   248  		t.Fatalf("err: %v", err)
   249  	}
   250  	if _, _, ok = r.FindRoute("dc0"); ok {
   251  		t.Fatalf("bad")
   252  	}
   253  
   254  	// Now delete some areas.
   255  	if _, _, ok = r.FindRoute("dc1"); !ok {
   256  		t.Fatalf("bad")
   257  	}
   258  	if err := r.RemoveArea(types.AreaWAN); err != nil {
   259  		t.Fatalf("err: %v", err)
   260  	}
   261  	if _, _, ok = r.FindRoute("dc1"); !ok {
   262  		t.Fatalf("bad")
   263  	}
   264  	if err := r.RemoveArea(otherID); err != nil {
   265  		t.Fatalf("err: %v", err)
   266  	}
   267  	if _, _, ok = r.FindRoute("dc1"); ok {
   268  		t.Fatalf("bad")
   269  	}
   270  }
   271  
   272  func TestRouter_Routing_Offline(t *testing.T) {
   273  	r := testRouter("dc0")
   274  
   275  	// Create a WAN-looking area.
   276  	self := "node0.dc0"
   277  	wan := testCluster(self)
   278  	if err := r.AddArea(types.AreaWAN, wan, &fauxConnPool{1.0}, false); err != nil {
   279  		t.Fatalf("err: %v", err)
   280  	}
   281  
   282  	// Adding the area should enable all the routes right away.
   283  	if _, _, ok := r.FindRoute("dc0"); !ok {
   284  		t.Fatalf("bad")
   285  	}
   286  	if _, _, ok := r.FindRoute("dc1"); !ok {
   287  		t.Fatalf("bad")
   288  	}
   289  	if _, _, ok := r.FindRoute("dc2"); !ok {
   290  		t.Fatalf("bad")
   291  	}
   292  	if _, _, ok := r.FindRoute("dcX"); !ok {
   293  		t.Fatalf("bad")
   294  	}
   295  
   296  	// Do a rebalance for dc1, which should knock it offline.
   297  	func() {
   298  		r.Lock()
   299  		defer r.Unlock()
   300  
   301  		area, ok := r.areas[types.AreaWAN]
   302  		if !ok {
   303  			t.Fatalf("bad")
   304  		}
   305  
   306  		info, ok := area.managers["dc1"]
   307  		if !ok {
   308  			t.Fatalf("bad")
   309  		}
   310  		info.manager.RebalanceServers()
   311  	}()
   312  
   313  	// Recheck all the routes.
   314  	if _, _, ok := r.FindRoute("dc0"); !ok {
   315  		t.Fatalf("bad")
   316  	}
   317  	if _, _, ok := r.FindRoute("dc1"); ok {
   318  		t.Fatalf("bad")
   319  	}
   320  	if _, _, ok := r.FindRoute("dc2"); !ok {
   321  		t.Fatalf("bad")
   322  	}
   323  	if _, _, ok := r.FindRoute("dcX"); !ok {
   324  		t.Fatalf("bad")
   325  	}
   326  
   327  	// Add another area with a route to dc1.
   328  	otherID := types.AreaID("other")
   329  	other := newMockCluster(self)
   330  	other.AddMember("dc0", "node0", nil)
   331  	other.AddMember("dc1", "node1", nil)
   332  	if err := r.AddArea(otherID, other, &fauxConnPool{}, false); err != nil {
   333  		t.Fatalf("err: %v", err)
   334  	}
   335  
   336  	// Recheck all the routes and make sure it finds the one that's
   337  	// online.
   338  	if _, _, ok := r.FindRoute("dc0"); !ok {
   339  		t.Fatalf("bad")
   340  	}
   341  	if _, _, ok := r.FindRoute("dc1"); !ok {
   342  		t.Fatalf("bad")
   343  	}
   344  	if _, _, ok := r.FindRoute("dc2"); !ok {
   345  		t.Fatalf("bad")
   346  	}
   347  	if _, _, ok := r.FindRoute("dcX"); !ok {
   348  		t.Fatalf("bad")
   349  	}
   350  }
   351  
   352  func TestRouter_GetDatacenters(t *testing.T) {
   353  	r := testRouter("dc0")
   354  
   355  	self := "node0.dc0"
   356  	wan := testCluster(self)
   357  	if err := r.AddArea(types.AreaWAN, wan, &fauxConnPool{}, false); err != nil {
   358  		t.Fatalf("err: %v", err)
   359  	}
   360  
   361  	actual := r.GetDatacenters()
   362  	expected := []string{"dc0", "dc1", "dc2", "dcX"}
   363  	if !reflect.DeepEqual(actual, expected) {
   364  		t.Fatalf("bad: %#v", actual)
   365  	}
   366  }
   367  
   368  func TestRouter_distanceSorter(t *testing.T) {
   369  	actual := &datacenterSorter{
   370  		Names: []string{"foo", "bar", "baz", "zoo"},
   371  		Vec:   []float64{3.0, 1.0, 1.0, 0.0},
   372  	}
   373  	sort.Stable(actual)
   374  	expected := &datacenterSorter{
   375  		Names: []string{"zoo", "bar", "baz", "foo"},
   376  		Vec:   []float64{0.0, 1.0, 1.0, 3.0},
   377  	}
   378  	if !reflect.DeepEqual(actual, expected) {
   379  		t.Fatalf("bad: %#v", *expected)
   380  	}
   381  }
   382  
   383  func TestRouter_GetDatacentersByDistance(t *testing.T) {
   384  	r := testRouter("dc0")
   385  
   386  	// Start with just the WAN area described in the diagram above.
   387  	self := "node0.dc0"
   388  	wan := testCluster(self)
   389  	if err := r.AddArea(types.AreaWAN, wan, &fauxConnPool{}, false); err != nil {
   390  		t.Fatalf("err: %v", err)
   391  	}
   392  
   393  	actual, err := r.GetDatacentersByDistance()
   394  	if err != nil {
   395  		t.Fatalf("err: %v", err)
   396  	}
   397  	expected := []string{"dc0", "dc2", "dc1", "dcX"}
   398  	if !reflect.DeepEqual(actual, expected) {
   399  		t.Fatalf("bad: %#v", actual)
   400  	}
   401  
   402  	// Now add another area with a closer route for dc1.
   403  	otherID := types.AreaID("other")
   404  	other := newMockCluster(self)
   405  	other.AddMember("dc0", "node0", lib.GenerateCoordinate(20*time.Millisecond))
   406  	other.AddMember("dc1", "node1", lib.GenerateCoordinate(21*time.Millisecond))
   407  	if err := r.AddArea(otherID, other, &fauxConnPool{}, false); err != nil {
   408  		t.Fatalf("err: %v", err)
   409  	}
   410  
   411  	actual, err = r.GetDatacentersByDistance()
   412  	if err != nil {
   413  		t.Fatalf("err: %v", err)
   414  	}
   415  	expected = []string{"dc0", "dc1", "dc2", "dcX"}
   416  	if !reflect.DeepEqual(actual, expected) {
   417  		t.Fatalf("bad: %#v", actual)
   418  	}
   419  }
   420  
   421  func TestRouter_GetDatacenterMaps(t *testing.T) {
   422  	r := testRouter("dc0")
   423  
   424  	self := "node0.dc0"
   425  	wan := testCluster(self)
   426  	if err := r.AddArea(types.AreaWAN, wan, &fauxConnPool{}, false); err != nil {
   427  		t.Fatalf("err: %v", err)
   428  	}
   429  
   430  	actual, err := r.GetDatacenterMaps()
   431  	if err != nil {
   432  		t.Fatalf("err: %v", err)
   433  	}
   434  	if len(actual) != 3 {
   435  		t.Fatalf("bad: %#v", actual)
   436  	}
   437  	for _, entry := range actual {
   438  		switch entry.Datacenter {
   439  		case "dc0":
   440  			if !reflect.DeepEqual(entry, structs.DatacenterMap{
   441  				Datacenter: "dc0",
   442  				AreaID:     types.AreaWAN,
   443  				Coordinates: structs.Coordinates{
   444  					&structs.Coordinate{
   445  						Node:  "node0.dc0",
   446  						Coord: lib.GenerateCoordinate(10 * time.Millisecond),
   447  					},
   448  				},
   449  			}) {
   450  				t.Fatalf("bad: %#v", entry)
   451  			}
   452  		case "dc1":
   453  			if !reflect.DeepEqual(entry, structs.DatacenterMap{
   454  				Datacenter: "dc1",
   455  				AreaID:     types.AreaWAN,
   456  				Coordinates: structs.Coordinates{
   457  					&structs.Coordinate{
   458  						Node:  "node1.dc1",
   459  						Coord: lib.GenerateCoordinate(3 * time.Millisecond),
   460  					},
   461  					&structs.Coordinate{
   462  						Node:  "node2.dc1",
   463  						Coord: lib.GenerateCoordinate(2 * time.Millisecond),
   464  					},
   465  					&structs.Coordinate{
   466  						Node:  "node3.dc1",
   467  						Coord: lib.GenerateCoordinate(5 * time.Millisecond),
   468  					},
   469  				},
   470  			}) {
   471  				t.Fatalf("bad: %#v", entry)
   472  			}
   473  		case "dc2":
   474  			if !reflect.DeepEqual(entry, structs.DatacenterMap{
   475  				Datacenter: "dc2",
   476  				AreaID:     types.AreaWAN,
   477  				Coordinates: structs.Coordinates{
   478  					&structs.Coordinate{
   479  						Node:  "node1.dc2",
   480  						Coord: lib.GenerateCoordinate(8 * time.Millisecond),
   481  					},
   482  				},
   483  			}) {
   484  				t.Fatalf("bad: %#v", entry)
   485  			}
   486  		default:
   487  			t.Fatalf("bad: %#v", entry)
   488  		}
   489  	}
   490  }