github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/worker/storageprovisioner/filesystem_ops.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package storageprovisioner 5 6 import ( 7 "path/filepath" 8 9 "github.com/juju/errors" 10 "gopkg.in/juju/names.v2" 11 12 "github.com/juju/juju/apiserver/params" 13 "github.com/juju/juju/status" 14 "github.com/juju/juju/storage" 15 ) 16 17 // createFilesystems creates filesystems with the specified parameters. 18 func createFilesystems(ctx *context, ops map[names.FilesystemTag]*createFilesystemOp) error { 19 filesystemParams := make([]storage.FilesystemParams, 0, len(ops)) 20 for _, op := range ops { 21 filesystemParams = append(filesystemParams, op.args) 22 } 23 paramsBySource, filesystemSources, err := filesystemParamsBySource( 24 ctx.config.StorageDir, 25 filesystemParams, 26 ctx.managedFilesystemSource, 27 ctx.config.Registry, 28 ) 29 if err != nil { 30 return errors.Trace(err) 31 } 32 var reschedule []scheduleOp 33 var filesystems []storage.Filesystem 34 var statuses []params.EntityStatusArgs 35 for sourceName, filesystemParams := range paramsBySource { 36 logger.Debugf("creating filesystems: %v", filesystemParams) 37 filesystemSource := filesystemSources[sourceName] 38 validFilesystemParams, validationErrors := validateFilesystemParams( 39 filesystemSource, filesystemParams, 40 ) 41 for i, err := range validationErrors { 42 if err == nil { 43 continue 44 } 45 statuses = append(statuses, params.EntityStatusArgs{ 46 Tag: filesystemParams[i].Tag.String(), 47 Status: status.Error.String(), 48 Info: err.Error(), 49 }) 50 logger.Debugf( 51 "failed to validate parameters for %s: %v", 52 names.ReadableString(filesystemParams[i].Tag), err, 53 ) 54 } 55 filesystemParams = validFilesystemParams 56 if len(filesystemParams) == 0 { 57 continue 58 } 59 results, err := filesystemSource.CreateFilesystems(filesystemParams) 60 if err != nil { 61 return errors.Annotatef(err, "creating filesystems from source %q", sourceName) 62 } 63 for i, result := range results { 64 statuses = append(statuses, params.EntityStatusArgs{ 65 Tag: filesystemParams[i].Tag.String(), 66 Status: status.Attaching.String(), 67 }) 68 entityStatus := &statuses[len(statuses)-1] 69 if result.Error != nil { 70 // Reschedule the filesystem creation. 71 reschedule = append(reschedule, ops[filesystemParams[i].Tag]) 72 73 // Note: we keep the status as "pending" to indicate 74 // that we will retry. When we distinguish between 75 // transient and permanent errors, we will set the 76 // status to "error" for permanent errors. 77 entityStatus.Status = status.Pending.String() 78 entityStatus.Info = result.Error.Error() 79 logger.Debugf( 80 "failed to create %s: %v", 81 names.ReadableString(filesystemParams[i].Tag), 82 result.Error, 83 ) 84 continue 85 } 86 filesystems = append(filesystems, *result.Filesystem) 87 } 88 } 89 scheduleOperations(ctx, reschedule...) 90 setStatus(ctx, statuses) 91 if len(filesystems) == 0 { 92 return nil 93 } 94 // TODO(axw) we need to be able to list filesystems in the provider, 95 // by environment, so that we can "harvest" them if they're 96 // unknown. This will take care of killing filesystems that we fail 97 // to record in state. 98 errorResults, err := ctx.config.Filesystems.SetFilesystemInfo(filesystemsFromStorage(filesystems)) 99 if err != nil { 100 return errors.Annotate(err, "publishing filesystems to state") 101 } 102 for i, result := range errorResults { 103 if result.Error != nil { 104 logger.Errorf( 105 "publishing filesystem %s to state: %v", 106 filesystems[i].Tag.Id(), 107 result.Error, 108 ) 109 } 110 } 111 for _, v := range filesystems { 112 updateFilesystem(ctx, v) 113 } 114 return nil 115 } 116 117 // attachFilesystems creates filesystem attachments with the specified parameters. 118 func attachFilesystems(ctx *context, ops map[params.MachineStorageId]*attachFilesystemOp) error { 119 filesystemAttachmentParams := make([]storage.FilesystemAttachmentParams, 0, len(ops)) 120 for _, op := range ops { 121 args := op.args 122 if args.Path == "" { 123 args.Path = filepath.Join(ctx.config.StorageDir, args.Filesystem.Id()) 124 } 125 filesystemAttachmentParams = append(filesystemAttachmentParams, args) 126 } 127 paramsBySource, filesystemSources, err := filesystemAttachmentParamsBySource( 128 ctx.config.StorageDir, 129 filesystemAttachmentParams, 130 ctx.filesystems, 131 ctx.managedFilesystemSource, 132 ctx.config.Registry, 133 ) 134 if err != nil { 135 return errors.Trace(err) 136 } 137 var reschedule []scheduleOp 138 var filesystemAttachments []storage.FilesystemAttachment 139 var statuses []params.EntityStatusArgs 140 for sourceName, filesystemAttachmentParams := range paramsBySource { 141 logger.Debugf("attaching filesystems: %+v", filesystemAttachmentParams) 142 filesystemSource := filesystemSources[sourceName] 143 results, err := filesystemSource.AttachFilesystems(filesystemAttachmentParams) 144 if err != nil { 145 return errors.Annotatef(err, "attaching filesystems from source %q", sourceName) 146 } 147 for i, result := range results { 148 p := filesystemAttachmentParams[i] 149 statuses = append(statuses, params.EntityStatusArgs{ 150 Tag: p.Filesystem.String(), 151 Status: status.Attached.String(), 152 }) 153 entityStatus := &statuses[len(statuses)-1] 154 if result.Error != nil { 155 // Reschedule the filesystem attachment. 156 id := params.MachineStorageId{ 157 MachineTag: p.Machine.String(), 158 AttachmentTag: p.Filesystem.String(), 159 } 160 reschedule = append(reschedule, ops[id]) 161 162 // Note: we keep the status as "attaching" to 163 // indicate that we will retry. When we distinguish 164 // between transient and permanent errors, we will 165 // set the status to "error" for permanent errors. 166 entityStatus.Status = status.Attaching.String() 167 entityStatus.Info = result.Error.Error() 168 logger.Debugf( 169 "failed to attach %s to %s: %v", 170 names.ReadableString(p.Filesystem), 171 names.ReadableString(p.Machine), 172 result.Error, 173 ) 174 continue 175 } 176 filesystemAttachments = append(filesystemAttachments, *result.FilesystemAttachment) 177 } 178 } 179 scheduleOperations(ctx, reschedule...) 180 setStatus(ctx, statuses) 181 if err := setFilesystemAttachmentInfo(ctx, filesystemAttachments); err != nil { 182 return errors.Trace(err) 183 } 184 return nil 185 } 186 187 // destroyFilesystems destroys filesystems with the specified parameters. 188 func destroyFilesystems(ctx *context, ops map[names.FilesystemTag]*destroyFilesystemOp) error { 189 tags := make([]names.FilesystemTag, 0, len(ops)) 190 for tag := range ops { 191 tags = append(tags, tag) 192 } 193 filesystemParams, err := filesystemParams(ctx, tags) 194 if err != nil { 195 return errors.Trace(err) 196 } 197 paramsBySource, filesystemSources, err := filesystemParamsBySource( 198 ctx.config.StorageDir, 199 filesystemParams, 200 ctx.managedFilesystemSource, 201 ctx.config.Registry, 202 ) 203 if err != nil { 204 return errors.Trace(err) 205 } 206 var remove []names.Tag 207 var reschedule []scheduleOp 208 var statuses []params.EntityStatusArgs 209 for sourceName, filesystemParams := range paramsBySource { 210 logger.Debugf("destroying filesystems from %q: %v", sourceName, filesystemParams) 211 filesystemSource := filesystemSources[sourceName] 212 validFilesystemParams, validationErrors := validateFilesystemParams(filesystemSource, filesystemParams) 213 for i, err := range validationErrors { 214 if err == nil { 215 continue 216 } 217 statuses = append(statuses, params.EntityStatusArgs{ 218 Tag: filesystemParams[i].Tag.String(), 219 Status: status.Error.String(), 220 Info: err.Error(), 221 }) 222 logger.Debugf( 223 "failed to validate parameters for %s: %v", 224 names.ReadableString(filesystemParams[i].Tag), err, 225 ) 226 } 227 filesystemParams = validFilesystemParams 228 if len(filesystemParams) == 0 { 229 continue 230 } 231 filesystemIds := make([]string, len(filesystemParams)) 232 for i, filesystemParams := range filesystemParams { 233 filesystem, ok := ctx.filesystems[filesystemParams.Tag] 234 if !ok { 235 return errors.NotFoundf("filesystem %s", filesystemParams.Tag.Id()) 236 } 237 filesystemIds[i] = filesystem.FilesystemId 238 } 239 errs, err := filesystemSource.DestroyFilesystems(filesystemIds) 240 if err != nil { 241 return errors.Trace(err) 242 } 243 for i, err := range errs { 244 tag := filesystemParams[i].Tag 245 if err == nil { 246 remove = append(remove, tag) 247 continue 248 } 249 // Failed to destroy filesystem; reschedule and update status. 250 reschedule = append(reschedule, ops[tag]) 251 statuses = append(statuses, params.EntityStatusArgs{ 252 Tag: tag.String(), 253 Status: status.Destroying.String(), 254 Info: err.Error(), 255 }) 256 } 257 } 258 scheduleOperations(ctx, reschedule...) 259 setStatus(ctx, statuses) 260 if err := removeEntities(ctx, remove); err != nil { 261 return errors.Annotate(err, "removing filesystems from state") 262 } 263 return nil 264 } 265 266 // detachFilesystems destroys filesystem attachments with the specified parameters. 267 func detachFilesystems(ctx *context, ops map[params.MachineStorageId]*detachFilesystemOp) error { 268 filesystemAttachmentParams := make([]storage.FilesystemAttachmentParams, 0, len(ops)) 269 for _, op := range ops { 270 filesystemAttachmentParams = append(filesystemAttachmentParams, op.args) 271 } 272 paramsBySource, filesystemSources, err := filesystemAttachmentParamsBySource( 273 ctx.config.StorageDir, 274 filesystemAttachmentParams, 275 ctx.filesystems, 276 ctx.managedFilesystemSource, 277 ctx.config.Registry, 278 ) 279 if err != nil { 280 return errors.Trace(err) 281 } 282 var reschedule []scheduleOp 283 var statuses []params.EntityStatusArgs 284 var remove []params.MachineStorageId 285 for sourceName, filesystemAttachmentParams := range paramsBySource { 286 logger.Debugf("detaching filesystems: %+v", filesystemAttachmentParams) 287 filesystemSource := filesystemSources[sourceName] 288 errs, err := filesystemSource.DetachFilesystems(filesystemAttachmentParams) 289 if err != nil { 290 return errors.Annotatef(err, "detaching filesystems from source %q", sourceName) 291 } 292 for i, err := range errs { 293 p := filesystemAttachmentParams[i] 294 statuses = append(statuses, params.EntityStatusArgs{ 295 Tag: p.Filesystem.String(), 296 // TODO(axw) when we support multiple 297 // attachment, we'll have to check if 298 // there are any other attachments 299 // before saying the status "detached". 300 Status: status.Detached.String(), 301 }) 302 id := params.MachineStorageId{ 303 MachineTag: p.Machine.String(), 304 AttachmentTag: p.Filesystem.String(), 305 } 306 entityStatus := &statuses[len(statuses)-1] 307 if err != nil { 308 reschedule = append(reschedule, ops[id]) 309 entityStatus.Status = status.Detaching.String() 310 entityStatus.Info = err.Error() 311 logger.Debugf( 312 "failed to detach %s from %s: %v", 313 names.ReadableString(p.Filesystem), 314 names.ReadableString(p.Machine), 315 err, 316 ) 317 continue 318 } 319 remove = append(remove, id) 320 } 321 } 322 scheduleOperations(ctx, reschedule...) 323 setStatus(ctx, statuses) 324 if err := removeAttachments(ctx, remove); err != nil { 325 return errors.Annotate(err, "removing attachments from state") 326 } 327 for _, id := range remove { 328 delete(ctx.filesystemAttachments, id) 329 } 330 return nil 331 } 332 333 // filesystemParamsBySource separates the filesystem parameters by filesystem source. 334 func filesystemParamsBySource( 335 baseStorageDir string, 336 params []storage.FilesystemParams, 337 managedFilesystemSource storage.FilesystemSource, 338 registry storage.ProviderRegistry, 339 ) (map[string][]storage.FilesystemParams, map[string]storage.FilesystemSource, error) { 340 // TODO(axw) later we may have multiple instantiations (sources) 341 // for a storage provider, e.g. multiple Ceph installations. For 342 // now we assume a single source for each provider type, with no 343 // configuration. 344 filesystemSources := make(map[string]storage.FilesystemSource) 345 for _, params := range params { 346 sourceName := string(params.Provider) 347 if _, ok := filesystemSources[sourceName]; ok { 348 continue 349 } 350 if params.Volume != (names.VolumeTag{}) { 351 filesystemSources[sourceName] = managedFilesystemSource 352 continue 353 } 354 filesystemSource, err := filesystemSource( 355 baseStorageDir, sourceName, params.Provider, registry, 356 ) 357 if errors.Cause(err) == errNonDynamic { 358 filesystemSource = nil 359 } else if err != nil { 360 return nil, nil, errors.Annotate(err, "getting filesystem source") 361 } 362 filesystemSources[sourceName] = filesystemSource 363 } 364 paramsBySource := make(map[string][]storage.FilesystemParams) 365 for _, params := range params { 366 sourceName := string(params.Provider) 367 filesystemSource := filesystemSources[sourceName] 368 if filesystemSource == nil { 369 // Ignore nil filesystem sources; this means that the 370 // filesystem should be created by the machine-provisioner. 371 continue 372 } 373 paramsBySource[sourceName] = append(paramsBySource[sourceName], params) 374 } 375 return paramsBySource, filesystemSources, nil 376 } 377 378 // validateFilesystemParams validates a collection of filesystem parameters. 379 func validateFilesystemParams( 380 filesystemSource storage.FilesystemSource, 381 filesystemParams []storage.FilesystemParams, 382 ) ([]storage.FilesystemParams, []error) { 383 valid := make([]storage.FilesystemParams, 0, len(filesystemParams)) 384 results := make([]error, len(filesystemParams)) 385 for i, params := range filesystemParams { 386 err := filesystemSource.ValidateFilesystemParams(params) 387 if err == nil { 388 valid = append(valid, params) 389 } 390 results[i] = err 391 } 392 return valid, results 393 } 394 395 // filesystemAttachmentParamsBySource separates the filesystem attachment parameters by filesystem source. 396 func filesystemAttachmentParamsBySource( 397 baseStorageDir string, 398 params []storage.FilesystemAttachmentParams, 399 filesystems map[names.FilesystemTag]storage.Filesystem, 400 managedFilesystemSource storage.FilesystemSource, 401 registry storage.ProviderRegistry, 402 ) (map[string][]storage.FilesystemAttachmentParams, map[string]storage.FilesystemSource, error) { 403 // TODO(axw) later we may have multiple instantiations (sources) 404 // for a storage provider, e.g. multiple Ceph installations. For 405 // now we assume a single source for each provider type, with no 406 // configuration. 407 filesystemSources := make(map[string]storage.FilesystemSource) 408 paramsBySource := make(map[string][]storage.FilesystemAttachmentParams) 409 for _, params := range params { 410 sourceName := string(params.Provider) 411 paramsBySource[sourceName] = append(paramsBySource[sourceName], params) 412 if _, ok := filesystemSources[sourceName]; ok { 413 continue 414 } 415 filesystem := filesystems[params.Filesystem] 416 if filesystem.Volume != (names.VolumeTag{}) { 417 filesystemSources[sourceName] = managedFilesystemSource 418 continue 419 } 420 filesystemSource, err := filesystemSource( 421 baseStorageDir, sourceName, params.Provider, registry, 422 ) 423 if err != nil { 424 return nil, nil, errors.Annotate(err, "getting filesystem source") 425 } 426 filesystemSources[sourceName] = filesystemSource 427 } 428 return paramsBySource, filesystemSources, nil 429 } 430 431 func setFilesystemAttachmentInfo(ctx *context, filesystemAttachments []storage.FilesystemAttachment) error { 432 if len(filesystemAttachments) == 0 { 433 return nil 434 } 435 // TODO(axw) we need to be able to list filesystem attachments in the 436 // provider, by environment, so that we can "harvest" them if they're 437 // unknown. This will take care of killing filesystems that we fail to 438 // record in state. 439 errorResults, err := ctx.config.Filesystems.SetFilesystemAttachmentInfo( 440 filesystemAttachmentsFromStorage(filesystemAttachments), 441 ) 442 if err != nil { 443 return errors.Annotate(err, "publishing filesystems to state") 444 } 445 for i, result := range errorResults { 446 if result.Error != nil { 447 return errors.Annotatef( 448 result.Error, "publishing attachment of %s to %s to state", 449 names.ReadableString(filesystemAttachments[i].Filesystem), 450 names.ReadableString(filesystemAttachments[i].Machine), 451 ) 452 } 453 // Record the filesystem attachment in the context. 454 id := params.MachineStorageId{ 455 MachineTag: filesystemAttachments[i].Machine.String(), 456 AttachmentTag: filesystemAttachments[i].Filesystem.String(), 457 } 458 ctx.filesystemAttachments[id] = filesystemAttachments[i] 459 removePendingFilesystemAttachment(ctx, id) 460 } 461 return nil 462 } 463 464 func filesystemsFromStorage(in []storage.Filesystem) []params.Filesystem { 465 out := make([]params.Filesystem, len(in)) 466 for i, f := range in { 467 paramsFilesystem := params.Filesystem{ 468 f.Tag.String(), 469 "", 470 params.FilesystemInfo{ 471 f.FilesystemId, 472 f.Size, 473 }, 474 } 475 if f.Volume != (names.VolumeTag{}) { 476 paramsFilesystem.VolumeTag = f.Volume.String() 477 } 478 out[i] = paramsFilesystem 479 } 480 return out 481 } 482 483 func filesystemAttachmentsFromStorage(in []storage.FilesystemAttachment) []params.FilesystemAttachment { 484 out := make([]params.FilesystemAttachment, len(in)) 485 for i, f := range in { 486 out[i] = params.FilesystemAttachment{ 487 f.Filesystem.String(), 488 f.Machine.String(), 489 params.FilesystemAttachmentInfo{ 490 f.Path, 491 f.ReadOnly, 492 }, 493 } 494 } 495 return out 496 } 497 498 type createFilesystemOp struct { 499 exponentialBackoff 500 args storage.FilesystemParams 501 } 502 503 func (op *createFilesystemOp) key() interface{} { 504 return op.args.Tag 505 } 506 507 type destroyFilesystemOp struct { 508 exponentialBackoff 509 tag names.FilesystemTag 510 } 511 512 func (op *destroyFilesystemOp) key() interface{} { 513 return op.tag 514 } 515 516 type attachFilesystemOp struct { 517 exponentialBackoff 518 args storage.FilesystemAttachmentParams 519 } 520 521 func (op *attachFilesystemOp) key() interface{} { 522 return params.MachineStorageId{ 523 MachineTag: op.args.Machine.String(), 524 AttachmentTag: op.args.Filesystem.String(), 525 } 526 } 527 528 type detachFilesystemOp struct { 529 exponentialBackoff 530 args storage.FilesystemAttachmentParams 531 } 532 533 func (op *detachFilesystemOp) key() interface{} { 534 return params.MachineStorageId{ 535 MachineTag: op.args.Machine.String(), 536 AttachmentTag: op.args.Filesystem.String(), 537 } 538 }