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 }