github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/allocrunner/taskrunner/service_hook_test.go (about)

     1  package taskrunner
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/hashicorp/nomad/ci"
     8  	"github.com/hashicorp/nomad/client/allocrunner/interfaces"
     9  	regMock "github.com/hashicorp/nomad/client/serviceregistration/mock"
    10  	"github.com/hashicorp/nomad/client/serviceregistration/wrapper"
    11  	"github.com/hashicorp/nomad/client/taskenv"
    12  	agentconsul "github.com/hashicorp/nomad/command/agent/consul"
    13  	"github.com/hashicorp/nomad/helper/testlog"
    14  	"github.com/hashicorp/nomad/nomad/mock"
    15  	"github.com/hashicorp/nomad/nomad/structs"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  // Statically assert the stats hook implements the expected interfaces
    20  var _ interfaces.TaskPoststartHook = (*serviceHook)(nil)
    21  var _ interfaces.TaskExitedHook = (*serviceHook)(nil)
    22  var _ interfaces.TaskPreKillHook = (*serviceHook)(nil)
    23  var _ interfaces.TaskUpdateHook = (*serviceHook)(nil)
    24  
    25  func TestUpdate_beforePoststart(t *testing.T) {
    26  	alloc := mock.Alloc()
    27  	alloc.Job.Canonicalize()
    28  	logger := testlog.HCLogger(t)
    29  
    30  	c := regMock.NewServiceRegistrationHandler(logger)
    31  	regWrap := wrapper.NewHandlerWrapper(logger, c, nil)
    32  
    33  	// Interpolating workload services performs a check on the task env, if it
    34  	// is nil, nil is returned meaning no services. This does not work with the
    35  	// wrapper len protections, so we need a dummy taskenv.
    36  	spoofTaskEnv := taskenv.TaskEnv{NodeAttrs: map[string]string{}}
    37  
    38  	hook := newServiceHook(serviceHookConfig{
    39  		alloc:             alloc,
    40  		task:              alloc.LookupTask("web"),
    41  		serviceRegWrapper: regWrap,
    42  		logger:            logger,
    43  	})
    44  	require.NoError(t, hook.Update(context.Background(), &interfaces.TaskUpdateRequest{
    45  		Alloc:   alloc,
    46  		TaskEnv: &spoofTaskEnv,
    47  	}, &interfaces.TaskUpdateResponse{}))
    48  	require.Len(t, c.GetOps(), 0)
    49  
    50  	require.NoError(t, hook.Poststart(context.Background(), &interfaces.TaskPoststartRequest{
    51  		TaskEnv: &spoofTaskEnv,
    52  	}, &interfaces.TaskPoststartResponse{}))
    53  	require.Len(t, c.GetOps(), 1)
    54  
    55  	require.NoError(t, hook.Update(context.Background(), &interfaces.TaskUpdateRequest{
    56  		Alloc:   alloc,
    57  		TaskEnv: &spoofTaskEnv,
    58  	}, &interfaces.TaskUpdateResponse{}))
    59  	require.Len(t, c.GetOps(), 2)
    60  
    61  	// When a task exits it could be restarted with new driver info
    62  	// so Update should again wait on Poststart.
    63  
    64  	require.NoError(t, hook.Exited(context.Background(), &interfaces.TaskExitedRequest{}, &interfaces.TaskExitedResponse{}))
    65  	require.Len(t, c.GetOps(), 3)
    66  
    67  	require.NoError(t, hook.Update(context.Background(), &interfaces.TaskUpdateRequest{
    68  		Alloc:   alloc,
    69  		TaskEnv: &spoofTaskEnv,
    70  	}, &interfaces.TaskUpdateResponse{}))
    71  	require.Len(t, c.GetOps(), 3)
    72  
    73  	require.NoError(t, hook.Poststart(context.Background(), &interfaces.TaskPoststartRequest{
    74  		TaskEnv: &spoofTaskEnv,
    75  	}, &interfaces.TaskPoststartResponse{}))
    76  	require.Len(t, c.GetOps(), 4)
    77  
    78  	require.NoError(t, hook.Update(context.Background(), &interfaces.TaskUpdateRequest{
    79  		Alloc:   alloc,
    80  		TaskEnv: &spoofTaskEnv,
    81  	}, &interfaces.TaskUpdateResponse{}))
    82  	require.Len(t, c.GetOps(), 5)
    83  
    84  	require.NoError(t, hook.PreKilling(context.Background(), &interfaces.TaskPreKillRequest{}, &interfaces.TaskPreKillResponse{}))
    85  	require.Len(t, c.GetOps(), 6)
    86  
    87  	require.NoError(t, hook.Update(context.Background(), &interfaces.TaskUpdateRequest{
    88  		Alloc:   alloc,
    89  		TaskEnv: &spoofTaskEnv,
    90  	}, &interfaces.TaskUpdateResponse{}))
    91  	require.Len(t, c.GetOps(), 6)
    92  }
    93  
    94  func Test_serviceHook_multipleDeRegisterCall(t *testing.T) {
    95  	ci.Parallel(t)
    96  
    97  	alloc := mock.Alloc()
    98  	logger := testlog.HCLogger(t)
    99  
   100  	c := regMock.NewServiceRegistrationHandler(logger)
   101  	regWrap := wrapper.NewHandlerWrapper(logger, c, nil)
   102  
   103  	hook := newServiceHook(serviceHookConfig{
   104  		alloc:             alloc,
   105  		task:              alloc.LookupTask("web"),
   106  		serviceRegWrapper: regWrap,
   107  		logger:            logger,
   108  	})
   109  
   110  	// Interpolating workload services performs a check on the task env, if it
   111  	// is nil, nil is returned meaning no services. This does not work with the
   112  	// wrapper len protections, so we need a dummy taskenv.
   113  	spoofTaskEnv := taskenv.TaskEnv{NodeAttrs: map[string]string{}}
   114  
   115  	// Add a registration, as we would in normal operation.
   116  	require.NoError(t, hook.Poststart(context.Background(), &interfaces.TaskPoststartRequest{
   117  		TaskEnv: &spoofTaskEnv,
   118  	}, &interfaces.TaskPoststartResponse{}))
   119  	require.Len(t, c.GetOps(), 1)
   120  
   121  	// Call all three deregister backed functions in a row. Ensure the number
   122  	// of operations does not increase and that the second is always a remove.
   123  	require.NoError(t, hook.Exited(context.Background(), &interfaces.TaskExitedRequest{}, &interfaces.TaskExitedResponse{}))
   124  	require.Len(t, c.GetOps(), 2)
   125  	require.Equal(t, c.GetOps()[1].Op, "remove")
   126  
   127  	require.NoError(t, hook.PreKilling(context.Background(), &interfaces.TaskPreKillRequest{}, &interfaces.TaskPreKillResponse{}))
   128  	require.Len(t, c.GetOps(), 2)
   129  	require.Equal(t, c.GetOps()[1].Op, "remove")
   130  
   131  	require.NoError(t, hook.Stop(context.Background(), &interfaces.TaskStopRequest{}, &interfaces.TaskStopResponse{}))
   132  	require.Len(t, c.GetOps(), 2)
   133  	require.Equal(t, c.GetOps()[1].Op, "remove")
   134  
   135  	// Now we act like a restart.
   136  	require.NoError(t, hook.Poststart(context.Background(), &interfaces.TaskPoststartRequest{
   137  		TaskEnv: &spoofTaskEnv,
   138  	}, &interfaces.TaskPoststartResponse{}))
   139  	require.Len(t, c.GetOps(), 3)
   140  	require.Equal(t, c.GetOps()[2].Op, "add")
   141  
   142  	// Go again through the process or shutting down.
   143  	require.NoError(t, hook.Exited(context.Background(), &interfaces.TaskExitedRequest{}, &interfaces.TaskExitedResponse{}))
   144  	require.Len(t, c.GetOps(), 4)
   145  	require.Equal(t, c.GetOps()[3].Op, "remove")
   146  
   147  	require.NoError(t, hook.PreKilling(context.Background(), &interfaces.TaskPreKillRequest{}, &interfaces.TaskPreKillResponse{}))
   148  	require.Len(t, c.GetOps(), 4)
   149  	require.Equal(t, c.GetOps()[3].Op, "remove")
   150  
   151  	require.NoError(t, hook.Stop(context.Background(), &interfaces.TaskStopRequest{}, &interfaces.TaskStopResponse{}))
   152  	require.Len(t, c.GetOps(), 4)
   153  	require.Equal(t, c.GetOps()[3].Op, "remove")
   154  }
   155  
   156  // Test_serviceHook_Nomad performs a normal operation test of the serviceHook
   157  // when using task services which utilise the Nomad provider.
   158  func Test_serviceHook_Nomad(t *testing.T) {
   159  	ci.Parallel(t)
   160  
   161  	// Create a mock alloc, and add a task service using provider Nomad.
   162  	alloc := mock.Alloc()
   163  	alloc.Job.TaskGroups[0].Tasks[0].Services = []*structs.Service{
   164  		{
   165  			Name:     "nomad-provider-service",
   166  			Provider: structs.ServiceProviderNomad,
   167  		},
   168  	}
   169  
   170  	// Create our base objects and our subsequent wrapper.
   171  	logger := testlog.HCLogger(t)
   172  	consulMockClient := regMock.NewServiceRegistrationHandler(logger)
   173  	nomadMockClient := regMock.NewServiceRegistrationHandler(logger)
   174  
   175  	regWrapper := wrapper.NewHandlerWrapper(logger, consulMockClient, nomadMockClient)
   176  
   177  	h := newServiceHook(serviceHookConfig{
   178  		alloc:             alloc,
   179  		task:              alloc.LookupTask("web"),
   180  		providerNamespace: "default",
   181  		serviceRegWrapper: regWrapper,
   182  		restarter:         agentconsul.NoopRestarter(),
   183  		logger:            logger,
   184  	})
   185  
   186  	// Create a taskEnv builder to use in requests, otherwise interpolation of
   187  	// services will always return nil.
   188  	taskEnvBuilder := taskenv.NewBuilder(mock.Node(), alloc, nil, alloc.Job.Region)
   189  
   190  	// Trigger our initial hook function.
   191  	require.NoError(t, h.Poststart(context.Background(), &interfaces.TaskPoststartRequest{
   192  		TaskEnv: taskEnvBuilder.Build()}, nil))
   193  
   194  	// Trigger all the possible stop functions to ensure we only deregister
   195  	// once.
   196  	require.NoError(t, h.PreKilling(context.Background(), nil, nil))
   197  	require.NoError(t, h.Exited(context.Background(), nil, nil))
   198  	require.NoError(t, h.Stop(context.Background(), nil, nil))
   199  
   200  	// Ensure the Nomad mock provider has the expected operations.
   201  	nomadOps := nomadMockClient.GetOps()
   202  	require.Len(t, nomadOps, 2)
   203  	require.Equal(t, "add", nomadOps[0].Op)    // Poststart
   204  	require.Equal(t, "remove", nomadOps[1].Op) // PreKilling,Exited,Stop
   205  
   206  	// Ensure the Consul mock provider has zero operations.
   207  	require.Len(t, consulMockClient.GetOps(), 0)
   208  }