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