github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 "gopkg.in/juju/charm.v6/hooks" 13 "gopkg.in/juju/names.v2" 14 15 "github.com/juju/juju/api/uniter" 16 "github.com/juju/juju/apiserver/params" 17 "github.com/juju/juju/core/leadership" 18 "github.com/juju/juju/core/model" 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 hooks.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 hooks.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 modelName string 73 modelType model.ModelType 74 machineTag names.MachineTag 75 storage StorageContextAccessor 76 clock Clock 77 zone string 78 principal string 79 80 // Callback to get relation state snapshot. 81 getRelationInfos RelationsFunc 82 relationCaches map[int]*RelationCache 83 84 // For generating "unique" context ids. 85 rand *rand.Rand 86 } 87 88 // FactoryConfig contains configuration values 89 // for the context factory. 90 type FactoryConfig struct { 91 State *uniter.State 92 UnitTag names.UnitTag 93 Tracker leadership.Tracker 94 GetRelationInfos RelationsFunc 95 Storage StorageContextAccessor 96 Paths Paths 97 Clock Clock 98 } 99 100 // NewContextFactory returns a ContextFactory capable of creating execution contexts backed 101 // by the supplied unit's supplied API connection. 102 func NewContextFactory(config FactoryConfig) (ContextFactory, error) { 103 unit, err := config.State.Unit(config.UnitTag) 104 if err != nil { 105 return nil, errors.Trace(err) 106 } 107 m, err := config.State.Model() 108 if err != nil { 109 return nil, errors.Trace(err) 110 } 111 var ( 112 machineTag names.MachineTag 113 zone string 114 ) 115 if m.ModelType == model.IAAS { 116 machineTag, err = unit.AssignedMachine() 117 if err != nil { 118 return nil, errors.Trace(err) 119 } 120 121 zone, err = unit.AvailabilityZone() 122 if err != nil { 123 return nil, errors.Trace(err) 124 } 125 } 126 principal, ok, err := unit.PrincipalName() 127 if err != nil { 128 return nil, errors.Trace(err) 129 } else if !ok { 130 principal = "" 131 } 132 133 f := &contextFactory{ 134 unit: unit, 135 state: config.State, 136 tracker: config.Tracker, 137 paths: config.Paths, 138 modelUUID: m.UUID, 139 modelName: m.Name, 140 machineTag: machineTag, 141 getRelationInfos: config.GetRelationInfos, 142 relationCaches: map[int]*RelationCache{}, 143 storage: config.Storage, 144 rand: rand.New(rand.NewSource(time.Now().Unix())), 145 clock: config.Clock, 146 zone: zone, 147 principal: principal, 148 modelType: m.ModelType, 149 } 150 return f, nil 151 } 152 153 // newId returns a probably-unique identifier for a new context, containing the 154 // supplied string. 155 func (f *contextFactory) newId(name string) string { 156 return fmt.Sprintf("%s-%s-%d", f.unit.Name(), name, f.rand.Int63()) 157 } 158 159 // coreContext creates a new context with all unspecialised fields filled in. 160 func (f *contextFactory) coreContext() (*HookContext, error) { 161 leadershipContext := newLeadershipContext( 162 f.state.LeadershipSettings, 163 f.tracker, 164 f.unit.Name(), 165 ) 166 ctx := &HookContext{ 167 unit: f.unit, 168 state: f.state, 169 LeadershipContext: leadershipContext, 170 uuid: f.modelUUID, 171 modelName: f.modelName, 172 unitName: f.unit.Name(), 173 assignedMachineTag: f.machineTag, 174 relations: f.getContextRelations(), 175 relationId: -1, 176 pendingPorts: make(map[PortRange]PortRangeInfo), 177 storage: f.storage, 178 clock: f.clock, 179 componentDir: f.paths.ComponentDir, 180 componentFuncs: registeredComponentFuncs, 181 availabilityzone: f.zone, 182 principal: f.principal, 183 } 184 if err := f.updateContext(ctx); err != nil { 185 return nil, err 186 } 187 return ctx, nil 188 } 189 190 // ActionContext is part of the ContextFactory interface. 191 func (f *contextFactory) ActionContext(actionData *ActionData) (*HookContext, error) { 192 if actionData == nil { 193 return nil, errors.New("nil actionData specified") 194 } 195 ctx, err := f.coreContext() 196 if err != nil { 197 return nil, errors.Trace(err) 198 } 199 ctx.actionData = actionData 200 ctx.id = f.newId(actionData.Name) 201 return ctx, nil 202 } 203 204 // HookContext is part of the ContextFactory interface. 205 func (f *contextFactory) HookContext(hookInfo hook.Info) (*HookContext, error) { 206 ctx, err := f.coreContext() 207 if err != nil { 208 return nil, errors.Trace(err) 209 } 210 hookName := string(hookInfo.Kind) 211 if hookInfo.Kind.IsRelation() { 212 ctx.relationId = hookInfo.RelationId 213 ctx.remoteUnitName = hookInfo.RemoteUnit 214 relation, found := ctx.relations[hookInfo.RelationId] 215 if !found { 216 return nil, errors.Errorf("unknown relation id: %v", hookInfo.RelationId) 217 } 218 if hookInfo.Kind == hooks.RelationDeparted { 219 relation.cache.RemoveMember(hookInfo.RemoteUnit) 220 } else if hookInfo.RemoteUnit != "" { 221 // Clear remote settings cache for changing remote unit. 222 relation.cache.InvalidateMember(hookInfo.RemoteUnit) 223 } 224 hookName = fmt.Sprintf("%s-%s", relation.Name(), hookInfo.Kind) 225 } 226 if hookInfo.Kind.IsStorage() { 227 ctx.storageTag = names.NewStorageTag(hookInfo.StorageId) 228 if _, err := ctx.storage.Storage(ctx.storageTag); err != nil { 229 return nil, errors.Annotatef(err, "could not retrieve storage for id: %v", hookInfo.StorageId) 230 } 231 storageName, err := names.StorageName(hookInfo.StorageId) 232 if err != nil { 233 return nil, errors.Trace(err) 234 } 235 hookName = fmt.Sprintf("%s-%s", storageName, hookName) 236 } 237 ctx.id = f.newId(hookName) 238 return ctx, nil 239 } 240 241 // CommandContext is part of the ContextFactory interface. 242 func (f *contextFactory) CommandContext(commandInfo CommandInfo) (*HookContext, error) { 243 ctx, err := f.coreContext() 244 if err != nil { 245 return nil, errors.Trace(err) 246 } 247 relationId, remoteUnitName, err := inferRemoteUnit(ctx.relations, commandInfo) 248 if err != nil { 249 return nil, errors.Trace(err) 250 } 251 ctx.relationId = relationId 252 ctx.remoteUnitName = remoteUnitName 253 ctx.id = f.newId("run-commands") 254 return ctx, nil 255 } 256 257 // getContextRelations updates the factory's relation caches, and uses them 258 // to construct ContextRelations for a fresh context. 259 func (f *contextFactory) getContextRelations() map[int]*ContextRelation { 260 contextRelations := map[int]*ContextRelation{} 261 relationInfos := f.getRelationInfos() 262 relationCaches := map[int]*RelationCache{} 263 for id, info := range relationInfos { 264 relationUnit := info.RelationUnit 265 memberNames := info.MemberNames 266 cache, found := f.relationCaches[id] 267 if found { 268 cache.Prune(memberNames) 269 } else { 270 cache = NewRelationCache(relationUnit.ReadSettings, memberNames) 271 } 272 relationCaches[id] = cache 273 contextRelations[id] = NewContextRelation(relationUnit, cache) 274 } 275 f.relationCaches = relationCaches 276 return contextRelations 277 } 278 279 // updateContext fills in all unspecialized fields that require an API call to 280 // discover. 281 // 282 // Approximately *every* line of code in this function represents a bug: ie, some 283 // piece of information we expose to the charm but which we fail to report changes 284 // to via hooks. Furthermore, the fact that we make multiple API calls at this 285 // time, rather than grabbing everything we need in one go, is unforgivably yucky. 286 func (f *contextFactory) updateContext(ctx *HookContext) (err error) { 287 defer errors.Trace(err) 288 289 ctx.apiAddrs, err = f.state.APIAddresses() 290 if err != nil { 291 return err 292 } 293 294 sla, err := f.state.SLALevel() 295 if err != nil { 296 return errors.Annotate(err, "could not retrieve the SLA level") 297 } 298 ctx.slaLevel = sla 299 300 // TODO(fwereade) 23-10-2014 bug 1384572 301 // Nothing here should ever be getting the environ config directly. 302 modelConfig, err := f.state.ModelConfig() 303 if err != nil { 304 return err 305 } 306 ctx.legacyProxySettings = modelConfig.LegacyProxySettings() 307 ctx.jujuProxySettings = modelConfig.JujuProxySettings() 308 309 statusCode, statusInfo, err := f.unit.MeterStatus() 310 if err != nil { 311 return errors.Annotate(err, "could not retrieve meter status for unit") 312 } 313 ctx.meterStatus = &meterStatus{ 314 code: statusCode, 315 info: statusInfo, 316 } 317 318 if f.modelType == model.IAAS { 319 ctx.machinePorts, err = f.state.AllMachinePorts(f.machineTag) 320 if err != nil { 321 return errors.Trace(err) 322 } 323 324 // Calling these last, because there's a potential race: they're not guaranteed 325 // to be set in time to be needed for a hook. If they're not, we just leave them 326 // unset as we always have; this isn't great but it's about behaviour preservation. 327 ctx.publicAddress, err = f.unit.PublicAddress() 328 if err != nil && !params.IsCodeNoAddressSet(err) { 329 logger.Warningf("cannot get legacy public address for %v: %v", f.unit.Name(), err) 330 } 331 ctx.privateAddress, err = f.unit.PrivateAddress() 332 if err != nil && !params.IsCodeNoAddressSet(err) { 333 logger.Warningf("cannot get legacy private address for %v: %v", f.unit.Name(), err) 334 } 335 } 336 return nil 337 } 338 339 func inferRemoteUnit(rctxs map[int]*ContextRelation, info CommandInfo) (int, string, error) { 340 relationId := info.RelationId 341 hasRelation := relationId != -1 342 remoteUnit := info.RemoteUnitName 343 hasRemoteUnit := remoteUnit != "" 344 345 // Check baseline sanity of remote unit, if supplied. 346 if hasRemoteUnit { 347 if !names.IsValidUnit(remoteUnit) { 348 return -1, "", errors.Errorf(`invalid remote unit: %s`, remoteUnit) 349 } else if !hasRelation { 350 return -1, "", errors.Errorf("remote unit provided without a relation: %s", remoteUnit) 351 } 352 } 353 354 // Check sanity of relation, if supplied, otherwise easy early return. 355 if !hasRelation { 356 return relationId, remoteUnit, nil 357 } 358 rctx, found := rctxs[relationId] 359 if !found { 360 return -1, "", errors.Errorf("unknown relation id: %d", relationId) 361 } 362 363 // Past basic sanity checks; if forced, accept what we're given. 364 if info.ForceRemoteUnit { 365 return relationId, remoteUnit, nil 366 } 367 368 // Infer an appropriate remote unit if we can. 369 possibles := rctx.UnitNames() 370 if remoteUnit == "" { 371 switch len(possibles) { 372 case 0: 373 return -1, "", errors.Errorf("cannot infer remote unit in empty relation %d", relationId) 374 case 1: 375 return relationId, possibles[0], nil 376 } 377 return -1, "", errors.Errorf("ambiguous remote unit; possibilities are %+v", possibles) 378 } 379 for _, possible := range possibles { 380 if remoteUnit == possible { 381 return relationId, remoteUnit, nil 382 } 383 } 384 return -1, "", errors.Errorf("unknown remote unit %s; possibilities are %+v", remoteUnit, possibles) 385 }