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