github.com/KyaXTeam/consul@v1.4.5/watch/funcs_test.go (about)

     1  package watch_test
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/hashicorp/consul/testrpc"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  
    14  	"github.com/hashicorp/consul/agent"
    15  	"github.com/hashicorp/consul/agent/connect"
    16  	consulapi "github.com/hashicorp/consul/api"
    17  	"github.com/hashicorp/consul/watch"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  var errBadContent = errors.New("bad content")
    22  var errTimeout = errors.New("timeout")
    23  
    24  var timeout = 5 * time.Second
    25  
    26  func makeInvokeCh() chan error {
    27  	ch := make(chan error)
    28  	time.AfterFunc(timeout, func() { ch <- errTimeout })
    29  	return ch
    30  }
    31  
    32  func TestKeyWatch(t *testing.T) {
    33  	t.Parallel()
    34  	a := agent.NewTestAgent(t, t.Name(), ``)
    35  	defer a.Shutdown()
    36  	testrpc.WaitForTestAgent(t, a.RPC, "dc1")
    37  
    38  	invoke := makeInvokeCh()
    39  	plan := mustParse(t, `{"type":"key", "key":"foo/bar/baz"}`)
    40  	plan.Handler = func(idx uint64, raw interface{}) {
    41  		if raw == nil {
    42  			return // ignore
    43  		}
    44  		v, ok := raw.(*consulapi.KVPair)
    45  		if !ok || v == nil {
    46  			return // ignore
    47  		}
    48  		if string(v.Value) != "test" {
    49  			invoke <- errBadContent
    50  			return
    51  		}
    52  		invoke <- nil
    53  	}
    54  
    55  	var wg sync.WaitGroup
    56  	wg.Add(1)
    57  	go func() {
    58  		defer wg.Done()
    59  		kv := a.Client().KV()
    60  
    61  		time.Sleep(20 * time.Millisecond)
    62  		pair := &consulapi.KVPair{
    63  			Key:   "foo/bar/baz",
    64  			Value: []byte("test"),
    65  		}
    66  		if _, err := kv.Put(pair, nil); err != nil {
    67  			t.Fatalf("err: %v", err)
    68  		}
    69  	}()
    70  
    71  	wg.Add(1)
    72  	go func() {
    73  		defer wg.Done()
    74  		if err := plan.Run(a.HTTPAddr()); err != nil {
    75  			t.Fatalf("err: %v", err)
    76  		}
    77  	}()
    78  
    79  	if err := <-invoke; err != nil {
    80  		t.Fatalf("err: %v", err)
    81  	}
    82  
    83  	plan.Stop()
    84  	wg.Wait()
    85  }
    86  
    87  func TestKeyWatch_With_PrefixDelete(t *testing.T) {
    88  	t.Parallel()
    89  	a := agent.NewTestAgent(t, t.Name(), ``)
    90  	defer a.Shutdown()
    91  	testrpc.WaitForTestAgent(t, a.RPC, "dc1")
    92  
    93  	invoke := makeInvokeCh()
    94  	plan := mustParse(t, `{"type":"key", "key":"foo/bar/baz"}`)
    95  	plan.Handler = func(idx uint64, raw interface{}) {
    96  		if raw == nil {
    97  			return // ignore
    98  		}
    99  		v, ok := raw.(*consulapi.KVPair)
   100  		if !ok || v == nil {
   101  			return // ignore
   102  		}
   103  		if string(v.Value) != "test" {
   104  			invoke <- errBadContent
   105  			return
   106  		}
   107  		invoke <- nil
   108  	}
   109  
   110  	var wg sync.WaitGroup
   111  	wg.Add(1)
   112  	go func() {
   113  		defer wg.Done()
   114  		kv := a.Client().KV()
   115  
   116  		time.Sleep(20 * time.Millisecond)
   117  		pair := &consulapi.KVPair{
   118  			Key:   "foo/bar/baz",
   119  			Value: []byte("test"),
   120  		}
   121  		if _, err := kv.Put(pair, nil); err != nil {
   122  			t.Fatalf("err: %v", err)
   123  		}
   124  	}()
   125  
   126  	wg.Add(1)
   127  	go func() {
   128  		defer wg.Done()
   129  		if err := plan.Run(a.HTTPAddr()); err != nil {
   130  			t.Fatalf("err: %v", err)
   131  		}
   132  	}()
   133  
   134  	if err := <-invoke; err != nil {
   135  		t.Fatalf("err: %v", err)
   136  	}
   137  
   138  	plan.Stop()
   139  	wg.Wait()
   140  }
   141  
   142  func TestKeyPrefixWatch(t *testing.T) {
   143  	t.Parallel()
   144  	a := agent.NewTestAgent(t, t.Name(), ``)
   145  	defer a.Shutdown()
   146  	testrpc.WaitForTestAgent(t, a.RPC, "dc1")
   147  
   148  	invoke := makeInvokeCh()
   149  	plan := mustParse(t, `{"type":"keyprefix", "prefix":"foo/"}`)
   150  	plan.Handler = func(idx uint64, raw interface{}) {
   151  		if raw == nil {
   152  			return // ignore
   153  		}
   154  		v, ok := raw.(consulapi.KVPairs)
   155  		if !ok || len(v) == 0 {
   156  			return
   157  		}
   158  		if string(v[0].Key) != "foo/bar" {
   159  			invoke <- errBadContent
   160  			return
   161  		}
   162  		invoke <- nil
   163  	}
   164  
   165  	var wg sync.WaitGroup
   166  	wg.Add(1)
   167  	go func() {
   168  		defer wg.Done()
   169  		kv := a.Client().KV()
   170  
   171  		time.Sleep(20 * time.Millisecond)
   172  		pair := &consulapi.KVPair{
   173  			Key: "foo/bar",
   174  		}
   175  		if _, err := kv.Put(pair, nil); err != nil {
   176  			t.Fatalf("err: %v", err)
   177  		}
   178  	}()
   179  
   180  	wg.Add(1)
   181  	go func() {
   182  		defer wg.Done()
   183  		if err := plan.Run(a.HTTPAddr()); err != nil {
   184  			t.Fatalf("err: %v", err)
   185  		}
   186  	}()
   187  
   188  	if err := <-invoke; err != nil {
   189  		t.Fatalf("err: %v", err)
   190  	}
   191  
   192  	plan.Stop()
   193  	wg.Wait()
   194  }
   195  
   196  func TestServicesWatch(t *testing.T) {
   197  	t.Parallel()
   198  	a := agent.NewTestAgent(t, t.Name(), ``)
   199  	defer a.Shutdown()
   200  	testrpc.WaitForTestAgent(t, a.RPC, "dc1")
   201  
   202  	invoke := makeInvokeCh()
   203  	plan := mustParse(t, `{"type":"services"}`)
   204  	plan.Handler = func(idx uint64, raw interface{}) {
   205  		if raw == nil {
   206  			return // ignore
   207  		}
   208  		v, ok := raw.(map[string][]string)
   209  		if !ok || len(v) == 0 {
   210  			return // ignore
   211  		}
   212  		if v["consul"] == nil {
   213  			invoke <- errBadContent
   214  			return
   215  		}
   216  		invoke <- nil
   217  	}
   218  
   219  	var wg sync.WaitGroup
   220  	wg.Add(1)
   221  	go func() {
   222  		defer wg.Done()
   223  		agent := a.Client().Agent()
   224  
   225  		time.Sleep(20 * time.Millisecond)
   226  		reg := &consulapi.AgentServiceRegistration{
   227  			ID:   "foo",
   228  			Name: "foo",
   229  		}
   230  		if err := agent.ServiceRegister(reg); err != nil {
   231  			t.Fatalf("err: %v", err)
   232  		}
   233  	}()
   234  
   235  	wg.Add(1)
   236  	go func() {
   237  		defer wg.Done()
   238  		if err := plan.Run(a.HTTPAddr()); err != nil {
   239  			t.Fatalf("err: %v", err)
   240  		}
   241  	}()
   242  
   243  	if err := <-invoke; err != nil {
   244  		t.Fatalf("err: %v", err)
   245  	}
   246  
   247  	plan.Stop()
   248  	wg.Wait()
   249  }
   250  
   251  func TestNodesWatch(t *testing.T) {
   252  	t.Parallel()
   253  	a := agent.NewTestAgent(t, t.Name(), ``)
   254  	defer a.Shutdown()
   255  	testrpc.WaitForTestAgent(t, a.RPC, "dc1")
   256  
   257  	invoke := makeInvokeCh()
   258  	plan := mustParse(t, `{"type":"nodes"}`)
   259  	plan.Handler = func(idx uint64, raw interface{}) {
   260  		if raw == nil {
   261  			return // ignore
   262  		}
   263  		v, ok := raw.([]*consulapi.Node)
   264  		if !ok || len(v) == 0 {
   265  			return // ignore
   266  		}
   267  		invoke <- nil
   268  	}
   269  
   270  	var wg sync.WaitGroup
   271  	wg.Add(1)
   272  	go func() {
   273  		defer wg.Done()
   274  		catalog := a.Client().Catalog()
   275  
   276  		time.Sleep(20 * time.Millisecond)
   277  		reg := &consulapi.CatalogRegistration{
   278  			Node:       "foobar",
   279  			Address:    "1.1.1.1",
   280  			Datacenter: "dc1",
   281  		}
   282  		if _, err := catalog.Register(reg, nil); err != nil {
   283  			t.Fatalf("err: %v", err)
   284  		}
   285  	}()
   286  
   287  	wg.Add(1)
   288  	go func() {
   289  		defer wg.Done()
   290  		if err := plan.Run(a.HTTPAddr()); err != nil {
   291  			t.Fatalf("err: %v", err)
   292  		}
   293  	}()
   294  
   295  	if err := <-invoke; err != nil {
   296  		t.Fatalf("err: %v", err)
   297  	}
   298  
   299  	plan.Stop()
   300  	wg.Wait()
   301  }
   302  
   303  func TestServiceWatch(t *testing.T) {
   304  	t.Parallel()
   305  	a := agent.NewTestAgent(t, t.Name(), ``)
   306  	defer a.Shutdown()
   307  	testrpc.WaitForTestAgent(t, a.RPC, "dc1")
   308  
   309  	invoke := makeInvokeCh()
   310  	plan := mustParse(t, `{"type":"service", "service":"foo", "tag":"bar", "passingonly":true}`)
   311  	plan.Handler = func(idx uint64, raw interface{}) {
   312  		if raw == nil {
   313  			return // ignore
   314  		}
   315  		v, ok := raw.([]*consulapi.ServiceEntry)
   316  		if !ok || len(v) == 0 {
   317  			return // ignore
   318  		}
   319  		if v[0].Service.ID != "foo" {
   320  			invoke <- errBadContent
   321  			return
   322  		}
   323  		invoke <- nil
   324  	}
   325  
   326  	var wg sync.WaitGroup
   327  
   328  	wg.Add(1)
   329  	go func() {
   330  		defer wg.Done()
   331  		agent := a.Client().Agent()
   332  
   333  		time.Sleep(20 * time.Millisecond)
   334  		reg := &consulapi.AgentServiceRegistration{
   335  			ID:   "foo",
   336  			Name: "foo",
   337  			Tags: []string{"bar"},
   338  		}
   339  		if err := agent.ServiceRegister(reg); err != nil {
   340  			t.Fatalf("err: %v", err)
   341  		}
   342  	}()
   343  
   344  	wg.Add(1)
   345  	go func() {
   346  		defer wg.Done()
   347  		if err := plan.Run(a.HTTPAddr()); err != nil {
   348  			t.Fatalf("err: %v", err)
   349  		}
   350  	}()
   351  
   352  	if err := <-invoke; err != nil {
   353  		t.Fatalf("err: %v", err)
   354  	}
   355  
   356  	plan.Stop()
   357  	wg.Wait()
   358  }
   359  
   360  func TestChecksWatch_State(t *testing.T) {
   361  	t.Parallel()
   362  	a := agent.NewTestAgent(t, t.Name(), ``)
   363  	defer a.Shutdown()
   364  	testrpc.WaitForTestAgent(t, a.RPC, "dc1")
   365  
   366  	invoke := makeInvokeCh()
   367  	plan := mustParse(t, `{"type":"checks", "state":"warning"}`)
   368  	plan.Handler = func(idx uint64, raw interface{}) {
   369  		if raw == nil {
   370  			return // ignore
   371  		}
   372  		v, ok := raw.([]*consulapi.HealthCheck)
   373  		if !ok || len(v) == 0 {
   374  			return // ignore
   375  		}
   376  		if v[0].CheckID != "foobar" || v[0].Status != "warning" {
   377  			invoke <- errBadContent
   378  			return
   379  		}
   380  		invoke <- nil
   381  	}
   382  
   383  	var wg sync.WaitGroup
   384  	wg.Add(1)
   385  	go func() {
   386  		defer wg.Done()
   387  		catalog := a.Client().Catalog()
   388  
   389  		time.Sleep(20 * time.Millisecond)
   390  		reg := &consulapi.CatalogRegistration{
   391  			Node:       "foobar",
   392  			Address:    "1.1.1.1",
   393  			Datacenter: "dc1",
   394  			Check: &consulapi.AgentCheck{
   395  				Node:    "foobar",
   396  				CheckID: "foobar",
   397  				Name:    "foobar",
   398  				Status:  consulapi.HealthWarning,
   399  			},
   400  		}
   401  		if _, err := catalog.Register(reg, nil); err != nil {
   402  			t.Fatalf("err: %v", err)
   403  		}
   404  	}()
   405  
   406  	wg.Add(1)
   407  	go func() {
   408  		defer wg.Done()
   409  		if err := plan.Run(a.HTTPAddr()); err != nil {
   410  			t.Fatalf("err: %v", err)
   411  		}
   412  	}()
   413  
   414  	if err := <-invoke; err != nil {
   415  		t.Fatalf("err: %v", err)
   416  	}
   417  
   418  	plan.Stop()
   419  	wg.Wait()
   420  }
   421  
   422  func TestChecksWatch_Service(t *testing.T) {
   423  	t.Parallel()
   424  	a := agent.NewTestAgent(t, t.Name(), ``)
   425  	defer a.Shutdown()
   426  	testrpc.WaitForTestAgent(t, a.RPC, "dc1")
   427  
   428  	invoke := makeInvokeCh()
   429  	plan := mustParse(t, `{"type":"checks", "service":"foobar"}`)
   430  	plan.Handler = func(idx uint64, raw interface{}) {
   431  		if raw == nil {
   432  			return // ignore
   433  		}
   434  		v, ok := raw.([]*consulapi.HealthCheck)
   435  		if !ok || len(v) == 0 {
   436  			return // ignore
   437  		}
   438  		if v[0].CheckID != "foobar" {
   439  			invoke <- errBadContent
   440  			return
   441  		}
   442  		invoke <- nil
   443  	}
   444  
   445  	var wg sync.WaitGroup
   446  	wg.Add(1)
   447  	go func() {
   448  		defer wg.Done()
   449  		catalog := a.Client().Catalog()
   450  
   451  		time.Sleep(20 * time.Millisecond)
   452  		reg := &consulapi.CatalogRegistration{
   453  			Node:       "foobar",
   454  			Address:    "1.1.1.1",
   455  			Datacenter: "dc1",
   456  			Service: &consulapi.AgentService{
   457  				ID:      "foobar",
   458  				Service: "foobar",
   459  			},
   460  			Check: &consulapi.AgentCheck{
   461  				Node:      "foobar",
   462  				CheckID:   "foobar",
   463  				Name:      "foobar",
   464  				Status:    consulapi.HealthPassing,
   465  				ServiceID: "foobar",
   466  			},
   467  		}
   468  		if _, err := catalog.Register(reg, nil); err != nil {
   469  			t.Fatalf("err: %v", err)
   470  		}
   471  	}()
   472  
   473  	wg.Add(1)
   474  	go func() {
   475  		defer wg.Done()
   476  		if err := plan.Run(a.HTTPAddr()); err != nil {
   477  			t.Fatalf("err: %v", err)
   478  		}
   479  	}()
   480  
   481  	if err := <-invoke; err != nil {
   482  		t.Fatalf("err: %v", err)
   483  	}
   484  
   485  	plan.Stop()
   486  	wg.Wait()
   487  }
   488  
   489  func TestEventWatch(t *testing.T) {
   490  	t.Parallel()
   491  	a := agent.NewTestAgent(t, t.Name(), ``)
   492  	defer a.Shutdown()
   493  	testrpc.WaitForTestAgent(t, a.RPC, "dc1")
   494  
   495  	invoke := makeInvokeCh()
   496  	plan := mustParse(t, `{"type":"event", "name": "foo"}`)
   497  	plan.Handler = func(idx uint64, raw interface{}) {
   498  		if raw == nil {
   499  			return
   500  		}
   501  		v, ok := raw.([]*consulapi.UserEvent)
   502  		if !ok || len(v) == 0 {
   503  			return // ignore
   504  		}
   505  		if string(v[len(v)-1].Name) != "foo" {
   506  			invoke <- errBadContent
   507  			return
   508  		}
   509  		invoke <- nil
   510  	}
   511  
   512  	var wg sync.WaitGroup
   513  	wg.Add(1)
   514  	go func() {
   515  		defer wg.Done()
   516  		event := a.Client().Event()
   517  
   518  		time.Sleep(20 * time.Millisecond)
   519  		params := &consulapi.UserEvent{Name: "foo"}
   520  		if _, _, err := event.Fire(params, nil); err != nil {
   521  			t.Fatalf("err: %v", err)
   522  		}
   523  	}()
   524  
   525  	wg.Add(1)
   526  	go func() {
   527  		defer wg.Done()
   528  		if err := plan.Run(a.HTTPAddr()); err != nil {
   529  			t.Fatalf("err: %v", err)
   530  		}
   531  	}()
   532  
   533  	if err := <-invoke; err != nil {
   534  		t.Fatalf("err: %v", err)
   535  	}
   536  
   537  	plan.Stop()
   538  	wg.Wait()
   539  }
   540  
   541  func TestConnectRootsWatch(t *testing.T) {
   542  	t.Parallel()
   543  	// NewTestAgent will bootstrap a new CA
   544  	a := agent.NewTestAgent(t, t.Name(), "")
   545  	defer a.Shutdown()
   546  	testrpc.WaitForTestAgent(t, a.RPC, "dc1")
   547  
   548  	var originalCAID string
   549  	invoke := makeInvokeCh()
   550  	plan := mustParse(t, `{"type":"connect_roots"}`)
   551  	plan.Handler = func(idx uint64, raw interface{}) {
   552  		if raw == nil {
   553  			return // ignore
   554  		}
   555  		v, ok := raw.(*consulapi.CARootList)
   556  		if !ok || v == nil {
   557  			return // ignore
   558  		}
   559  		// Only 1 CA is the bootstrapped state (i.e. first response). Ignore this
   560  		// state and wait for the new CA to show up too.
   561  		if len(v.Roots) == 1 {
   562  			originalCAID = v.ActiveRootID
   563  			return
   564  		}
   565  		assert.NotEmpty(t, originalCAID)
   566  		assert.NotEqual(t, originalCAID, v.ActiveRootID)
   567  		invoke <- nil
   568  	}
   569  
   570  	var wg sync.WaitGroup
   571  	wg.Add(1)
   572  	go func() {
   573  		defer wg.Done()
   574  		time.Sleep(20 * time.Millisecond)
   575  		// Set a new CA
   576  		connect.TestCAConfigSet(t, a, nil)
   577  	}()
   578  
   579  	wg.Add(1)
   580  	go func() {
   581  		defer wg.Done()
   582  		if err := plan.Run(a.HTTPAddr()); err != nil {
   583  			t.Fatalf("err: %v", err)
   584  		}
   585  	}()
   586  
   587  	if err := <-invoke; err != nil {
   588  		t.Fatalf("err: %v", err)
   589  	}
   590  
   591  	plan.Stop()
   592  	wg.Wait()
   593  }
   594  
   595  func TestConnectLeafWatch(t *testing.T) {
   596  	t.Parallel()
   597  	// NewTestAgent will bootstrap a new CA
   598  	a := agent.NewTestAgent(t, t.Name(), ``)
   599  	defer a.Shutdown()
   600  	testrpc.WaitForTestAgent(t, a.RPC, "dc1")
   601  
   602  	// Register a web service to get certs for
   603  	{
   604  		agent := a.Client().Agent()
   605  		reg := consulapi.AgentServiceRegistration{
   606  			ID:   "web",
   607  			Name: "web",
   608  			Port: 9090,
   609  		}
   610  		err := agent.ServiceRegister(&reg)
   611  		require.Nil(t, err)
   612  	}
   613  
   614  	var lastCert *consulapi.LeafCert
   615  
   616  	//invoke := makeInvokeCh()
   617  	invoke := make(chan error)
   618  	plan := mustParse(t, `{"type":"connect_leaf", "service":"web"}`)
   619  	plan.Handler = func(idx uint64, raw interface{}) {
   620  		if raw == nil {
   621  			return // ignore
   622  		}
   623  		v, ok := raw.(*consulapi.LeafCert)
   624  		if !ok || v == nil {
   625  			return // ignore
   626  		}
   627  		if lastCert == nil {
   628  			// Initial fetch, just store the cert and return
   629  			lastCert = v
   630  			return
   631  		}
   632  		// TODO(banks): right now the root rotation actually causes Serial numbers
   633  		// to reset so these end up all being the same. That needs fixing but it's
   634  		// a bigger task than I want to bite off for this PR.
   635  		//assert.NotEqual(t, lastCert.SerialNumber, v.SerialNumber)
   636  		assert.NotEqual(t, lastCert.CertPEM, v.CertPEM)
   637  		assert.NotEqual(t, lastCert.PrivateKeyPEM, v.PrivateKeyPEM)
   638  		assert.NotEqual(t, lastCert.ModifyIndex, v.ModifyIndex)
   639  		invoke <- nil
   640  	}
   641  
   642  	var wg sync.WaitGroup
   643  	wg.Add(1)
   644  	go func() {
   645  		defer wg.Done()
   646  		time.Sleep(20 * time.Millisecond)
   647  		// Change the CA to trigger a leaf change
   648  		connect.TestCAConfigSet(t, a, nil)
   649  	}()
   650  
   651  	wg.Add(1)
   652  	go func() {
   653  		defer wg.Done()
   654  		if err := plan.Run(a.HTTPAddr()); err != nil {
   655  			t.Fatalf("err: %v", err)
   656  		}
   657  	}()
   658  
   659  	if err := <-invoke; err != nil {
   660  		t.Fatalf("err: %v", err)
   661  	}
   662  
   663  	plan.Stop()
   664  	wg.Wait()
   665  }
   666  
   667  func TestConnectProxyConfigWatch(t *testing.T) {
   668  	t.Parallel()
   669  	a := agent.NewTestAgent(t, t.Name(), `
   670  	connect {
   671  		enabled = true
   672  		proxy {
   673  			allow_managed_api_registration = true
   674  		}
   675  	}
   676  	`)
   677  	defer a.Shutdown()
   678  	testrpc.WaitForTestAgent(t, a.RPC, "dc1")
   679  
   680  	// Register a local agent service with a managed proxy
   681  	reg := &consulapi.AgentServiceRegistration{
   682  		Name: "web",
   683  		Port: 8080,
   684  		Connect: &consulapi.AgentServiceConnect{
   685  			Proxy: &consulapi.AgentServiceConnectProxy{
   686  				Config: map[string]interface{}{
   687  					"foo": "bar",
   688  				},
   689  			},
   690  		},
   691  	}
   692  	client := a.Client()
   693  	agent := client.Agent()
   694  	err := agent.ServiceRegister(reg)
   695  	require.NoError(t, err)
   696  
   697  	invoke := makeInvokeCh()
   698  	plan := mustParse(t, `{"type":"connect_proxy_config", "proxy_service_id":"web-proxy"}`)
   699  	plan.HybridHandler = func(blockParamVal watch.BlockingParamVal, raw interface{}) {
   700  		if raw == nil {
   701  			return // ignore
   702  		}
   703  		v, ok := raw.(*consulapi.ConnectProxyConfig)
   704  		if !ok || v == nil {
   705  			return // ignore
   706  		}
   707  		invoke <- nil
   708  	}
   709  
   710  	var wg sync.WaitGroup
   711  	wg.Add(1)
   712  	go func() {
   713  		defer wg.Done()
   714  		time.Sleep(20 * time.Millisecond)
   715  
   716  		// Change the proxy's config
   717  		reg.Connect.Proxy.Config["foo"] = "buzz"
   718  		reg.Connect.Proxy.Config["baz"] = "qux"
   719  		err := agent.ServiceRegister(reg)
   720  		require.NoError(t, err)
   721  	}()
   722  
   723  	wg.Add(1)
   724  	go func() {
   725  		defer wg.Done()
   726  		if err := plan.Run(a.HTTPAddr()); err != nil {
   727  			t.Fatalf("err: %v", err)
   728  		}
   729  	}()
   730  
   731  	if err := <-invoke; err != nil {
   732  		t.Fatalf("err: %v", err)
   733  	}
   734  
   735  	plan.Stop()
   736  	wg.Wait()
   737  }
   738  
   739  func TestAgentServiceWatch(t *testing.T) {
   740  	t.Parallel()
   741  	a := agent.NewTestAgent(t, t.Name(), ``)
   742  	defer a.Shutdown()
   743  	testrpc.WaitForTestAgent(t, a.RPC, "dc1")
   744  
   745  	// Register a local agent service
   746  	reg := &consulapi.AgentServiceRegistration{
   747  		Name: "web",
   748  		Port: 8080,
   749  	}
   750  	client := a.Client()
   751  	agent := client.Agent()
   752  	err := agent.ServiceRegister(reg)
   753  	require.NoError(t, err)
   754  
   755  	invoke := makeInvokeCh()
   756  	plan := mustParse(t, `{"type":"agent_service", "service_id":"web"}`)
   757  	plan.HybridHandler = func(blockParamVal watch.BlockingParamVal, raw interface{}) {
   758  		if raw == nil {
   759  			return // ignore
   760  		}
   761  		v, ok := raw.(*consulapi.AgentService)
   762  		if !ok || v == nil {
   763  			return // ignore
   764  		}
   765  		invoke <- nil
   766  	}
   767  
   768  	var wg sync.WaitGroup
   769  	wg.Add(1)
   770  	go func() {
   771  		defer wg.Done()
   772  		time.Sleep(20 * time.Millisecond)
   773  
   774  		// Change the service definition
   775  		reg.Port = 9090
   776  		err := agent.ServiceRegister(reg)
   777  		require.NoError(t, err)
   778  	}()
   779  
   780  	wg.Add(1)
   781  	go func() {
   782  		defer wg.Done()
   783  		if err := plan.Run(a.HTTPAddr()); err != nil {
   784  			t.Fatalf("err: %v", err)
   785  		}
   786  	}()
   787  
   788  	if err := <-invoke; err != nil {
   789  		t.Fatalf("err: %v", err)
   790  	}
   791  
   792  	plan.Stop()
   793  	wg.Wait()
   794  }
   795  
   796  func mustParse(t *testing.T, q string) *watch.Plan {
   797  	t.Helper()
   798  	var params map[string]interface{}
   799  	if err := json.Unmarshal([]byte(q), &params); err != nil {
   800  		t.Fatal(err)
   801  	}
   802  	plan, err := watch.Parse(params)
   803  	if err != nil {
   804  		t.Fatalf("err: %v", err)
   805  	}
   806  	return plan
   807  }