github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/client/allocrunner/taskrunner/envoy_version_hook_test.go (about)

     1  package taskrunner
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/hashicorp/nomad/client/allocdir"
     8  	ifs "github.com/hashicorp/nomad/client/allocrunner/interfaces"
     9  	"github.com/hashicorp/nomad/client/taskenv"
    10  	"github.com/hashicorp/nomad/command/agent/consul"
    11  	"github.com/hashicorp/nomad/helper/envoy"
    12  	"github.com/hashicorp/nomad/helper/testlog"
    13  	"github.com/hashicorp/nomad/nomad/mock"
    14  	"github.com/hashicorp/nomad/nomad/structs"
    15  	"github.com/pkg/errors"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  var (
    20  	taskEnvDefault = taskenv.NewTaskEnv(nil, nil, nil, map[string]string{
    21  		"meta.connect.sidecar_image": envoy.ImageFormat,
    22  		"meta.connect.gateway_image": envoy.ImageFormat,
    23  	}, "", "")
    24  )
    25  
    26  func TestEnvoyVersionHook_semver(t *testing.T) {
    27  	t.Parallel()
    28  
    29  	t.Run("with v", func(t *testing.T) {
    30  		result, err := semver("v1.2.3")
    31  		require.NoError(t, err)
    32  		require.Equal(t, "1.2.3", result)
    33  	})
    34  
    35  	t.Run("without v", func(t *testing.T) {
    36  		result, err := semver("1.2.3")
    37  		require.NoError(t, err)
    38  		require.Equal(t, "1.2.3", result)
    39  	})
    40  
    41  	t.Run("unexpected", func(t *testing.T) {
    42  		_, err := semver("foo")
    43  		require.EqualError(t, err, "unexpected envoy version format: Malformed version: foo")
    44  	})
    45  }
    46  
    47  func TestEnvoyVersionHook_taskImage(t *testing.T) {
    48  	t.Parallel()
    49  
    50  	t.Run("absent", func(t *testing.T) {
    51  		result := (*envoyVersionHook)(nil).taskImage(map[string]interface{}{
    52  			// empty
    53  		})
    54  		require.Equal(t, envoy.ImageFormat, result)
    55  	})
    56  
    57  	t.Run("not a string", func(t *testing.T) {
    58  		result := (*envoyVersionHook)(nil).taskImage(map[string]interface{}{
    59  			"image": 7, // not a string
    60  		})
    61  		require.Equal(t, envoy.ImageFormat, result)
    62  	})
    63  
    64  	t.Run("normal", func(t *testing.T) {
    65  		result := (*envoyVersionHook)(nil).taskImage(map[string]interface{}{
    66  			"image": "custom/envoy:latest",
    67  		})
    68  		require.Equal(t, "custom/envoy:latest", result)
    69  	})
    70  }
    71  
    72  func TestEnvoyVersionHook_tweakImage(t *testing.T) {
    73  	t.Parallel()
    74  
    75  	image := envoy.ImageFormat
    76  
    77  	t.Run("legacy", func(t *testing.T) {
    78  		result, err := (*envoyVersionHook)(nil).tweakImage(image, nil)
    79  		require.NoError(t, err)
    80  		require.Equal(t, envoy.FallbackImage, result)
    81  	})
    82  
    83  	t.Run("unexpected", func(t *testing.T) {
    84  		_, err := (*envoyVersionHook)(nil).tweakImage(image, map[string][]string{
    85  			"envoy": {"foo", "bar", "baz"},
    86  		})
    87  		require.EqualError(t, err, "unexpected envoy version format: Malformed version: foo")
    88  	})
    89  
    90  	t.Run("standard envoy", func(t *testing.T) {
    91  		result, err := (*envoyVersionHook)(nil).tweakImage(image, map[string][]string{
    92  			"envoy": {"1.15.0", "1.14.4", "1.13.4", "1.12.6"},
    93  		})
    94  		require.NoError(t, err)
    95  		require.Equal(t, "envoyproxy/envoy:v1.15.0", result)
    96  	})
    97  
    98  	t.Run("custom image", func(t *testing.T) {
    99  		custom := "custom-${NOMAD_envoy_version}/envoy:${NOMAD_envoy_version}"
   100  		result, err := (*envoyVersionHook)(nil).tweakImage(custom, map[string][]string{
   101  			"envoy": {"1.15.0", "1.14.4", "1.13.4", "1.12.6"},
   102  		})
   103  		require.NoError(t, err)
   104  		require.Equal(t, "custom-1.15.0/envoy:1.15.0", result)
   105  	})
   106  }
   107  
   108  func TestEnvoyVersionHook_interpolateImage(t *testing.T) {
   109  	t.Parallel()
   110  
   111  	hook := (*envoyVersionHook)(nil)
   112  
   113  	t.Run("default sidecar", func(t *testing.T) {
   114  		task := &structs.Task{
   115  			Config: map[string]interface{}{"image": envoy.SidecarConfigVar},
   116  		}
   117  		hook.interpolateImage(task, taskEnvDefault)
   118  		require.Equal(t, envoy.ImageFormat, task.Config["image"])
   119  	})
   120  
   121  	t.Run("default gateway", func(t *testing.T) {
   122  		task := &structs.Task{
   123  			Config: map[string]interface{}{"image": envoy.GatewayConfigVar},
   124  		}
   125  		hook.interpolateImage(task, taskEnvDefault)
   126  		require.Equal(t, envoy.ImageFormat, task.Config["image"])
   127  	})
   128  
   129  	t.Run("custom static", func(t *testing.T) {
   130  		task := &structs.Task{
   131  			Config: map[string]interface{}{"image": "custom/envoy"},
   132  		}
   133  		hook.interpolateImage(task, taskEnvDefault)
   134  		require.Equal(t, "custom/envoy", task.Config["image"])
   135  	})
   136  
   137  	t.Run("custom interpolated", func(t *testing.T) {
   138  		task := &structs.Task{
   139  			Config: map[string]interface{}{"image": "${MY_ENVOY}"},
   140  		}
   141  		hook.interpolateImage(task, taskenv.NewTaskEnv(map[string]string{
   142  			"MY_ENVOY": "my/envoy",
   143  		}, map[string]string{
   144  			"MY_ENVOY": "my/envoy",
   145  		}, nil, nil, "", ""))
   146  		require.Equal(t, "my/envoy", task.Config["image"])
   147  	})
   148  
   149  	t.Run("no image", func(t *testing.T) {
   150  		task := &structs.Task{
   151  			Config: map[string]interface{}{},
   152  		}
   153  		hook.interpolateImage(task, taskEnvDefault)
   154  		require.Empty(t, task.Config)
   155  	})
   156  }
   157  
   158  func TestEnvoyVersionHook_skip(t *testing.T) {
   159  	t.Parallel()
   160  
   161  	h := new(envoyVersionHook)
   162  
   163  	t.Run("not docker", func(t *testing.T) {
   164  		skip := h.skip(&ifs.TaskPrestartRequest{
   165  			Task: &structs.Task{
   166  				Driver: "exec",
   167  				Config: nil,
   168  			},
   169  		})
   170  		require.True(t, skip)
   171  	})
   172  
   173  	t.Run("not connect", func(t *testing.T) {
   174  		skip := h.skip(&ifs.TaskPrestartRequest{
   175  			Task: &structs.Task{
   176  				Driver: "docker",
   177  				Kind:   "",
   178  			},
   179  		})
   180  		require.True(t, skip)
   181  	})
   182  
   183  	t.Run("version not needed", func(t *testing.T) {
   184  		skip := h.skip(&ifs.TaskPrestartRequest{
   185  			Task: &structs.Task{
   186  				Driver: "docker",
   187  				Kind:   structs.NewTaskKind(structs.ConnectProxyPrefix, "task"),
   188  				Config: map[string]interface{}{
   189  					"image": "custom/envoy:latest",
   190  				},
   191  			},
   192  		})
   193  		require.True(t, skip)
   194  	})
   195  
   196  	t.Run("version needed custom", func(t *testing.T) {
   197  		skip := h.skip(&ifs.TaskPrestartRequest{
   198  			Task: &structs.Task{
   199  				Driver: "docker",
   200  				Kind:   structs.NewTaskKind(structs.ConnectProxyPrefix, "task"),
   201  				Config: map[string]interface{}{
   202  					"image": "custom/envoy:v${NOMAD_envoy_version}",
   203  				},
   204  			},
   205  		})
   206  		require.False(t, skip)
   207  	})
   208  
   209  	t.Run("version needed standard", func(t *testing.T) {
   210  		skip := h.skip(&ifs.TaskPrestartRequest{
   211  			Task: &structs.Task{
   212  				Driver: "docker",
   213  				Kind:   structs.NewTaskKind(structs.ConnectProxyPrefix, "task"),
   214  				Config: map[string]interface{}{
   215  					"image": envoy.ImageFormat,
   216  				},
   217  			},
   218  		})
   219  		require.False(t, skip)
   220  	})
   221  }
   222  
   223  func TestTaskRunner_EnvoyVersionHook_Prestart_standard(t *testing.T) {
   224  	t.Parallel()
   225  
   226  	logger := testlog.HCLogger(t)
   227  
   228  	// Setup an Allocation
   229  	alloc := mock.ConnectAlloc()
   230  	alloc.Job.TaskGroups[0].Tasks[0] = mock.ConnectSidecarTask()
   231  	allocDir, cleanupDir := allocdir.TestAllocDir(t, logger, "EnvoyVersionHook")
   232  	defer cleanupDir()
   233  
   234  	// Setup a mock for Consul API
   235  	spAPI := consul.MockSupportedProxiesAPI{
   236  		Value: map[string][]string{
   237  			"envoy": {"1.15.0", "1.14.4"},
   238  		},
   239  		Error: nil,
   240  	}
   241  
   242  	// Run envoy_version hook
   243  	h := newEnvoyVersionHook(newEnvoyVersionHookConfig(alloc, spAPI, logger))
   244  
   245  	// Create a prestart request
   246  	request := &ifs.TaskPrestartRequest{
   247  		Task:    alloc.Job.TaskGroups[0].Tasks[0],
   248  		TaskDir: allocDir.NewTaskDir(alloc.Job.TaskGroups[0].Tasks[0].Name),
   249  		TaskEnv: taskEnvDefault,
   250  	}
   251  	require.NoError(t, request.TaskDir.Build(false, nil))
   252  
   253  	// Prepare a response
   254  	var response ifs.TaskPrestartResponse
   255  
   256  	// Run the hook
   257  	require.NoError(t, h.Prestart(context.Background(), request, &response))
   258  
   259  	// Assert the hook is Done
   260  	require.True(t, response.Done)
   261  
   262  	// Assert the Task.Config[image] is concrete
   263  	require.Equal(t, "envoyproxy/envoy:v1.15.0", request.Task.Config["image"])
   264  }
   265  
   266  func TestTaskRunner_EnvoyVersionHook_Prestart_custom(t *testing.T) {
   267  	t.Parallel()
   268  
   269  	logger := testlog.HCLogger(t)
   270  
   271  	// Setup an Allocation
   272  	alloc := mock.ConnectAlloc()
   273  	alloc.Job.TaskGroups[0].Tasks[0] = mock.ConnectSidecarTask()
   274  	alloc.Job.TaskGroups[0].Tasks[0].Config["image"] = "custom-${NOMAD_envoy_version}:latest"
   275  	allocDir, cleanupDir := allocdir.TestAllocDir(t, logger, "EnvoyVersionHook")
   276  	defer cleanupDir()
   277  
   278  	// Setup a mock for Consul API
   279  	spAPI := consul.MockSupportedProxiesAPI{
   280  		Value: map[string][]string{
   281  			"envoy": {"1.14.1", "1.13.3"},
   282  		},
   283  		Error: nil,
   284  	}
   285  
   286  	// Run envoy_version hook
   287  	h := newEnvoyVersionHook(newEnvoyVersionHookConfig(alloc, spAPI, logger))
   288  
   289  	// Create a prestart request
   290  	request := &ifs.TaskPrestartRequest{
   291  		Task:    alloc.Job.TaskGroups[0].Tasks[0],
   292  		TaskDir: allocDir.NewTaskDir(alloc.Job.TaskGroups[0].Tasks[0].Name),
   293  		TaskEnv: taskEnvDefault,
   294  	}
   295  	require.NoError(t, request.TaskDir.Build(false, nil))
   296  
   297  	// Prepare a response
   298  	var response ifs.TaskPrestartResponse
   299  
   300  	// Run the hook
   301  	require.NoError(t, h.Prestart(context.Background(), request, &response))
   302  
   303  	// Assert the hook is Done
   304  	require.True(t, response.Done)
   305  
   306  	// Assert the Task.Config[image] is concrete
   307  	require.Equal(t, "custom-1.14.1:latest", request.Task.Config["image"])
   308  }
   309  
   310  func TestTaskRunner_EnvoyVersionHook_Prestart_skip(t *testing.T) {
   311  	t.Parallel()
   312  
   313  	logger := testlog.HCLogger(t)
   314  
   315  	// Setup an Allocation
   316  	alloc := mock.ConnectAlloc()
   317  	alloc.Job.TaskGroups[0].Tasks[0] = mock.ConnectSidecarTask()
   318  	alloc.Job.TaskGroups[0].Tasks[0].Driver = "exec"
   319  	alloc.Job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
   320  		"command": "/sidecar",
   321  	}
   322  	allocDir, cleanupDir := allocdir.TestAllocDir(t, logger, "EnvoyVersionHook")
   323  	defer cleanupDir()
   324  
   325  	// Setup a mock for Consul API
   326  	spAPI := consul.MockSupportedProxiesAPI{
   327  		Value: map[string][]string{
   328  			"envoy": {"1.14.1", "1.13.3"},
   329  		},
   330  		Error: nil,
   331  	}
   332  
   333  	// Run envoy_version hook
   334  	h := newEnvoyVersionHook(newEnvoyVersionHookConfig(alloc, spAPI, logger))
   335  
   336  	// Create a prestart request
   337  	request := &ifs.TaskPrestartRequest{
   338  		Task:    alloc.Job.TaskGroups[0].Tasks[0],
   339  		TaskDir: allocDir.NewTaskDir(alloc.Job.TaskGroups[0].Tasks[0].Name),
   340  		TaskEnv: taskEnvDefault,
   341  	}
   342  	require.NoError(t, request.TaskDir.Build(false, nil))
   343  
   344  	// Prepare a response
   345  	var response ifs.TaskPrestartResponse
   346  
   347  	// Run the hook
   348  	require.NoError(t, h.Prestart(context.Background(), request, &response))
   349  
   350  	// Assert the hook is Done
   351  	require.True(t, response.Done)
   352  
   353  	// Assert the Task.Config[image] does not get set
   354  	require.Empty(t, request.Task.Config["image"])
   355  }
   356  
   357  func TestTaskRunner_EnvoyVersionHook_Prestart_fallback(t *testing.T) {
   358  	t.Parallel()
   359  
   360  	logger := testlog.HCLogger(t)
   361  
   362  	// Setup an Allocation
   363  	alloc := mock.ConnectAlloc()
   364  	alloc.Job.TaskGroups[0].Tasks[0] = mock.ConnectSidecarTask()
   365  	allocDir, cleanupDir := allocdir.TestAllocDir(t, logger, "EnvoyVersionHook")
   366  	defer cleanupDir()
   367  
   368  	// Setup a mock for Consul API
   369  	spAPI := consul.MockSupportedProxiesAPI{
   370  		Value: nil, // old consul, no .xDS.SupportedProxies
   371  		Error: nil,
   372  	}
   373  
   374  	// Run envoy_version hook
   375  	h := newEnvoyVersionHook(newEnvoyVersionHookConfig(alloc, spAPI, logger))
   376  
   377  	// Create a prestart request
   378  	request := &ifs.TaskPrestartRequest{
   379  		Task:    alloc.Job.TaskGroups[0].Tasks[0],
   380  		TaskDir: allocDir.NewTaskDir(alloc.Job.TaskGroups[0].Tasks[0].Name),
   381  		TaskEnv: taskEnvDefault,
   382  	}
   383  	require.NoError(t, request.TaskDir.Build(false, nil))
   384  
   385  	// Prepare a response
   386  	var response ifs.TaskPrestartResponse
   387  
   388  	// Run the hook
   389  	require.NoError(t, h.Prestart(context.Background(), request, &response))
   390  
   391  	// Assert the hook is Done
   392  	require.True(t, response.Done)
   393  
   394  	// Assert the Task.Config[image] is the fallback image
   395  	require.Equal(t, "envoyproxy/envoy:v1.11.2@sha256:a7769160c9c1a55bb8d07a3b71ce5d64f72b1f665f10d81aa1581bc3cf850d09", request.Task.Config["image"])
   396  }
   397  
   398  func TestTaskRunner_EnvoyVersionHook_Prestart_error(t *testing.T) {
   399  	t.Parallel()
   400  
   401  	logger := testlog.HCLogger(t)
   402  
   403  	// Setup an Allocation
   404  	alloc := mock.ConnectAlloc()
   405  	alloc.Job.TaskGroups[0].Tasks[0] = mock.ConnectSidecarTask()
   406  	allocDir, cleanupDir := allocdir.TestAllocDir(t, logger, "EnvoyVersionHook")
   407  	defer cleanupDir()
   408  
   409  	// Setup a mock for Consul API
   410  	spAPI := consul.MockSupportedProxiesAPI{
   411  		Value: nil,
   412  		Error: errors.New("some consul error"),
   413  	}
   414  
   415  	// Run envoy_version hook
   416  	h := newEnvoyVersionHook(newEnvoyVersionHookConfig(alloc, spAPI, logger))
   417  
   418  	// Create a prestart request
   419  	request := &ifs.TaskPrestartRequest{
   420  		Task:    alloc.Job.TaskGroups[0].Tasks[0],
   421  		TaskDir: allocDir.NewTaskDir(alloc.Job.TaskGroups[0].Tasks[0].Name),
   422  		TaskEnv: taskEnvDefault,
   423  	}
   424  	require.NoError(t, request.TaskDir.Build(false, nil))
   425  
   426  	// Prepare a response
   427  	var response ifs.TaskPrestartResponse
   428  
   429  	// Run the hook, error should be recoverable
   430  	err := h.Prestart(context.Background(), request, &response)
   431  	require.EqualError(t, err, "error retrieving supported Envoy versions from Consul: some consul error")
   432  
   433  	// Assert the hook is not Done
   434  	require.False(t, response.Done)
   435  }