github.com/manicqin/nomad@v0.9.5/client/allocrunner/taskrunner/envoybootstrap_hook_test.go (about)

     1  package taskrunner
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"testing"
    10  
    11  	consulapi "github.com/hashicorp/consul/api"
    12  	consultest "github.com/hashicorp/consul/testutil"
    13  	"github.com/hashicorp/nomad/client/allocdir"
    14  	"github.com/hashicorp/nomad/client/allocrunner/interfaces"
    15  	"github.com/hashicorp/nomad/client/taskenv"
    16  	"github.com/hashicorp/nomad/client/testutil"
    17  	agentconsul "github.com/hashicorp/nomad/command/agent/consul"
    18  	"github.com/hashicorp/nomad/helper/args"
    19  	"github.com/hashicorp/nomad/helper/testlog"
    20  	"github.com/hashicorp/nomad/nomad/mock"
    21  	"github.com/hashicorp/nomad/nomad/structs"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  var _ interfaces.TaskPrestartHook = (*envoyBootstrapHook)(nil)
    26  
    27  // TestTaskRunner_EnvoyBootstrapHook_Prestart asserts the EnvoyBootstrapHook
    28  // creates Envoy's bootstrap.json configuration based on Connect proxy sidecars
    29  // registered for the task.
    30  func TestTaskRunner_EnvoyBootstrapHook_Ok(t *testing.T) {
    31  	t.Parallel()
    32  	testutil.RequireConsul(t)
    33  
    34  	testconsul, err := consultest.NewTestServerConfig(func(c *consultest.TestServerConfig) {
    35  		// If -v wasn't specified squelch consul logging
    36  		if !testing.Verbose() {
    37  			c.Stdout = ioutil.Discard
    38  			c.Stderr = ioutil.Discard
    39  		}
    40  	})
    41  	if err != nil {
    42  		t.Fatalf("error starting test consul server: %v", err)
    43  	}
    44  	defer testconsul.Stop()
    45  
    46  	alloc := mock.Alloc()
    47  	alloc.AllocatedResources.Shared.Networks = []*structs.NetworkResource{
    48  		{
    49  			Mode: "bridge",
    50  			IP:   "10.0.0.1",
    51  			DynamicPorts: []structs.Port{
    52  				{
    53  					Label: "connect-proxy-foo",
    54  					Value: 9999,
    55  					To:    9999,
    56  				},
    57  			},
    58  		},
    59  	}
    60  	tg := alloc.Job.TaskGroups[0]
    61  	tg.Services = []*structs.Service{
    62  		{
    63  			Name:      "foo",
    64  			PortLabel: "9999", // Just need a valid port, nothing will bind to it
    65  			Connect: &structs.ConsulConnect{
    66  				SidecarService: &structs.ConsulSidecarService{},
    67  			},
    68  		},
    69  	}
    70  	sidecarTask := &structs.Task{
    71  		Name: "sidecar",
    72  		Kind: "connect-proxy:foo",
    73  	}
    74  	tg.Tasks = append(tg.Tasks, sidecarTask)
    75  
    76  	logger := testlog.HCLogger(t)
    77  
    78  	allocDir, cleanup := allocdir.TestAllocDir(t, logger, "EnvoyBootstrap")
    79  	defer cleanup()
    80  
    81  	// Register Group Services
    82  	consulConfig := consulapi.DefaultConfig()
    83  	consulConfig.Address = testconsul.HTTPAddr
    84  	consulAPIClient, err := consulapi.NewClient(consulConfig)
    85  	require.NoError(t, err)
    86  	consulClient := agentconsul.NewServiceClient(consulAPIClient.Agent(), logger, true)
    87  	go consulClient.Run()
    88  	defer consulClient.Shutdown()
    89  	require.NoError(t, consulClient.RegisterWorkload(agentconsul.BuildAllocServices(mock.Node(), alloc, agentconsul.NoopRestarter())))
    90  
    91  	// Run Connect bootstrap Hook
    92  	h := newEnvoyBootstrapHook(alloc, testconsul.HTTPAddr, logger)
    93  	req := &interfaces.TaskPrestartRequest{
    94  		Task:    sidecarTask,
    95  		TaskDir: allocDir.NewTaskDir(sidecarTask.Name),
    96  	}
    97  	require.NoError(t, req.TaskDir.Build(false, nil))
    98  
    99  	resp := &interfaces.TaskPrestartResponse{}
   100  
   101  	// Run the hook
   102  	require.NoError(t, h.Prestart(context.Background(), req, resp))
   103  
   104  	// Assert it is Done
   105  	require.True(t, resp.Done)
   106  
   107  	require.NotNil(t, resp.Env)
   108  	require.Equal(t, "localhost:19001", resp.Env[envoyAdminBindEnvPrefix+"foo"])
   109  
   110  	// Ensure the default path matches
   111  	env := map[string]string{
   112  		taskenv.SecretsDir: req.TaskDir.SecretsDir,
   113  	}
   114  	f, err := os.Open(args.ReplaceEnv(structs.EnvoyBootstrapPath, env))
   115  	require.NoError(t, err)
   116  	defer f.Close()
   117  
   118  	// Assert bootstrap configuration is valid json
   119  	var out map[string]interface{}
   120  	require.NoError(t, json.NewDecoder(f).Decode(&out))
   121  }
   122  
   123  // TestTaskRunner_EnvoyBootstrapHook_Noop asserts that the Envoy bootstrap hook
   124  // is a noop for non-Connect proxy sidecar tasks.
   125  func TestTaskRunner_EnvoyBootstrapHook_Noop(t *testing.T) {
   126  	t.Parallel()
   127  	logger := testlog.HCLogger(t)
   128  
   129  	allocDir, cleanup := allocdir.TestAllocDir(t, logger, "EnvoyBootstrap")
   130  	defer cleanup()
   131  
   132  	alloc := mock.Alloc()
   133  	task := alloc.Job.LookupTaskGroup(alloc.TaskGroup).Tasks[0]
   134  
   135  	// Run Envoy bootstrap Hook. Use invalid Consul address as it should
   136  	// not get hit.
   137  	h := newEnvoyBootstrapHook(alloc, "http://127.0.0.2:1", logger)
   138  	req := &interfaces.TaskPrestartRequest{
   139  		Task:    task,
   140  		TaskDir: allocDir.NewTaskDir(task.Name),
   141  	}
   142  	require.NoError(t, req.TaskDir.Build(false, nil))
   143  
   144  	resp := &interfaces.TaskPrestartResponse{}
   145  
   146  	// Run the hook
   147  	require.NoError(t, h.Prestart(context.Background(), req, resp))
   148  
   149  	// Assert it is Done
   150  	require.True(t, resp.Done)
   151  
   152  	// Assert no file was written
   153  	_, err := os.Open(filepath.Join(req.TaskDir.SecretsDir, "envoy_bootstrap.json"))
   154  	require.Error(t, err)
   155  	require.True(t, os.IsNotExist(err))
   156  }
   157  
   158  // TestTaskRunner_EnvoyBootstrapHook_RecoverableError asserts the Envoy
   159  // bootstrap hook returns a Recoverable error if the bootstrap command runs but
   160  // fails.
   161  func TestTaskRunner_EnvoyBootstrapHook_RecoverableError(t *testing.T) {
   162  	t.Parallel()
   163  	testutil.RequireConsul(t)
   164  
   165  	testconsul, err := consultest.NewTestServerConfig(func(c *consultest.TestServerConfig) {
   166  		// If -v wasn't specified squelch consul logging
   167  		if !testing.Verbose() {
   168  			c.Stdout = ioutil.Discard
   169  			c.Stderr = ioutil.Discard
   170  		}
   171  	})
   172  	if err != nil {
   173  		t.Fatalf("error starting test consul server: %v", err)
   174  	}
   175  	defer testconsul.Stop()
   176  
   177  	alloc := mock.Alloc()
   178  	alloc.AllocatedResources.Shared.Networks = []*structs.NetworkResource{
   179  		{
   180  			Mode: "bridge",
   181  			IP:   "10.0.0.1",
   182  			DynamicPorts: []structs.Port{
   183  				{
   184  					Label: "connect-proxy-foo",
   185  					Value: 9999,
   186  					To:    9999,
   187  				},
   188  			},
   189  		},
   190  	}
   191  	tg := alloc.Job.TaskGroups[0]
   192  	tg.Services = []*structs.Service{
   193  		{
   194  			Name:      "foo",
   195  			PortLabel: "9999", // Just need a valid port, nothing will bind to it
   196  			Connect: &structs.ConsulConnect{
   197  				SidecarService: &structs.ConsulSidecarService{},
   198  			},
   199  		},
   200  	}
   201  	sidecarTask := &structs.Task{
   202  		Name: "sidecar",
   203  		Kind: "connect-proxy:foo",
   204  	}
   205  	tg.Tasks = append(tg.Tasks, sidecarTask)
   206  
   207  	logger := testlog.HCLogger(t)
   208  
   209  	allocDir, cleanup := allocdir.TestAllocDir(t, logger, "EnvoyBootstrap")
   210  	defer cleanup()
   211  
   212  	// Unlike the successful test above, do NOT register the group services
   213  	// yet. This should cause a recoverable error similar to if Consul was
   214  	// not running.
   215  
   216  	// Run Connect bootstrap Hook
   217  	h := newEnvoyBootstrapHook(alloc, testconsul.HTTPAddr, logger)
   218  	req := &interfaces.TaskPrestartRequest{
   219  		Task:    sidecarTask,
   220  		TaskDir: allocDir.NewTaskDir(sidecarTask.Name),
   221  	}
   222  	require.NoError(t, req.TaskDir.Build(false, nil))
   223  
   224  	resp := &interfaces.TaskPrestartResponse{}
   225  
   226  	// Run the hook
   227  	err = h.Prestart(context.Background(), req, resp)
   228  	require.Error(t, err)
   229  	require.True(t, structs.IsRecoverable(err))
   230  
   231  	// Assert it is not Done
   232  	require.False(t, resp.Done)
   233  
   234  	// Assert no file was written
   235  	_, err = os.Open(filepath.Join(req.TaskDir.SecretsDir, "envoy_bootstrap.json"))
   236  	require.Error(t, err)
   237  	require.True(t, os.IsNotExist(err))
   238  }