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