github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/worker/uniter/runner/factory.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 "math/rand" 9 "time" 10 11 "github.com/juju/errors" 12 "github.com/juju/names" 13 "gopkg.in/juju/charm.v4" 14 "gopkg.in/juju/charm.v4/hooks" 15 16 "github.com/juju/juju/api/uniter" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/worker/uniter/hook" 19 ) 20 21 type CommandInfo struct { 22 // RelationId is the relation context to execute the commands in. 23 RelationId int 24 // RemoteUnitName is the remote unit for the relation context. 25 RemoteUnitName string 26 // ForceRemoteUnit skips unit inference and existence validation. 27 ForceRemoteUnit bool 28 } 29 30 // Factory represents a long-lived object that can create execution contexts 31 // relevant to a specific unit. In its current state, it is somewhat bizarre 32 // and inconsistent; its main value is as an evolutionary step towards a better 33 // division of responsibilities across worker/uniter and its subpackages. 34 type Factory interface { 35 36 // NewCommandRunner returns an execution context suitable for running 37 // an arbitrary script. 38 NewCommandRunner(commandInfo CommandInfo) (Runner, error) 39 40 // NewHookRunner returns an execution context suitable for running the 41 // supplied hook definition (which must be valid). 42 NewHookRunner(hookInfo hook.Info) (Runner, error) 43 44 // NewActionRunner returns an execution context suitable for running the 45 // action identified by the supplied id. 46 NewActionRunner(actionId string) (Runner, error) 47 } 48 49 // RelationsFunc is used to get snapshots of relation membership at context 50 // creation time. 51 type RelationsFunc func() map[int]*RelationInfo 52 53 // NewFactory returns a Factory capable of creating execution contexts backed 54 // by the supplied unit's supplied API connection. 55 func NewFactory( 56 state *uniter.State, 57 unitTag names.UnitTag, 58 getRelationInfos RelationsFunc, 59 paths Paths, 60 ) ( 61 Factory, error, 62 ) { 63 unit, err := state.Unit(unitTag) 64 if err != nil { 65 return nil, errors.Trace(err) 66 } 67 service, err := state.Service(unit.ServiceTag()) 68 if err != nil { 69 return nil, errors.Trace(err) 70 } 71 ownerTag, err := service.OwnerTag() 72 if err != nil { 73 return nil, errors.Trace(err) 74 } 75 machineTag, err := unit.AssignedMachine() 76 if err != nil { 77 return nil, errors.Trace(err) 78 } 79 environment, err := state.Environment() 80 if err != nil { 81 return nil, errors.Trace(err) 82 } 83 return &factory{ 84 unit: unit, 85 state: state, 86 paths: paths, 87 envUUID: environment.UUID(), 88 envName: environment.Name(), 89 machineTag: machineTag, 90 ownerTag: ownerTag, 91 getRelationInfos: getRelationInfos, 92 relationCaches: map[int]*RelationCache{}, 93 rand: rand.New(rand.NewSource(time.Now().Unix())), 94 }, nil 95 } 96 97 type factory struct { 98 // API connection fields; unit should be deprecated, but isn't yet. 99 unit *uniter.Unit 100 state *uniter.State 101 102 // Fields that shouldn't change in a factory's lifetime. 103 paths Paths 104 envUUID string 105 envName string 106 machineTag names.MachineTag 107 ownerTag names.UserTag 108 109 // Callback to get relation state snapshot. 110 getRelationInfos RelationsFunc 111 relationCaches map[int]*RelationCache 112 113 // For generating "unique" context ids. 114 rand *rand.Rand 115 } 116 117 // NewCommandRunner exists to satisfy the Factory interface. 118 func (f *factory) NewCommandRunner(commandInfo CommandInfo) (Runner, error) { 119 ctx, err := f.coreContext() 120 if err != nil { 121 return nil, errors.Trace(err) 122 } 123 relationId, remoteUnitName, err := inferRemoteUnit(ctx.relations, commandInfo) 124 if err != nil { 125 return nil, errors.Trace(err) 126 } 127 ctx.relationId = relationId 128 ctx.remoteUnitName = remoteUnitName 129 ctx.id = f.newId("run-commands") 130 runner := NewRunner(ctx, f.paths) 131 return runner, nil 132 } 133 134 // NewHookRunner exists to satisfy the Factory interface. 135 func (f *factory) NewHookRunner(hookInfo hook.Info) (Runner, error) { 136 if err := hookInfo.Validate(); err != nil { 137 return nil, errors.Trace(err) 138 } 139 140 ctx, err := f.coreContext() 141 if err != nil { 142 return nil, errors.Trace(err) 143 } 144 145 hookName := string(hookInfo.Kind) 146 if hookInfo.Kind.IsRelation() { 147 ctx.relationId = hookInfo.RelationId 148 ctx.remoteUnitName = hookInfo.RemoteUnit 149 relation, found := ctx.relations[hookInfo.RelationId] 150 if !found { 151 return nil, errors.Errorf("unknown relation id: %v", hookInfo.RelationId) 152 } 153 if hookInfo.Kind == hooks.RelationDeparted { 154 relation.cache.RemoveMember(hookInfo.RemoteUnit) 155 } else if hookInfo.RemoteUnit != "" { 156 // Clear remote settings cache for changing remote unit. 157 relation.cache.InvalidateMember(hookInfo.RemoteUnit) 158 } 159 hookName = fmt.Sprintf("%s-%s", relation.Name(), hookInfo.Kind) 160 } 161 if hookInfo.Kind.IsStorage() { 162 ctx.storageId = hookInfo.StorageId 163 if err := f.updateStorage(ctx); err != nil { 164 return nil, errors.Trace(err) 165 } 166 } 167 // Metrics are only sent from the collect-metrics hook. 168 if hookInfo.Kind == hooks.CollectMetrics { 169 ctx.canAddMetrics = true 170 ch, err := f.getCharm() 171 if err != nil { 172 return nil, errors.Trace(err) 173 } 174 ctx.definedMetrics = ch.Metrics() 175 } 176 ctx.id = f.newId(hookName) 177 runner := NewRunner(ctx, f.paths) 178 return runner, nil 179 } 180 181 // NewActionRunner exists to satisfy the Factory interface. 182 func (f *factory) NewActionRunner(actionId string) (Runner, error) { 183 ch, err := f.getCharm() 184 if err != nil { 185 return nil, errors.Trace(err) 186 } 187 188 ok := names.IsValidAction(actionId) 189 if !ok { 190 return nil, &badActionError{actionId, "not valid actionId"} 191 } 192 tag := names.NewActionTag(actionId) 193 action, err := f.state.Action(tag) 194 if params.IsCodeNotFoundOrCodeUnauthorized(err) { 195 return nil, ErrActionNotAvailable 196 } else if params.IsCodeActionNotAvailable(err) { 197 return nil, ErrActionNotAvailable 198 } else if err != nil { 199 return nil, errors.Trace(err) 200 } 201 202 err = f.state.ActionBegin(tag) 203 if err != nil { 204 return nil, errors.Trace(err) 205 } 206 207 name := action.Name() 208 spec, ok := ch.Actions().ActionSpecs[name] 209 if !ok { 210 return nil, &badActionError{name, "not defined"} 211 } 212 params := action.Params() 213 if err := spec.ValidateParams(params); err != nil { 214 return nil, &badActionError{name, err.Error()} 215 } 216 217 ctx, err := f.coreContext() 218 if err != nil { 219 return nil, errors.Trace(err) 220 } 221 ctx.actionData = newActionData(name, &tag, params) 222 ctx.id = f.newId(name) 223 runner := NewRunner(ctx, f.paths) 224 return runner, nil 225 } 226 227 // newId returns a probably-unique identifier for a new context, containing the 228 // supplied string. 229 func (f *factory) newId(name string) string { 230 return fmt.Sprintf("%s-%s-%d", f.unit.Name(), name, f.rand.Int63()) 231 } 232 233 // coreContext creates a new context with all unspecialised fields filled in. 234 func (f *factory) coreContext() (*HookContext, error) { 235 ctx := &HookContext{ 236 unit: f.unit, 237 state: f.state, 238 uuid: f.envUUID, 239 envName: f.envName, 240 unitName: f.unit.Name(), 241 assignedMachineTag: f.machineTag, 242 serviceOwner: f.ownerTag, 243 relations: f.getContextRelations(), 244 relationId: -1, 245 canAddMetrics: false, 246 definedMetrics: nil, 247 pendingPorts: make(map[PortRange]PortRangeInfo), 248 storageInstances: nil, 249 } 250 if err := f.updateContext(ctx); err != nil { 251 return nil, err 252 } 253 return ctx, nil 254 } 255 256 func (f *factory) getCharm() (charm.Charm, error) { 257 ch, err := charm.ReadCharm(f.paths.GetCharmDir()) 258 if err != nil { 259 return nil, err 260 } 261 return ch, nil 262 } 263 264 // getContextRelations updates the factory's relation caches, and uses them 265 // to construct contextRelations for a fresh context. 266 func (f *factory) getContextRelations() map[int]*ContextRelation { 267 contextRelations := map[int]*ContextRelation{} 268 relationInfos := f.getRelationInfos() 269 relationCaches := map[int]*RelationCache{} 270 for id, info := range relationInfos { 271 relationUnit := info.RelationUnit 272 memberNames := info.MemberNames 273 cache, found := f.relationCaches[id] 274 if found { 275 cache.Prune(memberNames) 276 } else { 277 cache = NewRelationCache(relationUnit.ReadSettings, memberNames) 278 } 279 relationCaches[id] = cache 280 contextRelations[id] = NewContextRelation(relationUnit, cache) 281 } 282 f.relationCaches = relationCaches 283 return contextRelations 284 } 285 286 // updateContext fills in all unspecialized fields that require an API call to 287 // discover. 288 // 289 // Approximately *every* line of code in this function represents a bug: ie, some 290 // piece of information we expose to the charm but which we fail to report changes 291 // to via hooks. Furthermore, the fact that we make multiple API calls at this 292 // time, rather than grabbing everything we need in one go, is unforgivably yucky. 293 func (f *factory) updateContext(ctx *HookContext) (err error) { 294 defer errors.Trace(err) 295 296 ctx.apiAddrs, err = f.state.APIAddresses() 297 if err != nil { 298 return err 299 } 300 ctx.machinePorts, err = f.state.AllMachinePorts(f.machineTag) 301 if err != nil { 302 return errors.Trace(err) 303 } 304 305 statusCode, statusInfo, err := f.unit.MeterStatus() 306 if err != nil { 307 return errors.Annotate(err, "could not retrieve meter status for unit") 308 } 309 ctx.meterStatus = &meterStatus{ 310 code: statusCode, 311 info: statusInfo, 312 } 313 314 // TODO(fwereade) 23-10-2014 bug 1384572 315 // Nothing here should ever be getting the environ config directly. 316 environConfig, err := f.state.EnvironConfig() 317 if err != nil { 318 return err 319 } 320 ctx.proxySettings = environConfig.ProxySettings() 321 322 // Calling these last, because there's a potential race: they're not guaranteed 323 // to be set in time to be needed for a hook. If they're not, we just leave them 324 // unset as we always have; this isn't great but it's about behaviour preservation. 325 ctx.publicAddress, err = f.unit.PublicAddress() 326 if err != nil && !params.IsCodeNoAddressSet(err) { 327 return err 328 } 329 ctx.privateAddress, err = f.unit.PrivateAddress() 330 if err != nil && !params.IsCodeNoAddressSet(err) { 331 return err 332 } 333 return nil 334 } 335 336 func (f *factory) updateStorage(ctx *HookContext) (err error) { 337 ctx.storageInstances, err = f.state.StorageInstances(f.unit.Tag()) 338 return err 339 } 340 341 func inferRemoteUnit(rctxs map[int]*ContextRelation, info CommandInfo) (int, string, error) { 342 relationId := info.RelationId 343 hasRelation := relationId != -1 344 remoteUnit := info.RemoteUnitName 345 hasRemoteUnit := remoteUnit != "" 346 347 // Check baseline sanity of remote unit, if supplied. 348 if hasRemoteUnit { 349 if !names.IsValidUnit(remoteUnit) { 350 return -1, "", errors.Errorf(`invalid remote unit: %s`, remoteUnit) 351 } else if !hasRelation { 352 return -1, "", errors.Errorf("remote unit provided without a relation: %s", remoteUnit) 353 } 354 } 355 356 // Check sanity of relation, if supplied, otherwise easy early return. 357 if !hasRelation { 358 return relationId, remoteUnit, nil 359 } 360 rctx, found := rctxs[relationId] 361 if !found { 362 return -1, "", errors.Errorf("unknown relation id: %d", relationId) 363 } 364 365 // Past basic sanity checks; if forced, accept what we're given. 366 if info.ForceRemoteUnit { 367 return relationId, remoteUnit, nil 368 } 369 370 // Infer an appropriate remote unit if we can. 371 possibles := rctx.UnitNames() 372 if remoteUnit == "" { 373 switch len(possibles) { 374 case 0: 375 return -1, "", errors.Errorf("cannot infer remote unit in empty relation %d", relationId) 376 case 1: 377 return relationId, possibles[0], nil 378 } 379 return -1, "", errors.Errorf("ambiguous remote unit; possibilities are %+v", possibles) 380 } 381 for _, possible := range possibles { 382 if remoteUnit == possible { 383 return relationId, remoteUnit, nil 384 } 385 } 386 return -1, "", errors.Errorf("unknown remote unit %s; possibilities are %+v", remoteUnit, possibles) 387 }