github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/worker/uniter/runner/context.go (about) 1 // Copyright 2012-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package runner 5 6 import ( 7 "fmt" 8 "os" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/juju/errors" 14 "github.com/juju/loggo" 15 "github.com/juju/names" 16 "github.com/juju/utils/proxy" 17 "gopkg.in/juju/charm.v4" 18 19 "github.com/juju/juju/api/uniter" 20 "github.com/juju/juju/apiserver/params" 21 "github.com/juju/juju/network" 22 "github.com/juju/juju/storage" 23 "github.com/juju/juju/worker/uniter/runner/jujuc" 24 ) 25 26 var logger = loggo.GetLogger("juju.worker.uniter.context") 27 var mutex = sync.Mutex{} 28 29 // meterStatus describes the unit's meter status. 30 type meterStatus struct { 31 code string 32 info string 33 } 34 35 // HookContext is the implementation of jujuc.Context. 36 type HookContext struct { 37 unit *uniter.Unit 38 39 // state is the handle to the uniter State so that HookContext can make 40 // API calls on the stateservice. 41 // NOTE: We would like to be rid of the fake-remote-Unit and switch 42 // over fully to API calls on State. This adds that ability, but we're 43 // not fully there yet. 44 state *uniter.State 45 46 // privateAddress is the cached value of the unit's private 47 // address. 48 privateAddress string 49 50 // publicAddress is the cached value of the unit's public 51 // address. 52 publicAddress string 53 54 // availabilityzone is the cached value of the unit's availability zone name. 55 availabilityzone string 56 57 // configSettings holds the service configuration. 58 configSettings charm.Settings 59 60 // id identifies the context. 61 id string 62 63 // actionData contains the values relevant to the run of an Action: 64 // its tag, its parameters, and its results. 65 actionData *ActionData 66 67 // uuid is the universally unique identifier of the environment. 68 uuid string 69 70 // envName is the human friendly name of the environment. 71 envName string 72 73 // unitName is the human friendly name of the local unit. 74 unitName string 75 76 // relationId identifies the relation for which a relation hook is 77 // executing. If it is -1, the context is not running a relation hook; 78 // otherwise, its value must be a valid key into the relations map. 79 relationId int 80 81 // remoteUnitName identifies the changing unit of the executing relation 82 // hook. It will be empty if the context is not running a relation hook, 83 // or if it is running a relation-broken hook. 84 remoteUnitName string 85 86 // relations contains the context for every relation the unit is a member 87 // of, keyed on relation id. 88 relations map[int]*ContextRelation 89 90 // apiAddrs contains the API server addresses. 91 apiAddrs []string 92 93 // serviceOwner contains the user tag of the service owner. 94 serviceOwner names.UserTag 95 96 // proxySettings are the current proxy settings that the uniter knows about. 97 proxySettings proxy.Settings 98 99 // metrics are the metrics recorded by calls to add-metric. 100 metrics []jujuc.Metric 101 102 // canAddMetrics specifies whether the hook allows recording metrics. 103 canAddMetrics bool 104 105 // definedMetrics specifies the metrics the charm has defined in its metrics.yaml file. 106 definedMetrics *charm.Metrics 107 108 // meterStatus is the status of the unit's metering. 109 meterStatus *meterStatus 110 111 // pendingPorts contains a list of port ranges to be opened or 112 // closed when the current hook is committed. 113 pendingPorts map[PortRange]PortRangeInfo 114 115 // machinePorts contains cached information about all opened port 116 // ranges on the unit's assigned machine, mapped to the unit that 117 // opened each range and the relevant relation. 118 machinePorts map[network.PortRange]params.RelationUnit 119 120 // assignedMachineTag contains the tag of the unit's assigned 121 // machine. 122 assignedMachineTag names.MachineTag 123 124 // process is the process of the command that is being run in the local context, 125 // like a juju-run command or a hook 126 process *os.Process 127 128 // rebootPriority tells us when the hook wants to reboot. If rebootPriority is jujuc.RebootNow 129 // the hook will be killed and requeued 130 rebootPriority jujuc.RebootPriority 131 132 // storageInstances contains the storageInstances associated with a unit, 133 storageInstances []storage.StorageInstance 134 135 // storageId is the id of the storage instance associated with the running hook. 136 storageId string 137 } 138 139 func (ctx *HookContext) RequestReboot(priority jujuc.RebootPriority) error { 140 var err error 141 if priority == jujuc.RebootNow { 142 // At this point, the hook should be running 143 err = ctx.killCharmHook() 144 } 145 146 switch err { 147 case nil, ErrNoProcess: 148 // ErrNoProcess almost certainly means we are running in debug hooks 149 ctx.SetRebootPriority(priority) 150 } 151 return err 152 } 153 154 func (ctx *HookContext) GetRebootPriority() jujuc.RebootPriority { 155 mutex.Lock() 156 defer mutex.Unlock() 157 return ctx.rebootPriority 158 } 159 160 func (ctx *HookContext) SetRebootPriority(priority jujuc.RebootPriority) { 161 mutex.Lock() 162 defer mutex.Unlock() 163 ctx.rebootPriority = priority 164 } 165 166 func (ctx *HookContext) GetProcess() *os.Process { 167 mutex.Lock() 168 defer mutex.Unlock() 169 return ctx.process 170 } 171 172 func (ctx *HookContext) SetProcess(process *os.Process) { 173 mutex.Lock() 174 defer mutex.Unlock() 175 ctx.process = process 176 } 177 178 func (ctx *HookContext) Id() string { 179 return ctx.id 180 } 181 182 func (ctx *HookContext) UnitName() string { 183 return ctx.unitName 184 } 185 186 func (ctx *HookContext) PublicAddress() (string, bool) { 187 return ctx.publicAddress, ctx.publicAddress != "" 188 } 189 190 func (ctx *HookContext) PrivateAddress() (string, bool) { 191 return ctx.privateAddress, ctx.privateAddress != "" 192 } 193 194 func (ctx *HookContext) AvailabilityZone() (string, bool) { 195 return ctx.availabilityzone, ctx.availabilityzone != "" 196 } 197 198 func (ctx *HookContext) HookStorageInstance() (*storage.StorageInstance, bool) { 199 return ctx.StorageInstance(ctx.storageId) 200 } 201 202 func (ctx *HookContext) StorageInstance(storageId string) (*storage.StorageInstance, bool) { 203 for _, storageInstance := range ctx.storageInstances { 204 if storageInstance.Id == ctx.storageId { 205 return &storageInstance, true 206 } 207 } 208 return nil, false 209 } 210 211 func (ctx *HookContext) OpenPorts(protocol string, fromPort, toPort int) error { 212 return tryOpenPorts( 213 protocol, fromPort, toPort, 214 ctx.unit.Tag(), 215 ctx.machinePorts, ctx.pendingPorts, 216 ) 217 } 218 219 func (ctx *HookContext) ClosePorts(protocol string, fromPort, toPort int) error { 220 return tryClosePorts( 221 protocol, fromPort, toPort, 222 ctx.unit.Tag(), 223 ctx.machinePorts, ctx.pendingPorts, 224 ) 225 } 226 227 func (ctx *HookContext) OpenedPorts() []network.PortRange { 228 var unitRanges []network.PortRange 229 for portRange, relUnit := range ctx.machinePorts { 230 if relUnit.Unit == ctx.unit.Tag().String() { 231 unitRanges = append(unitRanges, portRange) 232 } 233 } 234 network.SortPortRanges(unitRanges) 235 return unitRanges 236 } 237 238 func (ctx *HookContext) OwnerTag() string { 239 return ctx.serviceOwner.String() 240 } 241 242 func (ctx *HookContext) ConfigSettings() (charm.Settings, error) { 243 if ctx.configSettings == nil { 244 var err error 245 ctx.configSettings, err = ctx.unit.ConfigSettings() 246 if err != nil { 247 return nil, err 248 } 249 } 250 result := charm.Settings{} 251 for name, value := range ctx.configSettings { 252 result[name] = value 253 } 254 return result, nil 255 } 256 257 // ActionName returns the name of the action. 258 func (ctx *HookContext) ActionName() (string, error) { 259 if ctx.actionData == nil { 260 return "", errors.New("not running an action") 261 } 262 return ctx.actionData.ActionName, nil 263 } 264 265 // ActionParams simply returns the arguments to the Action. 266 func (ctx *HookContext) ActionParams() (map[string]interface{}, error) { 267 if ctx.actionData == nil { 268 return nil, errors.New("not running an action") 269 } 270 return ctx.actionData.ActionParams, nil 271 } 272 273 // SetActionMessage sets a message for the Action, usually an error message. 274 func (ctx *HookContext) SetActionMessage(message string) error { 275 if ctx.actionData == nil { 276 return errors.New("not running an action") 277 } 278 ctx.actionData.ResultsMessage = message 279 return nil 280 } 281 282 // SetActionFailed sets the fail state of the action. 283 func (ctx *HookContext) SetActionFailed() error { 284 if ctx.actionData == nil { 285 return errors.New("not running an action") 286 } 287 ctx.actionData.ActionFailed = true 288 return nil 289 } 290 291 // UpdateActionResults inserts new values for use with action-set and 292 // action-fail. The results struct will be delivered to the state server 293 // upon completion of the Action. It returns an error if not called on an 294 // Action-containing HookContext. 295 func (ctx *HookContext) UpdateActionResults(keys []string, value string) error { 296 if ctx.actionData == nil { 297 return errors.New("not running an action") 298 } 299 addValueToMap(keys, value, ctx.actionData.ResultsMap) 300 return nil 301 } 302 303 func (ctx *HookContext) HookRelation() (jujuc.ContextRelation, bool) { 304 return ctx.Relation(ctx.relationId) 305 } 306 307 func (ctx *HookContext) RemoteUnitName() (string, bool) { 308 return ctx.remoteUnitName, ctx.remoteUnitName != "" 309 } 310 311 func (ctx *HookContext) Relation(id int) (jujuc.ContextRelation, bool) { 312 r, found := ctx.relations[id] 313 return r, found 314 } 315 316 func (ctx *HookContext) RelationIds() []int { 317 ids := []int{} 318 for id := range ctx.relations { 319 ids = append(ids, id) 320 } 321 return ids 322 } 323 324 // AddMetrics adds metrics to the hook context. 325 func (ctx *HookContext) AddMetric(key, value string, created time.Time) error { 326 if !ctx.canAddMetrics || ctx.definedMetrics == nil { 327 return errors.New("metrics disabled") 328 } 329 err := ctx.definedMetrics.ValidateMetric(key, value) 330 if err != nil { 331 return errors.Annotatef(err, "invalid metric %q", key) 332 } 333 ctx.metrics = append(ctx.metrics, jujuc.Metric{key, value, created}) 334 return nil 335 } 336 337 // ActionData returns the context's internal action data. It's meant to be 338 // transitory; it exists to allow uniter and runner code to keep working as 339 // it did; it should be considered deprecated, and not used by new clients. 340 func (c *HookContext) ActionData() (*ActionData, error) { 341 if c.actionData == nil { 342 return nil, errors.New("not running an action") 343 } 344 return c.actionData, nil 345 } 346 347 // HookVars returns an os.Environ-style list of strings necessary to run a hook 348 // such that it can know what environment it's operating in, and can call back 349 // into context. 350 func (context *HookContext) HookVars(paths Paths) []string { 351 vars := context.proxySettings.AsEnvironmentValues() 352 vars = append(vars, 353 "CHARM_DIR="+paths.GetCharmDir(), // legacy, embarrassing 354 "JUJU_CHARM_DIR="+paths.GetCharmDir(), 355 "JUJU_CONTEXT_ID="+context.id, 356 "JUJU_AGENT_SOCKET="+paths.GetJujucSocket(), 357 "JUJU_UNIT_NAME="+context.unitName, 358 "JUJU_ENV_UUID="+context.uuid, 359 "JUJU_ENV_NAME="+context.envName, 360 "JUJU_API_ADDRESSES="+strings.Join(context.apiAddrs, " "), 361 "JUJU_METER_STATUS="+context.meterStatus.code, 362 "JUJU_METER_INFO="+context.meterStatus.info, 363 "JUJU_MACHINE_ID="+context.assignedMachineTag.Id(), 364 "JUJU_AVAILABILITY_ZONE="+context.availabilityzone, 365 ) 366 if r, found := context.HookRelation(); found { 367 vars = append(vars, 368 "JUJU_RELATION="+r.Name(), 369 "JUJU_RELATION_ID="+r.FakeId(), 370 "JUJU_REMOTE_UNIT="+context.remoteUnitName, 371 ) 372 } 373 if context.actionData != nil { 374 vars = append(vars, 375 "JUJU_ACTION_NAME="+context.actionData.ActionName, 376 "JUJU_ACTION_UUID="+context.actionData.ActionTag.Id(), 377 "JUJU_ACTION_TAG="+context.actionData.ActionTag.String(), 378 ) 379 } 380 return append(vars, osDependentEnvVars(paths)...) 381 } 382 383 func (ctx *HookContext) handleReboot(err *error) { 384 logger.Infof("handling reboot") 385 rebootPriority := ctx.GetRebootPriority() 386 switch rebootPriority { 387 case jujuc.RebootSkip: 388 return 389 case jujuc.RebootAfterHook: 390 // Reboot should happen only after hook has finished. 391 if *err != nil { 392 return 393 } 394 *err = ErrReboot 395 case jujuc.RebootNow: 396 *err = ErrRequeueAndReboot 397 } 398 reqErr := ctx.unit.RequestReboot() 399 if reqErr != nil { 400 *err = reqErr 401 } 402 } 403 404 func (ctx *HookContext) FlushContext(process string, ctxErr error) (err error) { 405 writeChanges := ctxErr == nil 406 407 // In the case of Actions, handle any errors using finalizeAction. 408 if ctx.actionData != nil { 409 // If we had an error in err at this point, it's part of the 410 // normal behavior of an Action. Errors which happen during 411 // the finalize should be handed back to the uniter. Close 412 // over the existing err, clear it, and only return errors 413 // which occur during the finalize, e.g. API call errors. 414 defer func(ctxErr error) { 415 err = ctx.finalizeAction(ctxErr, err) 416 }(ctxErr) 417 ctxErr = nil 418 } else { 419 // TODO(gsamfira): Just for now, reboot will not be supported in actions. 420 defer ctx.handleReboot(&err) 421 } 422 423 for id, rctx := range ctx.relations { 424 if writeChanges { 425 if e := rctx.WriteSettings(); e != nil { 426 e = errors.Errorf( 427 "could not write settings from %q to relation %d: %v", 428 process, id, e, 429 ) 430 logger.Errorf("%v", e) 431 if ctxErr == nil { 432 ctxErr = e 433 } 434 } 435 } 436 } 437 438 for rangeKey, rangeInfo := range ctx.pendingPorts { 439 if writeChanges { 440 var e error 441 var op string 442 if rangeInfo.ShouldOpen { 443 e = ctx.unit.OpenPorts( 444 rangeKey.Ports.Protocol, 445 rangeKey.Ports.FromPort, 446 rangeKey.Ports.ToPort, 447 ) 448 op = "open" 449 } else { 450 e = ctx.unit.ClosePorts( 451 rangeKey.Ports.Protocol, 452 rangeKey.Ports.FromPort, 453 rangeKey.Ports.ToPort, 454 ) 455 op = "close" 456 } 457 if e != nil { 458 e = errors.Annotatef(e, "cannot %s %v", op, rangeKey.Ports) 459 logger.Errorf("%v", e) 460 if ctxErr == nil { 461 ctxErr = e 462 } 463 } 464 } 465 } 466 if ctxErr != nil { 467 return ctxErr 468 } 469 470 // TODO (tasdomas) 2014 09 03: context finalization needs to modified to apply all 471 // changes in one api call to minimize the risk 472 // of partial failures. 473 if ctx.canAddMetrics && len(ctx.metrics) > 0 { 474 if writeChanges { 475 metrics := make([]params.Metric, len(ctx.metrics)) 476 for i, metric := range ctx.metrics { 477 metrics[i] = params.Metric{Key: metric.Key, Value: metric.Value, Time: metric.Time} 478 } 479 if e := ctx.unit.AddMetrics(metrics); e != nil { 480 logger.Errorf("%v", e) 481 if ctxErr == nil { 482 ctxErr = e 483 } 484 } 485 } 486 ctx.metrics = nil 487 } 488 489 return ctxErr 490 } 491 492 // finalizeAction passes back the final status of an Action hook to state. 493 // It wraps any errors which occurred in normal behavior of the Action run; 494 // only errors passed in unhandledErr will be returned. 495 func (ctx *HookContext) finalizeAction(err, unhandledErr error) error { 496 // TODO (binary132): synchronize with gsamfira's reboot logic 497 message := ctx.actionData.ResultsMessage 498 results := ctx.actionData.ResultsMap 499 tag := ctx.actionData.ActionTag 500 status := params.ActionCompleted 501 if ctx.actionData.ActionFailed { 502 status = params.ActionFailed 503 } 504 505 // If we had an action error, we'll simply encapsulate it in the response 506 // and discard the error state. Actions should not error the uniter. 507 if err != nil { 508 message = err.Error() 509 if IsMissingHookError(err) { 510 message = fmt.Sprintf("action not implemented on unit %q", ctx.unitName) 511 } 512 status = params.ActionFailed 513 } 514 515 callErr := ctx.state.ActionFinish(tag, status, results, message) 516 if callErr != nil { 517 unhandledErr = errors.Wrap(unhandledErr, callErr) 518 } 519 return unhandledErr 520 } 521 522 // killCharmHook tries to kill the current running charm hook. 523 func (ctx *HookContext) killCharmHook() error { 524 proc := ctx.GetProcess() 525 if proc == nil { 526 // nothing to kill 527 return ErrNoProcess 528 } 529 logger.Infof("trying to kill context process %d", proc.Pid) 530 531 tick := time.After(0) 532 timeout := time.After(30 * time.Second) 533 for { 534 // We repeatedly try to kill the process until we fail; this is 535 // because we don't control the *Process, and our clients expect 536 // to be able to Wait(); so we can't Wait. We could do better, 537 // but not with a single implementation across all platforms. 538 // TODO(gsamfira): come up with a better cross-platform approach. 539 select { 540 case <-tick: 541 err := proc.Kill() 542 if err != nil { 543 logger.Infof("kill returned: %s", err) 544 logger.Infof("assuming already killed") 545 return nil 546 } 547 case <-timeout: 548 return errors.Errorf("failed to kill context process %d", proc.Pid) 549 } 550 logger.Infof("waiting for context process %d to die", proc.Pid) 551 tick = time.After(100 * time.Millisecond) 552 } 553 }