github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/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/consul" 12 "github.com/hashicorp/nomad/client/taskenv" 13 agentconsul "github.com/hashicorp/nomad/command/agent/consul" 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 23 type serviceHookConfig struct { 24 alloc *structs.Allocation 25 task *structs.Task 26 consul consul.ConsulServiceAPI 27 28 // Restarter is a subset of the TaskLifecycle interface 29 restarter agentconsul.WorkloadRestarter 30 31 logger log.Logger 32 } 33 34 type serviceHook struct { 35 consul consul.ConsulServiceAPI 36 allocID string 37 taskName string 38 restarter agentconsul.WorkloadRestarter 39 logger log.Logger 40 41 // The following fields may be updated 42 driverExec tinterfaces.ScriptExecutor 43 driverNet *drivers.DriverNetwork 44 canary bool 45 services []*structs.Service 46 networks structs.Networks 47 taskEnv *taskenv.TaskEnv 48 49 // initialRegistrations tracks if Poststart has completed, initializing 50 // fields required in other lifecycle funcs 51 initialRegistration bool 52 53 // Since Update() may be called concurrently with any other hook all 54 // hook methods must be fully serialized 55 mu sync.Mutex 56 } 57 58 func newServiceHook(c serviceHookConfig) *serviceHook { 59 h := &serviceHook{ 60 consul: c.consul, 61 allocID: c.alloc.ID, 62 taskName: c.task.Name, 63 services: c.task.Services, 64 restarter: c.restarter, 65 } 66 67 if res := c.alloc.AllocatedResources.Tasks[c.task.Name]; res != nil { 68 h.networks = res.Networks 69 } 70 71 if c.alloc.DeploymentStatus != nil && c.alloc.DeploymentStatus.Canary { 72 h.canary = true 73 } 74 75 h.logger = c.logger.Named(h.Name()) 76 return h 77 } 78 79 func (h *serviceHook) Name() string { 80 return "consul_services" 81 } 82 83 func (h *serviceHook) Poststart(ctx context.Context, req *interfaces.TaskPoststartRequest, _ *interfaces.TaskPoststartResponse) error { 84 h.mu.Lock() 85 defer h.mu.Unlock() 86 87 // Store the TaskEnv for interpolating now and when Updating 88 h.driverExec = req.DriverExec 89 h.driverNet = req.DriverNetwork 90 h.taskEnv = req.TaskEnv 91 h.initialRegistration = true 92 93 // Create task services struct with request's driver metadata 94 workloadServices := h.getWorkloadServices() 95 96 return h.consul.RegisterWorkload(workloadServices) 97 } 98 99 func (h *serviceHook) Update(ctx context.Context, req *interfaces.TaskUpdateRequest, _ *interfaces.TaskUpdateResponse) error { 100 h.mu.Lock() 101 defer h.mu.Unlock() 102 if !h.initialRegistration { 103 // no op Consul since initial registration has not finished 104 // only update hook fields 105 return h.updateHookFields(req) 106 } 107 108 // Create old task services struct with request's driver metadata as it 109 // can't change due to Updates 110 oldWorkloadServices := h.getWorkloadServices() 111 112 if err := h.updateHookFields(req); err != nil { 113 return err 114 } 115 116 // Create new task services struct with those new values 117 newWorkloadServices := h.getWorkloadServices() 118 119 return h.consul.UpdateWorkload(oldWorkloadServices, newWorkloadServices) 120 } 121 122 func (h *serviceHook) updateHookFields(req *interfaces.TaskUpdateRequest) error { 123 // Store new updated values out of request 124 canary := false 125 if req.Alloc.DeploymentStatus != nil { 126 canary = req.Alloc.DeploymentStatus.Canary 127 } 128 129 var networks structs.Networks 130 if res := req.Alloc.AllocatedResources.Tasks[h.taskName]; res != nil { 131 networks = res.Networks 132 } 133 134 task := req.Alloc.LookupTask(h.taskName) 135 if task == nil { 136 return fmt.Errorf("task %q not found in updated alloc", h.taskName) 137 } 138 139 // Update service hook fields 140 h.taskEnv = req.TaskEnv 141 h.services = task.Services 142 h.networks = networks 143 h.canary = canary 144 145 return nil 146 } 147 148 func (h *serviceHook) PreKilling(ctx context.Context, req *interfaces.TaskPreKillRequest, resp *interfaces.TaskPreKillResponse) error { 149 h.mu.Lock() 150 defer h.mu.Unlock() 151 152 // Deregister before killing task 153 h.deregister() 154 155 return nil 156 } 157 158 func (h *serviceHook) Exited(context.Context, *interfaces.TaskExitedRequest, *interfaces.TaskExitedResponse) error { 159 h.mu.Lock() 160 defer h.mu.Unlock() 161 h.deregister() 162 return nil 163 } 164 165 // deregister services from Consul. 166 func (h *serviceHook) deregister() { 167 workloadServices := h.getWorkloadServices() 168 h.consul.RemoveWorkload(workloadServices) 169 170 // Canary flag may be getting flipped when the alloc is being 171 // destroyed, so remove both variations of the service 172 workloadServices.Canary = !workloadServices.Canary 173 h.consul.RemoveWorkload(workloadServices) 174 h.initialRegistration = false 175 } 176 177 func (h *serviceHook) Stop(ctx context.Context, req *interfaces.TaskStopRequest, resp *interfaces.TaskStopResponse) error { 178 h.mu.Lock() 179 defer h.mu.Unlock() 180 h.deregister() 181 return nil 182 } 183 184 func (h *serviceHook) getWorkloadServices() *agentconsul.WorkloadServices { 185 // Interpolate with the task's environment 186 interpolatedServices := taskenv.InterpolateServices(h.taskEnv, h.services) 187 188 // Create task services struct with request's driver metadata 189 return &agentconsul.WorkloadServices{ 190 AllocID: h.allocID, 191 Task: h.taskName, 192 Restarter: h.restarter, 193 Services: interpolatedServices, 194 DriverExec: h.driverExec, 195 DriverNetwork: h.driverNet, 196 Networks: h.networks, 197 Canary: h.canary, 198 } 199 }