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