github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/allocrunner/taskrunner/service_hook.go (about) 1 package taskrunner 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 8 log "github.com/hashicorp/go-hclog" 9 "github.com/hashicorp/nomad/client/allocrunner/interfaces" 10 tinterfaces "github.com/hashicorp/nomad/client/allocrunner/taskrunner/interfaces" 11 "github.com/hashicorp/nomad/client/serviceregistration" 12 "github.com/hashicorp/nomad/client/serviceregistration/wrapper" 13 "github.com/hashicorp/nomad/client/taskenv" 14 "github.com/hashicorp/nomad/nomad/structs" 15 "github.com/hashicorp/nomad/plugins/drivers" 16 ) 17 18 var _ interfaces.TaskPoststartHook = &serviceHook{} 19 var _ interfaces.TaskPreKillHook = &serviceHook{} 20 var _ interfaces.TaskExitedHook = &serviceHook{} 21 var _ interfaces.TaskStopHook = &serviceHook{} 22 var _ interfaces.TaskUpdateHook = &serviceHook{} 23 24 const ( 25 taskServiceHookName = "task_services" 26 ) 27 28 type serviceHookConfig struct { 29 alloc *structs.Allocation 30 task *structs.Task 31 32 // namespace is the Nomad or Consul namespace in which service 33 // registrations will be made. 34 providerNamespace string 35 36 // serviceRegWrapper is the handler wrapper that is used to perform service 37 // and check registration and deregistration. 38 serviceRegWrapper *wrapper.HandlerWrapper 39 40 // Restarter is a subset of the TaskLifecycle interface 41 restarter serviceregistration.WorkloadRestarter 42 43 logger log.Logger 44 } 45 46 type serviceHook struct { 47 allocID string 48 jobID string 49 groupName string 50 taskName string 51 namespace string 52 restarter serviceregistration.WorkloadRestarter 53 logger log.Logger 54 55 // The following fields may be updated 56 driverExec tinterfaces.ScriptExecutor 57 driverNet *drivers.DriverNetwork 58 canary bool 59 services []*structs.Service 60 networks structs.Networks 61 ports structs.AllocatedPorts 62 taskEnv *taskenv.TaskEnv 63 64 // providerNamespace is the Nomad or Consul namespace in which service 65 // registrations will be made. This field may be updated. 66 providerNamespace string 67 68 // serviceRegWrapper is the handler wrapper that is used to perform service 69 // and check registration and deregistration. 70 serviceRegWrapper *wrapper.HandlerWrapper 71 72 // initialRegistrations tracks if Poststart has completed, initializing 73 // fields required in other lifecycle funcs 74 initialRegistration bool 75 76 // deregistered tracks whether deregister() has previously been called, so 77 // we do not call this multiple times for a single task when not needed. 78 deregistered bool 79 80 // Since Update() may be called concurrently with any other hook all 81 // hook methods must be fully serialized 82 mu sync.Mutex 83 } 84 85 func newServiceHook(c serviceHookConfig) *serviceHook { 86 h := &serviceHook{ 87 allocID: c.alloc.ID, 88 jobID: c.alloc.JobID, 89 groupName: c.alloc.TaskGroup, 90 taskName: c.task.Name, 91 namespace: c.alloc.Namespace, 92 providerNamespace: c.providerNamespace, 93 serviceRegWrapper: c.serviceRegWrapper, 94 services: c.task.Services, 95 restarter: c.restarter, 96 ports: c.alloc.AllocatedResources.Shared.Ports, 97 } 98 99 if res := c.alloc.AllocatedResources.Tasks[c.task.Name]; res != nil { 100 h.networks = res.Networks 101 } 102 103 if c.alloc.DeploymentStatus != nil && c.alloc.DeploymentStatus.Canary { 104 h.canary = true 105 } 106 107 h.logger = c.logger.Named(h.Name()) 108 return h 109 } 110 111 func (h *serviceHook) Name() string { return taskServiceHookName } 112 113 func (h *serviceHook) Poststart(ctx context.Context, req *interfaces.TaskPoststartRequest, _ *interfaces.TaskPoststartResponse) error { 114 h.mu.Lock() 115 defer h.mu.Unlock() 116 117 // Store the TaskEnv for interpolating now and when Updating 118 h.driverExec = req.DriverExec 119 h.driverNet = req.DriverNetwork 120 h.taskEnv = req.TaskEnv 121 h.initialRegistration = true 122 123 // Ensure deregistered is unset. 124 h.deregistered = false 125 126 // Create task services struct with request's driver metadata 127 workloadServices := h.getWorkloadServices() 128 129 return h.serviceRegWrapper.RegisterWorkload(workloadServices) 130 } 131 132 func (h *serviceHook) Update(ctx context.Context, req *interfaces.TaskUpdateRequest, _ *interfaces.TaskUpdateResponse) error { 133 h.mu.Lock() 134 defer h.mu.Unlock() 135 if !h.initialRegistration { 136 // no op since initial registration has not finished only update hook 137 // fields. 138 return h.updateHookFields(req) 139 } 140 141 // Create old task services struct with request's driver metadata as it 142 // can't change due to Updates 143 oldWorkloadServices := h.getWorkloadServices() 144 145 if err := h.updateHookFields(req); err != nil { 146 return err 147 } 148 149 // Create new task services struct with those new values 150 newWorkloadServices := h.getWorkloadServices() 151 152 return h.serviceRegWrapper.UpdateWorkload(oldWorkloadServices, newWorkloadServices) 153 } 154 155 func (h *serviceHook) updateHookFields(req *interfaces.TaskUpdateRequest) error { 156 // Store new updated values out of request 157 canary := false 158 if req.Alloc.DeploymentStatus != nil { 159 canary = req.Alloc.DeploymentStatus.Canary 160 } 161 162 var networks structs.Networks 163 if res := req.Alloc.AllocatedResources.Tasks[h.taskName]; res != nil { 164 networks = res.Networks 165 } 166 167 task := req.Alloc.LookupTask(h.taskName) 168 if task == nil { 169 return fmt.Errorf("task %q not found in updated alloc", h.taskName) 170 } 171 172 // Update service hook fields 173 h.taskEnv = req.TaskEnv 174 h.services = task.Services 175 h.networks = networks 176 h.canary = canary 177 h.ports = req.Alloc.AllocatedResources.Shared.Ports 178 179 // An update may change the service provider, therefore we need to account 180 // for how namespaces work across providers also. 181 h.providerNamespace = req.Alloc.ServiceProviderNamespace() 182 183 return nil 184 } 185 186 func (h *serviceHook) PreKilling(ctx context.Context, req *interfaces.TaskPreKillRequest, resp *interfaces.TaskPreKillResponse) error { 187 h.mu.Lock() 188 defer h.mu.Unlock() 189 190 // Deregister before killing task 191 h.deregister() 192 193 return nil 194 } 195 196 func (h *serviceHook) Exited(context.Context, *interfaces.TaskExitedRequest, *interfaces.TaskExitedResponse) error { 197 h.mu.Lock() 198 defer h.mu.Unlock() 199 h.deregister() 200 return nil 201 } 202 203 // deregister services from Consul. 204 func (h *serviceHook) deregister() { 205 if len(h.services) > 0 && !h.deregistered { 206 workloadServices := h.getWorkloadServices() 207 h.serviceRegWrapper.RemoveWorkload(workloadServices) 208 } 209 h.initialRegistration = false 210 h.deregistered = true 211 } 212 213 func (h *serviceHook) Stop(ctx context.Context, req *interfaces.TaskStopRequest, resp *interfaces.TaskStopResponse) error { 214 h.mu.Lock() 215 defer h.mu.Unlock() 216 h.deregister() 217 return nil 218 } 219 220 func (h *serviceHook) getWorkloadServices() *serviceregistration.WorkloadServices { 221 // Interpolate with the task's environment 222 interpolatedServices := taskenv.InterpolateServices(h.taskEnv, h.services) 223 224 info := structs.AllocInfo{ 225 AllocID: h.allocID, 226 JobID: h.jobID, 227 Task: h.taskName, 228 Namespace: h.namespace, 229 } 230 231 // Create task services struct with request's driver metadata 232 return &serviceregistration.WorkloadServices{ 233 AllocInfo: info, 234 ProviderNamespace: h.providerNamespace, 235 Restarter: h.restarter, 236 Services: interpolatedServices, 237 DriverExec: h.driverExec, 238 DriverNetwork: h.driverNet, 239 Networks: h.networks, 240 Canary: h.canary, 241 Ports: h.ports, 242 } 243 }