github.com/mongey/nomad@v0.5.2/command/agent/consul/syncer_test.go (about)

     1  package consul
     2  
     3  import (
     4  	"io/ioutil"
     5  	"log"
     6  	"net"
     7  	"os"
     8  	"reflect"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/hashicorp/consul/api"
    13  	"github.com/hashicorp/consul/testutil"
    14  	"github.com/hashicorp/nomad/nomad/structs"
    15  	"github.com/hashicorp/nomad/nomad/structs/config"
    16  )
    17  
    18  const (
    19  	allocID          = "12"
    20  	serviceRegPrefix = "test"
    21  	serviceGroupName = "executor"
    22  )
    23  
    24  var logger = log.New(os.Stdout, "", log.LstdFlags)
    25  
    26  func TestSyncNow(t *testing.T) {
    27  	cs, testconsul := testConsul(t)
    28  	defer cs.Shutdown()
    29  	defer testconsul.Stop()
    30  
    31  	cs.SetAddrFinder(func(h string) (string, int) {
    32  		a, pstr, _ := net.SplitHostPort(h)
    33  		p, _ := net.LookupPort("tcp", pstr)
    34  		return a, p
    35  	})
    36  	cs.syncInterval = 9000 * time.Hour
    37  
    38  	service := &structs.Service{Name: "foo1", Tags: []string{"a", "b"}}
    39  	services := map[ServiceKey]*structs.Service{
    40  		GenerateServiceKey(service): service,
    41  	}
    42  
    43  	// Run syncs once on startup and then blocks forever
    44  	go cs.Run()
    45  
    46  	if err := cs.SetServices(serviceGroupName, services); err != nil {
    47  		t.Fatalf("error setting services: %v", err)
    48  	}
    49  
    50  	synced := false
    51  	for i := 0; !synced && i < 10; i++ {
    52  		time.Sleep(250 * time.Millisecond)
    53  		agentServices, err := cs.queryAgentServices()
    54  		if err != nil {
    55  			t.Fatalf("error querying consul services: %v", err)
    56  		}
    57  		synced = len(agentServices) == 1
    58  	}
    59  	if !synced {
    60  		t.Fatalf("initial sync never occurred")
    61  	}
    62  
    63  	// SetServices again should cause another sync
    64  	service1 := &structs.Service{Name: "foo1", Tags: []string{"Y", "Z"}}
    65  	service2 := &structs.Service{Name: "bar"}
    66  	services = map[ServiceKey]*structs.Service{
    67  		GenerateServiceKey(service1): service1,
    68  		GenerateServiceKey(service2): service2,
    69  	}
    70  
    71  	if err := cs.SetServices(serviceGroupName, services); err != nil {
    72  		t.Fatalf("error setting services: %v", err)
    73  	}
    74  
    75  	synced = false
    76  	for i := 0; !synced && i < 10; i++ {
    77  		time.Sleep(250 * time.Millisecond)
    78  		agentServices, err := cs.queryAgentServices()
    79  		if err != nil {
    80  			t.Fatalf("error querying consul services: %v", err)
    81  		}
    82  		synced = len(agentServices) == 2
    83  	}
    84  	if !synced {
    85  		t.Fatalf("SetServices didn't sync immediately")
    86  	}
    87  }
    88  
    89  func TestCheckRegistration(t *testing.T) {
    90  	cs, err := NewSyncer(config.DefaultConsulConfig(), make(chan struct{}), logger)
    91  	if err != nil {
    92  		t.Fatalf("Err: %v", err)
    93  	}
    94  
    95  	check1 := structs.ServiceCheck{
    96  		Name:          "check-foo-1",
    97  		Type:          structs.ServiceCheckTCP,
    98  		Interval:      30 * time.Second,
    99  		Timeout:       5 * time.Second,
   100  		InitialStatus: api.HealthPassing,
   101  	}
   102  	check2 := structs.ServiceCheck{
   103  		Name:      "check1",
   104  		Type:      "tcp",
   105  		PortLabel: "port2",
   106  		Interval:  3 * time.Second,
   107  		Timeout:   1 * time.Second,
   108  	}
   109  	check3 := structs.ServiceCheck{
   110  		Name:      "check3",
   111  		Type:      "http",
   112  		PortLabel: "port3",
   113  		Path:      "/health?p1=1&p2=2",
   114  		Interval:  3 * time.Second,
   115  		Timeout:   1 * time.Second,
   116  	}
   117  	service1 := structs.Service{
   118  		Name:      "foo-1",
   119  		Tags:      []string{"tag1", "tag2"},
   120  		PortLabel: "port1",
   121  		Checks: []*structs.ServiceCheck{
   122  			&check1, &check2,
   123  		},
   124  	}
   125  	task := structs.Task{
   126  		Name:     "foo",
   127  		Services: []*structs.Service{&service1},
   128  		Resources: &structs.Resources{
   129  			Networks: []*structs.NetworkResource{
   130  				&structs.NetworkResource{
   131  					IP: "10.10.11.5",
   132  					DynamicPorts: []structs.Port{
   133  						structs.Port{
   134  							Label: "port1",
   135  							Value: 20002,
   136  						},
   137  						structs.Port{
   138  							Label: "port2",
   139  							Value: 20003,
   140  						},
   141  						structs.Port{
   142  							Label: "port3",
   143  							Value: 20004,
   144  						},
   145  					},
   146  				},
   147  			},
   148  		},
   149  	}
   150  	cs.SetAddrFinder(task.FindHostAndPortFor)
   151  	srvReg, _ := cs.createService(&service1, "domain", "key")
   152  	check1Reg, _ := cs.createCheckReg(&check1, srvReg)
   153  	check2Reg, _ := cs.createCheckReg(&check2, srvReg)
   154  	check3Reg, _ := cs.createCheckReg(&check3, srvReg)
   155  
   156  	expected := "10.10.11.5:20002"
   157  	if check1Reg.TCP != expected {
   158  		t.Fatalf("expected: %v, actual: %v", expected, check1Reg.TCP)
   159  	}
   160  
   161  	expected = "10.10.11.5:20003"
   162  	if check2Reg.TCP != expected {
   163  		t.Fatalf("expected: %v, actual: %v", expected, check2Reg.TCP)
   164  	}
   165  
   166  	expected = "http://10.10.11.5:20004/health?p1=1&p2=2"
   167  	if check3Reg.HTTP != expected {
   168  		t.Fatalf("expected: %v, actual: %v", expected, check3Reg.HTTP)
   169  	}
   170  
   171  	expected = api.HealthPassing
   172  	if check1Reg.Status != expected {
   173  		t.Fatalf("expected: %v, actual: %v", expected, check1Reg.Status)
   174  	}
   175  }
   176  
   177  // testConsul returns a Syncer configured with an embedded Consul server.
   178  //
   179  // Callers must defer Syncer.Shutdown() and TestServer.Stop()
   180  //
   181  func testConsul(t *testing.T) (*Syncer, *testutil.TestServer) {
   182  	// Create an embedded Consul server
   183  	testconsul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
   184  		// If -v wasn't specified squelch consul logging
   185  		if !testing.Verbose() {
   186  			c.Stdout = ioutil.Discard
   187  			c.Stderr = ioutil.Discard
   188  		}
   189  	})
   190  
   191  	// Configure Syncer to talk to the test server
   192  	cconf := config.DefaultConsulConfig()
   193  	cconf.Addr = testconsul.HTTPAddr
   194  
   195  	cs, err := NewSyncer(cconf, nil, logger)
   196  	if err != nil {
   197  		t.Fatalf("Error creating Syncer: %v", err)
   198  	}
   199  	return cs, testconsul
   200  }
   201  
   202  func TestConsulServiceRegisterServices(t *testing.T) {
   203  	cs, testconsul := testConsul(t)
   204  	defer cs.Shutdown()
   205  	defer testconsul.Stop()
   206  
   207  	service1 := &structs.Service{Name: "foo", Tags: []string{"a", "b"}}
   208  	service2 := &structs.Service{Name: "foo"}
   209  	services := map[ServiceKey]*structs.Service{
   210  		GenerateServiceKey(service1): service1,
   211  		GenerateServiceKey(service2): service2,
   212  	}
   213  
   214  	// Call SetServices to update services in consul
   215  	if err := cs.SetServices(serviceGroupName, services); err != nil {
   216  		t.Fatalf("error setting services: %v", err)
   217  	}
   218  
   219  	// Manually call SyncServers to cause a synchronous consul update
   220  	if err := cs.SyncServices(); err != nil {
   221  		t.Fatalf("error syncing services: %v", err)
   222  	}
   223  
   224  	numservices := len(cs.flattenedServices())
   225  	if numservices != 2 {
   226  		t.Fatalf("expected 2 services but found %d", numservices)
   227  	}
   228  
   229  	numchecks := len(cs.flattenedChecks())
   230  	if numchecks != 0 {
   231  		t.Fatalf("expected 0 checks but found %d", numchecks)
   232  	}
   233  
   234  	// Assert services are in consul
   235  	agentServices, err := cs.client.Agent().Services()
   236  	if err != nil {
   237  		t.Fatalf("error querying consul services: %v", err)
   238  	}
   239  	found := 0
   240  	for id, as := range agentServices {
   241  		if id == "consul" {
   242  			found++
   243  			continue
   244  		}
   245  		if _, ok := services[ServiceKey(as.Service)]; ok {
   246  			found++
   247  			continue
   248  		}
   249  		t.Errorf("unexpected service in consul: %s", id)
   250  	}
   251  	if found != 3 {
   252  		t.Fatalf("expected 3 services in consul but found %d:\nconsul: %#v", len(agentServices), agentServices)
   253  	}
   254  
   255  	agentChecks, err := cs.queryChecks()
   256  	if err != nil {
   257  		t.Fatalf("error querying consul checks: %v", err)
   258  	}
   259  	if len(agentChecks) != numchecks {
   260  		t.Fatalf("expected %d checks in consul but found %d:\n%#v", numservices, len(agentChecks), agentChecks)
   261  	}
   262  }
   263  
   264  func TestConsulServiceUpdateService(t *testing.T) {
   265  	cs, testconsul := testConsul(t)
   266  	defer cs.Shutdown()
   267  	defer testconsul.Stop()
   268  
   269  	cs.SetAddrFinder(func(h string) (string, int) {
   270  		a, pstr, _ := net.SplitHostPort(h)
   271  		p, _ := net.LookupPort("tcp", pstr)
   272  		return a, p
   273  	})
   274  
   275  	service1 := &structs.Service{Name: "foo1", Tags: []string{"a", "b"}}
   276  	service2 := &structs.Service{Name: "foo2"}
   277  	services := map[ServiceKey]*structs.Service{
   278  		GenerateServiceKey(service1): service1,
   279  		GenerateServiceKey(service2): service2,
   280  	}
   281  	if err := cs.SetServices(serviceGroupName, services); err != nil {
   282  		t.Fatalf("error setting services: %v", err)
   283  	}
   284  	if err := cs.SyncServices(); err != nil {
   285  		t.Fatalf("error syncing services: %v", err)
   286  	}
   287  
   288  	// Now update both services
   289  	service1 = &structs.Service{Name: "foo1", Tags: []string{"a", "z"}}
   290  	service2 = &structs.Service{Name: "foo2", PortLabel: ":8899"}
   291  	service3 := &structs.Service{Name: "foo3"}
   292  	services = map[ServiceKey]*structs.Service{
   293  		GenerateServiceKey(service1): service1,
   294  		GenerateServiceKey(service2): service2,
   295  		GenerateServiceKey(service3): service3,
   296  	}
   297  	if err := cs.SetServices(serviceGroupName, services); err != nil {
   298  		t.Fatalf("error setting services: %v", err)
   299  	}
   300  	if err := cs.SyncServices(); err != nil {
   301  		t.Fatalf("error syncing services: %v", err)
   302  	}
   303  
   304  	agentServices, err := cs.queryAgentServices()
   305  	if err != nil {
   306  		t.Fatalf("error querying consul services: %v", err)
   307  	}
   308  	if len(agentServices) != 3 {
   309  		t.Fatalf("expected 3 services in consul but found %d:\n%#v", len(agentServices), agentServices)
   310  	}
   311  	consulServices := make(map[string]*api.AgentService, 3)
   312  	for _, as := range agentServices {
   313  		consulServices[as.ID] = as
   314  	}
   315  
   316  	found := 0
   317  	for _, s := range cs.flattenedServices() {
   318  		// Assert sure changes were applied to internal state
   319  		switch s.Name {
   320  		case "foo1":
   321  			found++
   322  			if !reflect.DeepEqual(service1.Tags, s.Tags) {
   323  				t.Errorf("incorrect tags on foo1:\n  expected: %v\n  found: %v", service1.Tags, s.Tags)
   324  			}
   325  		case "foo2":
   326  			found++
   327  			if s.Address != "" {
   328  				t.Errorf("expected empty host on foo2 but found %q", s.Address)
   329  			}
   330  			if s.Port != 8899 {
   331  				t.Errorf("expected port 8899 on foo2 but found %d", s.Port)
   332  			}
   333  		case "foo3":
   334  			found++
   335  		default:
   336  			t.Errorf("unexpected service: %s", s.Name)
   337  		}
   338  
   339  		// Assert internal state equals consul's state
   340  		cs, ok := consulServices[s.ID]
   341  		if !ok {
   342  			t.Errorf("service not in consul: %s id: %s", s.Name, s.ID)
   343  			continue
   344  		}
   345  		if !reflect.DeepEqual(s.Tags, cs.Tags) {
   346  			t.Errorf("mismatched tags in syncer state and consul for %s:\nsyncer: %v\nconsul: %v", s.Name, s.Tags, cs.Tags)
   347  		}
   348  		if cs.Port != s.Port {
   349  			t.Errorf("mismatched port in syncer state and consul for %s\nsyncer: %v\nconsul: %v", s.Name, s.Port, cs.Port)
   350  		}
   351  		if cs.Address != s.Address {
   352  			t.Errorf("mismatched address in syncer state and consul for %s\nsyncer: %v\nconsul: %v", s.Name, s.Address, cs.Address)
   353  		}
   354  	}
   355  	if found != 3 {
   356  		t.Fatalf("expected 3 services locally but found %d", found)
   357  	}
   358  }