github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/worker/uniter/context.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter 5 6 import ( 7 "bufio" 8 "fmt" 9 "io" 10 // "os" 11 "os/exec" 12 // "path/filepath" 13 "sort" 14 "sync" 15 "syscall" 16 "time" 17 18 "github.com/juju/loggo" 19 20 "launchpad.net/juju-core/charm" 21 "launchpad.net/juju-core/juju/osenv" 22 "launchpad.net/juju-core/state/api/params" 23 "launchpad.net/juju-core/state/api/uniter" 24 utilexec "launchpad.net/juju-core/utils/exec" 25 "launchpad.net/juju-core/worker/uniter/jujuc" 26 unitdebug "launchpad.net/juju-core/worker/uniter/debug" 27 ) 28 29 type missingHookError struct { 30 hookName string 31 } 32 33 func RebootRequiredError(err error) bool { 34 if err == nil { 35 return false 36 } 37 msg, _ := err.(*exec.ExitError) 38 if msg == nil { 39 return false 40 } 41 code := msg.Sys().(syscall.WaitStatus).ExitStatus() 42 if code == osenv.MustReboot { 43 return true 44 } 45 46 return false 47 } 48 49 func (e *missingHookError) Error() string { 50 return e.hookName + " does not exist" 51 } 52 53 func IsMissingHookError(err error) bool { 54 _, ok := err.(*missingHookError) 55 return ok 56 } 57 58 // HookContext is the implementation of jujuc.Context. 59 type HookContext struct { 60 unit *uniter.Unit 61 62 // privateAddress is the cached value of the unit's private 63 // address. 64 privateAddress string 65 66 // publicAddress is the cached value of the unit's public 67 // address. 68 publicAddress string 69 70 // configSettings holds the service configuration. 71 configSettings charm.Settings 72 73 // id identifies the context. 74 id string 75 76 // uuid is the universally unique identifier of the environment. 77 uuid string 78 79 // envName is the human friendly name of the environment. 80 envName string 81 82 // relationId identifies the relation for which a relation hook is 83 // executing. If it is -1, the context is not running a relation hook; 84 // otherwise, its value must be a valid key into the relations map. 85 relationId int 86 87 // remoteUnitName identifies the changing unit of the executing relation 88 // hook. It will be empty if the context is not running a relation hook, 89 // or if it is running a relation-broken hook. 90 remoteUnitName string 91 92 // relations contains the context for every relation the unit is a member 93 // of, keyed on relation id. 94 relations map[int]*ContextRelation 95 96 // apiAddrs contains the API server addresses. 97 apiAddrs []string 98 99 // serviceOwner contains the owner of the service 100 serviceOwner string 101 102 // proxySettings are the current proxy settings that the uniter knows about 103 proxySettings osenv.ProxySettings 104 } 105 106 func NewHookContext(unit *uniter.Unit, id, uuid, envName string, 107 relationId int, remoteUnitName string, relations map[int]*ContextRelation, 108 apiAddrs []string, serviceOwner string, proxySettings osenv.ProxySettings) (*HookContext, error) { 109 ctx := &HookContext{ 110 unit: unit, 111 id: id, 112 uuid: uuid, 113 envName: envName, 114 relationId: relationId, 115 remoteUnitName: remoteUnitName, 116 relations: relations, 117 apiAddrs: apiAddrs, 118 serviceOwner: serviceOwner, 119 proxySettings: proxySettings, 120 } 121 // Get and cache the addresses. 122 var err error 123 ctx.publicAddress, err = unit.PublicAddress() 124 if err != nil && !params.IsCodeNoAddressSet(err) { 125 return nil, err 126 } 127 ctx.privateAddress, err = unit.PrivateAddress() 128 if err != nil && !params.IsCodeNoAddressSet(err) { 129 return nil, err 130 } 131 return ctx, nil 132 } 133 134 func (ctx *HookContext) UnitName() string { 135 return ctx.unit.Name() 136 } 137 138 func (ctx *HookContext) PublicAddress() (string, bool) { 139 return ctx.publicAddress, ctx.publicAddress != "" 140 } 141 142 func (ctx *HookContext) PrivateAddress() (string, bool) { 143 return ctx.privateAddress, ctx.privateAddress != "" 144 } 145 146 func (ctx *HookContext) OpenPort(protocol string, port int) error { 147 return ctx.unit.OpenPort(protocol, port) 148 } 149 150 func (ctx *HookContext) ClosePort(protocol string, port int) error { 151 return ctx.unit.ClosePort(protocol, port) 152 } 153 154 func (ctx *HookContext) OwnerTag() string { 155 return ctx.serviceOwner 156 } 157 158 func (ctx *HookContext) ConfigSettings() (charm.Settings, error) { 159 if ctx.configSettings == nil { 160 var err error 161 ctx.configSettings, err = ctx.unit.ConfigSettings() 162 if err != nil { 163 return nil, err 164 } 165 } 166 result := charm.Settings{} 167 for name, value := range ctx.configSettings { 168 result[name] = value 169 } 170 return result, nil 171 } 172 173 func (ctx *HookContext) HookRelation() (jujuc.ContextRelation, bool) { 174 return ctx.Relation(ctx.relationId) 175 } 176 177 func (ctx *HookContext) RemoteUnitName() (string, bool) { 178 return ctx.remoteUnitName, ctx.remoteUnitName != "" 179 } 180 181 func (ctx *HookContext) Relation(id int) (jujuc.ContextRelation, bool) { 182 r, found := ctx.relations[id] 183 return r, found 184 } 185 186 func (ctx *HookContext) RelationIds() []int { 187 ids := []int{} 188 for id := range ctx.relations { 189 ids = append(ids, id) 190 } 191 return ids 192 } 193 194 func (ctx *HookContext) finalizeContext(process string, err error) error { 195 if err != nil{ 196 // gsamfira: We need this later to requeue the hook 197 logger.Infof("Checking if reboot is needed") 198 if RebootRequiredError(err){ 199 logger.Infof("Error code is reboot code") 200 return err 201 } 202 } 203 logger.Infof("Continuing without reboot") 204 writeChanges := err == nil 205 for id, rctx := range ctx.relations { 206 if writeChanges { 207 if e := rctx.WriteSettings(); e != nil { 208 e = fmt.Errorf( 209 "could not write settings from %q to relation %d: %v", 210 process, id, e, 211 ) 212 logger.Errorf("%v", e) 213 if err == nil { 214 err = e 215 } 216 } 217 } 218 rctx.ClearCache() 219 } 220 return err 221 } 222 223 // RunCommands executes the commands in an environment which allows it to to 224 // call back into the hook context to execute jujuc tools. 225 func (ctx *HookContext) RunCommands(commands, charmDir, toolsDir, socketPath string) (*utilexec.ExecResponse, error) { 226 env := ctx.hookVars(charmDir, toolsDir, socketPath) 227 result, err := utilexec.RunCommands( 228 utilexec.RunParams{ 229 Commands: commands, 230 WorkingDir: charmDir, 231 Environment: env}) 232 return result, ctx.finalizeContext("run commands", err) 233 } 234 235 func (ctx *HookContext) GetLogger(hookName string) loggo.Logger { 236 return loggo.GetLogger(fmt.Sprintf("unit.%s.%s", ctx.UnitName(), hookName)) 237 } 238 239 240 // RunHook executes a hook in an environment which allows it to to call back 241 // into the hook context to execute jujuc tools. 242 func (ctx *HookContext) RunHook(hookName, charmDir, toolsDir, socketPath string) error { 243 var err error 244 env := ctx.hookVars(charmDir, toolsDir, socketPath) 245 debugctx := unitdebug.NewHooksContext(ctx.unit.Name()) 246 if session, _ := debugctx.FindSession(); session != nil && session.MatchHook(hookName) { 247 logger.Infof("executing %s via debug-hooks", hookName) 248 err = session.RunHook(hookName, charmDir, env) 249 } else { 250 err = ctx.runCharmHook(hookName, charmDir, env) 251 } 252 return ctx.finalizeContext(hookName, err) 253 } 254 255 type hookLogger struct { 256 r io.ReadCloser 257 done chan struct{} 258 mu sync.Mutex 259 stopped bool 260 logger loggo.Logger 261 } 262 263 func (l *hookLogger) run() { 264 defer close(l.done) 265 defer l.r.Close() 266 br := bufio.NewReaderSize(l.r, 4096) 267 for { 268 line, _, err := br.ReadLine() 269 if err != nil { 270 if err != io.EOF { 271 logger.Errorf("cannot read hook output: %v", err) 272 } 273 break 274 } 275 l.mu.Lock() 276 if l.stopped { 277 l.mu.Unlock() 278 return 279 } 280 l.logger.Infof("%s", line) 281 l.mu.Unlock() 282 } 283 } 284 285 func (l *hookLogger) stop() { 286 // We can see the process exit before the logger has processed 287 // all its output, so allow a moment for the data buffered 288 // in the pipe to be processed. We don't wait indefinitely though, 289 // because the hook may have started a background process 290 // that keeps the pipe open. 291 select { 292 case <-l.done: 293 case <-time.After(100 * time.Millisecond): 294 } 295 // We can't close the pipe asynchronously, so just 296 // stifle output instead. 297 l.mu.Lock() 298 l.stopped = true 299 l.mu.Unlock() 300 } 301 302 // SettingsMap is a map from unit name to relation settings. 303 type SettingsMap map[string]params.RelationSettings 304 305 // ContextRelation is the implementation of jujuc.ContextRelation. 306 type ContextRelation struct { 307 ru *uniter.RelationUnit 308 309 // members contains settings for known relation members. Nil values 310 // indicate members whose settings have not yet been cached. 311 members SettingsMap 312 313 // settings allows read and write access to the relation unit settings. 314 settings *uniter.Settings 315 316 // cache is a short-term cache that enables consistent access to settings 317 // for units that are not currently participating in the relation. Its 318 // contents should be cleared whenever a new hook is executed. 319 cache SettingsMap 320 } 321 322 // NewContextRelation creates a new context for the given relation unit. 323 // The unit-name keys of members supplies the initial membership. 324 func NewContextRelation(ru *uniter.RelationUnit, members map[string]int64) *ContextRelation { 325 ctx := &ContextRelation{ru: ru, members: SettingsMap{}} 326 for unit := range members { 327 ctx.members[unit] = nil 328 } 329 ctx.ClearCache() 330 return ctx 331 } 332 333 // WriteSettings persists all changes made to the unit's relation settings. 334 func (ctx *ContextRelation) WriteSettings() (err error) { 335 if ctx.settings != nil { 336 err = ctx.settings.Write() 337 } 338 return 339 } 340 341 // ClearCache discards all cached settings for units that are not members 342 // of the relation, and all unwritten changes to the unit's relation settings. 343 // including any changes to Settings that have not been written. 344 func (ctx *ContextRelation) ClearCache() { 345 ctx.settings = nil 346 ctx.cache = make(SettingsMap) 347 } 348 349 // UpdateMembers ensures that the context is aware of every supplied 350 // member unit. For each supplied member, the cached settings will be 351 // overwritten. 352 func (ctx *ContextRelation) UpdateMembers(members SettingsMap) { 353 for m, s := range members { 354 ctx.members[m] = s 355 } 356 } 357 358 // DeleteMember drops the membership and cache of a single remote unit, without 359 // perturbing settings for the remaining members. 360 func (ctx *ContextRelation) DeleteMember(unitName string) { 361 delete(ctx.members, unitName) 362 } 363 364 func (ctx *ContextRelation) Id() int { 365 return ctx.ru.Relation().Id() 366 } 367 368 func (ctx *ContextRelation) Name() string { 369 return ctx.ru.Endpoint().Name 370 } 371 372 func (ctx *ContextRelation) FakeId() string { 373 return fmt.Sprintf("%s:%d", ctx.Name(), ctx.ru.Relation().Id()) 374 } 375 376 func (ctx *ContextRelation) UnitNames() (units []string) { 377 for unit := range ctx.members { 378 units = append(units, unit) 379 } 380 sort.Strings(units) 381 return units 382 } 383 384 func (ctx *ContextRelation) Settings() (jujuc.Settings, error) { 385 if ctx.settings == nil { 386 node, err := ctx.ru.Settings() 387 if err != nil { 388 return nil, err 389 } 390 ctx.settings = node 391 } 392 return ctx.settings, nil 393 } 394 395 func (ctx *ContextRelation) ReadSettings(unit string) (settings params.RelationSettings, err error) { 396 settings, member := ctx.members[unit] 397 if settings == nil { 398 if settings = ctx.cache[unit]; settings == nil { 399 settings, err = ctx.ru.ReadSettings(unit) 400 if err != nil { 401 return nil, err 402 } 403 } 404 } 405 if member { 406 ctx.members[unit] = settings 407 } else { 408 ctx.cache[unit] = settings 409 } 410 return settings, nil 411 }