github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/uniter/runner/context/contextfactory.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package context 5 6 import ( 7 "fmt" 8 "math/rand" 9 "time" 10 11 "github.com/juju/errors" 12 "github.com/juju/utils/clock" 13 "gopkg.in/juju/charm.v6-unstable/hooks" 14 "gopkg.in/juju/names.v2" 15 16 "github.com/juju/juju/api/uniter" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/core/leadership" 19 "github.com/juju/juju/worker/uniter/hook" 20 "github.com/juju/juju/worker/uniter/runner/jujuc" 21 ) 22 23 // CommandInfo specifies the information necessary to run a command. 24 type CommandInfo struct { 25 // RelationId is the relation context to execute the commands in. 26 RelationId int 27 // RemoteUnitName is the remote unit for the relation context. 28 RemoteUnitName string 29 // ForceRemoteUnit skips unit inference and existence validation. 30 ForceRemoteUnit bool 31 } 32 33 // ContextFactory represents a long-lived object that can create execution contexts 34 // relevant to a specific unit. 35 type ContextFactory interface { 36 // CommandContext creates a new context for running a juju command. 37 CommandContext(commandInfo CommandInfo) (*HookContext, error) 38 39 // HookContext creates a new context for running a juju hook. 40 HookContext(hookInfo hook.Info) (*HookContext, error) 41 42 // ActionContext creates a new context for running a juju action. 43 ActionContext(actionData *ActionData) (*HookContext, error) 44 } 45 46 // StorageContextAccessor is an interface providing access to StorageContexts 47 // for a jujuc.Context. 48 type StorageContextAccessor interface { 49 50 // StorageTags returns the tags of storage instances attached to 51 // the unit. 52 StorageTags() ([]names.StorageTag, error) 53 54 // Storage returns the jujuc.ContextStorageAttachment with the 55 // supplied tag if it was found, and whether it was found. 56 Storage(names.StorageTag) (jujuc.ContextStorageAttachment, error) 57 } 58 59 // RelationsFunc is used to get snapshots of relation membership at context 60 // creation time. 61 type RelationsFunc func() map[int]*RelationInfo 62 63 type contextFactory struct { 64 // API connection fields; unit should be deprecated, but isn't yet. 65 unit *uniter.Unit 66 state *uniter.State 67 tracker leadership.Tracker 68 69 // Fields that shouldn't change in a factory's lifetime. 70 paths Paths 71 modelUUID string 72 envName string 73 machineTag names.MachineTag 74 storage StorageContextAccessor 75 clock clock.Clock 76 zone string 77 78 // Callback to get relation state snapshot. 79 getRelationInfos RelationsFunc 80 relationCaches map[int]*RelationCache 81 82 // For generating "unique" context ids. 83 rand *rand.Rand 84 } 85 86 // NewContextFactory returns a ContextFactory capable of creating execution contexts backed 87 // by the supplied unit's supplied API connection. 88 func NewContextFactory( 89 state *uniter.State, 90 unitTag names.UnitTag, 91 tracker leadership.Tracker, 92 getRelationInfos RelationsFunc, 93 storage StorageContextAccessor, 94 paths Paths, 95 clock clock.Clock, 96 ) ( 97 ContextFactory, error, 98 ) { 99 unit, err := state.Unit(unitTag) 100 if err != nil { 101 return nil, errors.Trace(err) 102 } 103 machineTag, err := unit.AssignedMachine() 104 if err != nil { 105 return nil, errors.Trace(err) 106 } 107 model, err := state.Model() 108 if err != nil { 109 return nil, errors.Trace(err) 110 } 111 112 zone, err := unit.AvailabilityZone() 113 if err != nil { 114 return nil, errors.Trace(err) 115 } 116 f := &contextFactory{ 117 unit: unit, 118 state: state, 119 tracker: tracker, 120 paths: paths, 121 modelUUID: model.UUID(), 122 envName: model.Name(), 123 machineTag: machineTag, 124 getRelationInfos: getRelationInfos, 125 relationCaches: map[int]*RelationCache{}, 126 storage: storage, 127 rand: rand.New(rand.NewSource(time.Now().Unix())), 128 clock: clock, 129 zone: zone, 130 } 131 return f, nil 132 } 133 134 // newId returns a probably-unique identifier for a new context, containing the 135 // supplied string. 136 func (f *contextFactory) newId(name string) string { 137 return fmt.Sprintf("%s-%s-%d", f.unit.Name(), name, f.rand.Int63()) 138 } 139 140 // coreContext creates a new context with all unspecialised fields filled in. 141 func (f *contextFactory) coreContext() (*HookContext, error) { 142 leadershipContext := newLeadershipContext( 143 f.state.LeadershipSettings, 144 f.tracker, 145 ) 146 ctx := &HookContext{ 147 unit: f.unit, 148 state: f.state, 149 LeadershipContext: leadershipContext, 150 uuid: f.modelUUID, 151 envName: f.envName, 152 unitName: f.unit.Name(), 153 assignedMachineTag: f.machineTag, 154 relations: f.getContextRelations(), 155 relationId: -1, 156 pendingPorts: make(map[PortRange]PortRangeInfo), 157 storage: f.storage, 158 clock: f.clock, 159 componentDir: f.paths.ComponentDir, 160 componentFuncs: registeredComponentFuncs, 161 availabilityzone: f.zone, 162 } 163 if err := f.updateContext(ctx); err != nil { 164 return nil, err 165 } 166 return ctx, nil 167 } 168 169 // ActionContext is part of the ContextFactory interface. 170 func (f *contextFactory) ActionContext(actionData *ActionData) (*HookContext, error) { 171 if actionData == nil { 172 return nil, errors.New("nil actionData specified") 173 } 174 ctx, err := f.coreContext() 175 if err != nil { 176 return nil, errors.Trace(err) 177 } 178 ctx.actionData = actionData 179 ctx.id = f.newId(actionData.Name) 180 return ctx, nil 181 } 182 183 // HookContext is part of the ContextFactory interface. 184 func (f *contextFactory) HookContext(hookInfo hook.Info) (*HookContext, error) { 185 ctx, err := f.coreContext() 186 if err != nil { 187 return nil, errors.Trace(err) 188 } 189 hookName := string(hookInfo.Kind) 190 if hookInfo.Kind.IsRelation() { 191 ctx.relationId = hookInfo.RelationId 192 ctx.remoteUnitName = hookInfo.RemoteUnit 193 relation, found := ctx.relations[hookInfo.RelationId] 194 if !found { 195 return nil, errors.Errorf("unknown relation id: %v", hookInfo.RelationId) 196 } 197 if hookInfo.Kind == hooks.RelationDeparted { 198 relation.cache.RemoveMember(hookInfo.RemoteUnit) 199 } else if hookInfo.RemoteUnit != "" { 200 // Clear remote settings cache for changing remote unit. 201 relation.cache.InvalidateMember(hookInfo.RemoteUnit) 202 } 203 hookName = fmt.Sprintf("%s-%s", relation.Name(), hookInfo.Kind) 204 } 205 if hookInfo.Kind.IsStorage() { 206 ctx.storageTag = names.NewStorageTag(hookInfo.StorageId) 207 if _, err := ctx.storage.Storage(ctx.storageTag); err != nil { 208 return nil, errors.Annotatef(err, "could not retrieve storage for id: %v", hookInfo.StorageId) 209 } 210 storageName, err := names.StorageName(hookInfo.StorageId) 211 if err != nil { 212 return nil, errors.Trace(err) 213 } 214 hookName = fmt.Sprintf("%s-%s", storageName, hookName) 215 } 216 ctx.id = f.newId(hookName) 217 return ctx, nil 218 } 219 220 // CommandContext is part of the ContextFactory interface. 221 func (f *contextFactory) CommandContext(commandInfo CommandInfo) (*HookContext, error) { 222 ctx, err := f.coreContext() 223 if err != nil { 224 return nil, errors.Trace(err) 225 } 226 relationId, remoteUnitName, err := inferRemoteUnit(ctx.relations, commandInfo) 227 if err != nil { 228 return nil, errors.Trace(err) 229 } 230 ctx.relationId = relationId 231 ctx.remoteUnitName = remoteUnitName 232 ctx.id = f.newId("run-commands") 233 return ctx, nil 234 } 235 236 // getContextRelations updates the factory's relation caches, and uses them 237 // to construct ContextRelations for a fresh context. 238 func (f *contextFactory) getContextRelations() map[int]*ContextRelation { 239 contextRelations := map[int]*ContextRelation{} 240 relationInfos := f.getRelationInfos() 241 relationCaches := map[int]*RelationCache{} 242 for id, info := range relationInfos { 243 relationUnit := info.RelationUnit 244 memberNames := info.MemberNames 245 cache, found := f.relationCaches[id] 246 if found { 247 cache.Prune(memberNames) 248 } else { 249 cache = NewRelationCache(relationUnit.ReadSettings, memberNames) 250 } 251 relationCaches[id] = cache 252 contextRelations[id] = NewContextRelation(relationUnit, cache) 253 } 254 f.relationCaches = relationCaches 255 return contextRelations 256 } 257 258 // updateContext fills in all unspecialized fields that require an API call to 259 // discover. 260 // 261 // Approximately *every* line of code in this function represents a bug: ie, some 262 // piece of information we expose to the charm but which we fail to report changes 263 // to via hooks. Furthermore, the fact that we make multiple API calls at this 264 // time, rather than grabbing everything we need in one go, is unforgivably yucky. 265 func (f *contextFactory) updateContext(ctx *HookContext) (err error) { 266 defer errors.Trace(err) 267 268 ctx.apiAddrs, err = f.state.APIAddresses() 269 if err != nil { 270 return err 271 } 272 ctx.machinePorts, err = f.state.AllMachinePorts(f.machineTag) 273 if err != nil { 274 return errors.Trace(err) 275 } 276 277 statusCode, statusInfo, err := f.unit.MeterStatus() 278 if err != nil { 279 return errors.Annotate(err, "could not retrieve meter status for unit") 280 } 281 ctx.meterStatus = &meterStatus{ 282 code: statusCode, 283 info: statusInfo, 284 } 285 286 // TODO(fwereade) 23-10-2014 bug 1384572 287 // Nothing here should ever be getting the environ config directly. 288 modelConfig, err := f.state.ModelConfig() 289 if err != nil { 290 return err 291 } 292 ctx.proxySettings = modelConfig.ProxySettings() 293 294 // Calling these last, because there's a potential race: they're not guaranteed 295 // to be set in time to be needed for a hook. If they're not, we just leave them 296 // unset as we always have; this isn't great but it's about behaviour preservation. 297 ctx.publicAddress, err = f.unit.PublicAddress() 298 if err != nil && !params.IsCodeNoAddressSet(err) { 299 return err 300 } 301 ctx.privateAddress, err = f.unit.PrivateAddress() 302 if err != nil && !params.IsCodeNoAddressSet(err) { 303 return err 304 } 305 return nil 306 } 307 308 func inferRemoteUnit(rctxs map[int]*ContextRelation, info CommandInfo) (int, string, error) { 309 relationId := info.RelationId 310 hasRelation := relationId != -1 311 remoteUnit := info.RemoteUnitName 312 hasRemoteUnit := remoteUnit != "" 313 314 // Check baseline sanity of remote unit, if supplied. 315 if hasRemoteUnit { 316 if !names.IsValidUnit(remoteUnit) { 317 return -1, "", errors.Errorf(`invalid remote unit: %s`, remoteUnit) 318 } else if !hasRelation { 319 return -1, "", errors.Errorf("remote unit provided without a relation: %s", remoteUnit) 320 } 321 } 322 323 // Check sanity of relation, if supplied, otherwise easy early return. 324 if !hasRelation { 325 return relationId, remoteUnit, nil 326 } 327 rctx, found := rctxs[relationId] 328 if !found { 329 return -1, "", errors.Errorf("unknown relation id: %d", relationId) 330 } 331 332 // Past basic sanity checks; if forced, accept what we're given. 333 if info.ForceRemoteUnit { 334 return relationId, remoteUnit, nil 335 } 336 337 // Infer an appropriate remote unit if we can. 338 possibles := rctx.UnitNames() 339 if remoteUnit == "" { 340 switch len(possibles) { 341 case 0: 342 return -1, "", errors.Errorf("cannot infer remote unit in empty relation %d", relationId) 343 case 1: 344 return relationId, possibles[0], nil 345 } 346 return -1, "", errors.Errorf("ambiguous remote unit; possibilities are %+v", possibles) 347 } 348 for _, possible := range possibles { 349 if remoteUnit == possible { 350 return relationId, remoteUnit, nil 351 } 352 } 353 return -1, "", errors.Errorf("unknown remote unit %s; possibilities are %+v", remoteUnit, possibles) 354 }