github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/resources_persistence.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "time" 8 9 "github.com/juju/errors" 10 jujutxn "github.com/juju/txn" 11 charmresource "gopkg.in/juju/charm.v6-unstable/resource" 12 "gopkg.in/juju/names.v2" 13 "gopkg.in/mgo.v2/txn" 14 15 "github.com/juju/juju/resource" 16 ) 17 18 const ( 19 // CleanupKindResourceBlob identifies the cleanup kind 20 // for resource blobs. 21 CleanupKindResourceBlob = "resourceBlob" 22 ) 23 24 // ResourcePersistenceBase exposes the core persistence functionality 25 // needed for resources. 26 type ResourcePersistenceBase interface { 27 // One populates doc with the document corresponding to the given 28 // ID. Missing documents result in errors.NotFound. 29 One(collName, id string, doc interface{}) error 30 31 // All populates docs with the list of the documents corresponding 32 // to the provided query. 33 All(collName string, query, docs interface{}) error 34 35 // Run runs the transaction generated by the provided factory 36 // function. It may be retried several times. 37 Run(transactions jujutxn.TransactionSource) error 38 39 // ApplicationExistsOps returns the operations that verify that the 40 // identified application exists. 41 ApplicationExistsOps(applicationID string) []txn.Op 42 43 // IncCharmModifiedVersionOps returns the operations necessary to increment 44 // the CharmModifiedVersion field for the given application. 45 IncCharmModifiedVersionOps(applicationID string) []txn.Op 46 47 // NewCleanupOp creates a mgo transaction operation that queues up 48 // some cleanup action in state. 49 NewCleanupOp(kind, prefix string) txn.Op 50 } 51 52 // ResourcePersistence provides the persistence functionality for the 53 // Juju environment as a whole. 54 type ResourcePersistence struct { 55 base ResourcePersistenceBase 56 } 57 58 // NewResourcePersistence wraps the base in a new ResourcePersistence. 59 func NewResourcePersistence(base ResourcePersistenceBase) *ResourcePersistence { 60 return &ResourcePersistence{ 61 base: base, 62 } 63 } 64 65 // ListResources returns the info for each non-pending resource of the 66 // identified service. 67 func (p ResourcePersistence) ListResources(applicationID string) (resource.ServiceResources, error) { 68 logger.Tracef("listing all resources for application %q", applicationID) 69 70 docs, err := p.resources(applicationID) 71 if err != nil { 72 return resource.ServiceResources{}, errors.Trace(err) 73 } 74 75 store := map[string]charmresource.Resource{} 76 units := map[names.UnitTag][]resource.Resource{} 77 downloadProgress := make(map[names.UnitTag]map[string]int64) 78 79 var results resource.ServiceResources 80 for _, doc := range docs { 81 if doc.PendingID != "" { 82 continue 83 } 84 85 res, err := doc2basicResource(doc) 86 if err != nil { 87 return resource.ServiceResources{}, errors.Trace(err) 88 } 89 if !doc.LastPolled.IsZero() { 90 store[res.Name] = res.Resource 91 continue 92 } 93 if doc.UnitID == "" { 94 results.Resources = append(results.Resources, res) 95 continue 96 } 97 tag := names.NewUnitTag(doc.UnitID) 98 if doc.PendingID == "" { 99 units[tag] = append(units[tag], res) 100 } 101 if doc.DownloadProgress != nil { 102 if downloadProgress[tag] == nil { 103 downloadProgress[tag] = make(map[string]int64) 104 } 105 downloadProgress[tag][doc.Name] = *doc.DownloadProgress 106 } 107 } 108 for _, res := range results.Resources { 109 storeRes := store[res.Name] 110 results.CharmStoreResources = append(results.CharmStoreResources, storeRes) 111 } 112 for tag, res := range units { 113 results.UnitResources = append(results.UnitResources, resource.UnitResources{ 114 Tag: tag, 115 Resources: res, 116 DownloadProgress: downloadProgress[tag], 117 }) 118 } 119 return results, nil 120 } 121 122 // ListPendingResources returns the extended, model-related info for 123 // each pending resource of the identifies service. 124 func (p ResourcePersistence) ListPendingResources(applicationID string) ([]resource.Resource, error) { 125 docs, err := p.resources(applicationID) 126 if err != nil { 127 return nil, errors.Trace(err) 128 } 129 130 var resources []resource.Resource 131 for _, doc := range docs { 132 if doc.PendingID == "" { 133 continue 134 } 135 // doc.UnitID will always be empty here. 136 137 res, err := doc2basicResource(doc) 138 if err != nil { 139 return nil, errors.Trace(err) 140 } 141 resources = append(resources, res) 142 } 143 return resources, nil 144 } 145 146 // GetResource returns the extended, model-related info for the non-pending 147 // resource. 148 func (p ResourcePersistence) GetResource(id string) (res resource.Resource, storagePath string, _ error) { 149 doc, err := p.getOne(id) 150 if err != nil { 151 return res, "", errors.Trace(err) 152 } 153 154 stored, err := doc2resource(doc) 155 if err != nil { 156 return res, "", errors.Trace(err) 157 } 158 159 return stored.Resource, stored.storagePath, nil 160 } 161 162 // StageResource adds the resource in a separate staging area 163 // if the resource isn't already staged. If it is then 164 // errors.AlreadyExists is returned. A wrapper around the staged 165 // resource is returned which supports both finalizing and removing 166 // the staged resource. 167 func (p ResourcePersistence) StageResource(res resource.Resource, storagePath string) (*StagedResource, error) { 168 if storagePath == "" { 169 return nil, errors.Errorf("missing storage path") 170 } 171 172 if err := res.Validate(); err != nil { 173 return nil, errors.Annotate(err, "bad resource") 174 } 175 176 stored := storedResource{ 177 Resource: res, 178 storagePath: storagePath, 179 } 180 staged := &StagedResource{ 181 base: p.base, 182 id: res.ID, 183 stored: stored, 184 } 185 if err := staged.stage(); err != nil { 186 return nil, errors.Trace(err) 187 } 188 return staged, nil 189 } 190 191 // SetResource sets the info for the resource. 192 func (p ResourcePersistence) SetResource(res resource.Resource) error { 193 stored, err := p.getStored(res) 194 if errors.IsNotFound(err) { 195 stored = storedResource{Resource: res} 196 } else if err != nil { 197 return errors.Trace(err) 198 } 199 // TODO(ericsnow) Ensure that stored.Resource matches res? If we do 200 // so then the following line is unnecessary. 201 stored.Resource = res 202 203 if err := res.Validate(); err != nil { 204 return errors.Annotate(err, "bad resource") 205 } 206 207 buildTxn := func(attempt int) ([]txn.Op, error) { 208 // This is an "upsert". 209 var ops []txn.Op 210 switch attempt { 211 case 0: 212 ops = newInsertResourceOps(stored) 213 case 1: 214 ops = newUpdateResourceOps(stored) 215 default: 216 // Either insert or update will work so we should not get here. 217 return nil, errors.New("setting the resource failed") 218 } 219 if stored.PendingID == "" { 220 // Only non-pending resources must have an existing service. 221 ops = append(ops, p.base.ApplicationExistsOps(res.ApplicationID)...) 222 } 223 return ops, nil 224 } 225 if err := p.base.Run(buildTxn); err != nil { 226 return errors.Trace(err) 227 } 228 return nil 229 } 230 231 // SetCharmStoreResource stores the resource info that was retrieved 232 // from the charm store. 233 func (p ResourcePersistence) SetCharmStoreResource(id, applicationID string, res charmresource.Resource, lastPolled time.Time) error { 234 if err := res.Validate(); err != nil { 235 return errors.Annotate(err, "bad resource") 236 } 237 238 csRes := charmStoreResource{ 239 Resource: res, 240 id: id, 241 applicationID: applicationID, 242 lastPolled: lastPolled, 243 } 244 245 buildTxn := func(attempt int) ([]txn.Op, error) { 246 // This is an "upsert". 247 var ops []txn.Op 248 switch attempt { 249 case 0: 250 ops = newInsertCharmStoreResourceOps(csRes) 251 case 1: 252 ops = newUpdateCharmStoreResourceOps(csRes) 253 default: 254 // Either insert or update will work so we should not get here. 255 return nil, errors.New("setting the resource failed") 256 } 257 // No pending resources so we always do this here. 258 ops = append(ops, p.base.ApplicationExistsOps(applicationID)...) 259 return ops, nil 260 } 261 if err := p.base.Run(buildTxn); err != nil { 262 return errors.Trace(err) 263 } 264 return nil 265 } 266 267 // SetUnitResource stores the resource info for a particular unit. The 268 // resource must already be set for the application. 269 func (p ResourcePersistence) SetUnitResource(unitID string, res resource.Resource) error { 270 if res.PendingID != "" { 271 return errors.Errorf("pending resources not allowed") 272 } 273 return p.setUnitResource(unitID, res, nil) 274 } 275 276 // SetUnitResource stores the resource info for a particular unit. The 277 // resource must already be set for the application. The provided progress 278 // is stored in the DB. 279 func (p ResourcePersistence) SetUnitResourceProgress(unitID string, res resource.Resource, progress int64) error { 280 if res.PendingID == "" { 281 return errors.Errorf("only pending resources may track progress") 282 } 283 return p.setUnitResource(unitID, res, &progress) 284 } 285 286 func (p ResourcePersistence) setUnitResource(unitID string, res resource.Resource, progress *int64) error { 287 stored, err := p.getStored(res) 288 if err != nil { 289 return errors.Trace(err) 290 } 291 // TODO(ericsnow) Ensure that stored.Resource matches res? If we do 292 // so then the following line is unnecessary. 293 stored.Resource = res 294 295 if err := res.Validate(); err != nil { 296 return errors.Annotate(err, "bad resource") 297 } 298 299 buildTxn := func(attempt int) ([]txn.Op, error) { 300 // This is an "upsert". 301 var ops []txn.Op 302 switch attempt { 303 case 0: 304 ops = newInsertUnitResourceOps(unitID, stored, progress) 305 case 1: 306 ops = newUpdateUnitResourceOps(unitID, stored, progress) 307 default: 308 // Either insert or update will work so we should not get here. 309 return nil, errors.New("setting the resource failed") 310 } 311 // No pending resources so we always do this here. 312 ops = append(ops, p.base.ApplicationExistsOps(res.ApplicationID)...) 313 return ops, nil 314 } 315 if err := p.base.Run(buildTxn); err != nil { 316 return errors.Trace(err) 317 } 318 return nil 319 } 320 321 func (p ResourcePersistence) getStored(res resource.Resource) (storedResource, error) { 322 doc, err := p.getOne(res.ID) 323 if errors.IsNotFound(err) { 324 err = errors.NotFoundf("resource %q", res.Name) 325 } 326 if err != nil { 327 return storedResource{}, errors.Trace(err) 328 } 329 330 stored, err := doc2resource(doc) 331 if err != nil { 332 return stored, errors.Trace(err) 333 } 334 335 return stored, nil 336 } 337 338 // NewResolvePendingResourceOps generates mongo transaction operations 339 // to set the identified resource as active. 340 // 341 // Leaking mongo details (transaction ops) is a necessary evil since we 342 // do not have any machinery to facilitate transactions between 343 // different components. 344 func (p ResourcePersistence) NewResolvePendingResourceOps(resID, pendingID string) ([]txn.Op, error) { 345 if pendingID == "" { 346 return nil, errors.New("missing pending ID") 347 } 348 349 oldDoc, err := p.getOnePending(resID, pendingID) 350 if errors.IsNotFound(err) { 351 return nil, errors.NotFoundf("pending resource %q (%s)", resID, pendingID) 352 } 353 if err != nil { 354 return nil, errors.Trace(err) 355 } 356 pending, err := doc2resource(oldDoc) 357 if err != nil { 358 return nil, errors.Trace(err) 359 } 360 361 exists := true 362 if _, err := p.getOne(resID); errors.IsNotFound(err) { 363 exists = false 364 } else if err != nil { 365 return nil, errors.Trace(err) 366 } 367 368 ops := newResolvePendingResourceOps(pending, exists) 369 return ops, nil 370 } 371 372 // NewRemoveUnitResourcesOps returns mgo transaction operations 373 // that remove resource information specific to the unit from state. 374 func (p ResourcePersistence) NewRemoveUnitResourcesOps(unitID string) ([]txn.Op, error) { 375 docs, err := p.unitResources(unitID) 376 if err != nil { 377 return nil, errors.Trace(err) 378 } 379 380 ops := newRemoveResourcesOps(docs) 381 // We do not remove the resource from the blob store here. That is 382 // an application-level matter. 383 return ops, nil 384 } 385 386 // NewRemoveResourcesOps returns mgo transaction operations that 387 // remove all the service's resources from state. 388 func (p ResourcePersistence) NewRemoveResourcesOps(applicationID string) ([]txn.Op, error) { 389 docs, err := p.resources(applicationID) 390 if err != nil { 391 return nil, errors.Trace(err) 392 } 393 394 ops := newRemoveResourcesOps(docs) 395 for _, doc := range docs { 396 ops = append(ops, p.base.NewCleanupOp(CleanupKindResourceBlob, doc.StoragePath)) 397 } 398 return ops, nil 399 }