github.com/smithx10/nomad@v0.9.1-rc1/client/allocrunner/taskrunner/service_hook.go (about) 1 package taskrunner 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "time" 8 9 log "github.com/hashicorp/go-hclog" 10 "github.com/hashicorp/nomad/client/allocrunner/interfaces" 11 tinterfaces "github.com/hashicorp/nomad/client/allocrunner/taskrunner/interfaces" 12 "github.com/hashicorp/nomad/client/consul" 13 "github.com/hashicorp/nomad/client/taskenv" 14 agentconsul "github.com/hashicorp/nomad/command/agent/consul" 15 "github.com/hashicorp/nomad/nomad/structs" 16 "github.com/hashicorp/nomad/plugins/drivers" 17 ) 18 19 type serviceHookConfig struct { 20 alloc *structs.Allocation 21 task *structs.Task 22 consul consul.ConsulServiceAPI 23 24 // Restarter is a subset of the TaskLifecycle interface 25 restarter agentconsul.TaskRestarter 26 27 logger log.Logger 28 } 29 30 type serviceHook struct { 31 consul consul.ConsulServiceAPI 32 allocID string 33 taskName string 34 restarter agentconsul.TaskRestarter 35 logger log.Logger 36 37 // The following fields may be updated 38 delay time.Duration 39 driverExec tinterfaces.ScriptExecutor 40 driverNet *drivers.DriverNetwork 41 canary bool 42 services []*structs.Service 43 networks structs.Networks 44 taskEnv *taskenv.TaskEnv 45 46 // Since Update() may be called concurrently with any other hook all 47 // hook methods must be fully serialized 48 mu sync.Mutex 49 } 50 51 func newServiceHook(c serviceHookConfig) *serviceHook { 52 h := &serviceHook{ 53 consul: c.consul, 54 allocID: c.alloc.ID, 55 taskName: c.task.Name, 56 services: c.task.Services, 57 restarter: c.restarter, 58 delay: c.task.ShutdownDelay, 59 } 60 61 // COMPAT(0.10): Just use the AllocatedResources 62 if c.alloc.AllocatedResources != nil { 63 if res := c.alloc.AllocatedResources.Tasks[c.task.Name]; res != nil { 64 h.networks = res.Networks 65 } 66 } else { 67 if res := c.alloc.TaskResources[c.task.Name]; res != nil { 68 h.networks = res.Networks 69 } 70 } 71 72 if c.alloc.DeploymentStatus != nil && c.alloc.DeploymentStatus.Canary { 73 h.canary = true 74 } 75 76 h.logger = c.logger.Named(h.Name()) 77 return h 78 } 79 80 func (h *serviceHook) Name() string { 81 return "consul_services" 82 } 83 84 func (h *serviceHook) Poststart(ctx context.Context, req *interfaces.TaskPoststartRequest, _ *interfaces.TaskPoststartResponse) error { 85 h.mu.Lock() 86 defer h.mu.Unlock() 87 88 // Store the TaskEnv for interpolating now and when Updating 89 h.driverExec = req.DriverExec 90 h.driverNet = req.DriverNetwork 91 h.taskEnv = req.TaskEnv 92 93 // Create task services struct with request's driver metadata 94 taskServices := h.getTaskServices() 95 96 return h.consul.RegisterTask(taskServices) 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 103 // Create old task services struct with request's driver metadata as it 104 // can't change due to Updates 105 oldTaskServices := h.getTaskServices() 106 107 // Store new updated values out of request 108 canary := false 109 if req.Alloc.DeploymentStatus != nil { 110 canary = req.Alloc.DeploymentStatus.Canary 111 } 112 113 // COMPAT(0.10): Just use the AllocatedResources 114 var networks structs.Networks 115 if req.Alloc.AllocatedResources != nil { 116 if res := req.Alloc.AllocatedResources.Tasks[h.taskName]; res != nil { 117 networks = res.Networks 118 } 119 } else { 120 if res := req.Alloc.TaskResources[h.taskName]; res != nil { 121 networks = res.Networks 122 } 123 } 124 125 task := req.Alloc.LookupTask(h.taskName) 126 if task == nil { 127 return fmt.Errorf("task %q not found in updated alloc", h.taskName) 128 } 129 130 // Update service hook fields 131 h.delay = task.ShutdownDelay 132 h.taskEnv = req.TaskEnv 133 h.services = task.Services 134 h.networks = networks 135 h.canary = canary 136 137 // Create new task services struct with those new values 138 newTaskServices := h.getTaskServices() 139 140 return h.consul.UpdateTask(oldTaskServices, newTaskServices) 141 } 142 143 func (h *serviceHook) PreKilling(ctx context.Context, req *interfaces.TaskPreKillRequest, resp *interfaces.TaskPreKillResponse) error { 144 h.mu.Lock() 145 defer h.mu.Unlock() 146 147 // Deregister before killing task 148 h.deregister() 149 150 // If there's no shutdown delay, exit early 151 if h.delay == 0 { 152 return nil 153 } 154 155 h.logger.Debug("waiting before killing task", "shutdown_delay", h.delay) 156 select { 157 case <-ctx.Done(): 158 case <-time.After(h.delay): 159 } 160 return nil 161 } 162 163 func (h *serviceHook) Exited(context.Context, *interfaces.TaskExitedRequest, *interfaces.TaskExitedResponse) error { 164 h.mu.Lock() 165 defer h.mu.Unlock() 166 h.deregister() 167 return nil 168 } 169 170 // deregister services from Consul. 171 func (h *serviceHook) deregister() { 172 taskServices := h.getTaskServices() 173 h.consul.RemoveTask(taskServices) 174 175 // Canary flag may be getting flipped when the alloc is being 176 // destroyed, so remove both variations of the service 177 taskServices.Canary = !taskServices.Canary 178 h.consul.RemoveTask(taskServices) 179 180 } 181 182 func (h *serviceHook) getTaskServices() *agentconsul.TaskServices { 183 // Interpolate with the task's environment 184 interpolatedServices := interpolateServices(h.taskEnv, h.services) 185 186 // Create task services struct with request's driver metadata 187 return &agentconsul.TaskServices{ 188 AllocID: h.allocID, 189 Name: h.taskName, 190 Restarter: h.restarter, 191 Services: interpolatedServices, 192 DriverExec: h.driverExec, 193 DriverNetwork: h.driverNet, 194 Networks: h.networks, 195 Canary: h.canary, 196 } 197 } 198 199 // interpolateServices returns an interpolated copy of services and checks with 200 // values from the task's environment. 201 func interpolateServices(taskEnv *taskenv.TaskEnv, services []*structs.Service) []*structs.Service { 202 // Guard against not having a valid taskEnv. This can be the case if the 203 // PreKilling or Exited hook is run before Poststart. 204 if taskEnv == nil || len(services) == 0 { 205 return nil 206 } 207 208 interpolated := make([]*structs.Service, len(services)) 209 210 for i, origService := range services { 211 // Create a copy as we need to reinterpolate every time the 212 // environment changes 213 service := origService.Copy() 214 215 for _, check := range service.Checks { 216 check.Name = taskEnv.ReplaceEnv(check.Name) 217 check.Type = taskEnv.ReplaceEnv(check.Type) 218 check.Command = taskEnv.ReplaceEnv(check.Command) 219 check.Args = taskEnv.ParseAndReplace(check.Args) 220 check.Path = taskEnv.ReplaceEnv(check.Path) 221 check.Protocol = taskEnv.ReplaceEnv(check.Protocol) 222 check.PortLabel = taskEnv.ReplaceEnv(check.PortLabel) 223 check.InitialStatus = taskEnv.ReplaceEnv(check.InitialStatus) 224 check.Method = taskEnv.ReplaceEnv(check.Method) 225 check.GRPCService = taskEnv.ReplaceEnv(check.GRPCService) 226 if len(check.Header) > 0 { 227 header := make(map[string][]string, len(check.Header)) 228 for k, vs := range check.Header { 229 newVals := make([]string, len(vs)) 230 for i, v := range vs { 231 newVals[i] = taskEnv.ReplaceEnv(v) 232 } 233 header[taskEnv.ReplaceEnv(k)] = newVals 234 } 235 check.Header = header 236 } 237 } 238 239 service.Name = taskEnv.ReplaceEnv(service.Name) 240 service.PortLabel = taskEnv.ReplaceEnv(service.PortLabel) 241 service.Tags = taskEnv.ParseAndReplace(service.Tags) 242 service.CanaryTags = taskEnv.ParseAndReplace(service.CanaryTags) 243 interpolated[i] = service 244 } 245 246 return interpolated 247 }