github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/storageprovisioner/filesystem_events.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 "github.com/juju/errors" 8 "github.com/juju/names/v5" 9 10 "github.com/juju/juju/core/instance" 11 "github.com/juju/juju/core/watcher" 12 "github.com/juju/juju/rpc/params" 13 "github.com/juju/juju/storage" 14 ) 15 16 // filesystemsChanged is called when the lifecycle states of the filesystems 17 // with the provided IDs have been seen to have changed. 18 func filesystemsChanged(ctx *context, changes []string) error { 19 tags := make([]names.Tag, len(changes)) 20 for i, change := range changes { 21 tags[i] = names.NewFilesystemTag(change) 22 } 23 alive, dying, dead, err := storageEntityLife(ctx, tags) 24 if err != nil { 25 return errors.Trace(err) 26 } 27 ctx.config.Logger.Debugf("filesystems alive: %v, dying: %v, dead: %v", alive, dying, dead) 28 if len(alive)+len(dying)+len(dead) == 0 { 29 return nil 30 } 31 32 // Get filesystem information for filesystems, so we can provision, 33 // deprovision, attach and detach. 34 filesystemTags := make([]names.FilesystemTag, 0, len(alive)+len(dying)+len(dead)) 35 for _, tag := range alive { 36 filesystemTags = append(filesystemTags, tag.(names.FilesystemTag)) 37 } 38 for _, tag := range dying { 39 filesystemTags = append(filesystemTags, tag.(names.FilesystemTag)) 40 } 41 for _, tag := range dead { 42 filesystemTags = append(filesystemTags, tag.(names.FilesystemTag)) 43 } 44 filesystemResults, err := ctx.config.Filesystems.Filesystems(filesystemTags) 45 if err != nil { 46 return errors.Annotatef(err, "getting filesystem information") 47 } 48 49 aliveFilesystemTags := filesystemTags[:len(alive)] 50 dyingFilesystemTags := filesystemTags[len(alive) : len(alive)+len(dying)] 51 deadFilesystemTags := filesystemTags[len(alive)+len(dying):] 52 aliveFilesystemResults := filesystemResults[:len(alive)] 53 dyingFilesystemResults := filesystemResults[len(alive) : len(alive)+len(dying)] 54 deadFilesystemResults := filesystemResults[len(alive)+len(dying):] 55 56 if err := processDeadFilesystems(ctx, deadFilesystemTags, deadFilesystemResults); err != nil { 57 return errors.Annotate(err, "deprovisioning filesystems") 58 } 59 if err := processDyingFilesystems(ctx, dyingFilesystemTags, dyingFilesystemResults); err != nil { 60 return errors.Annotate(err, "processing dying filesystems") 61 } 62 if err := processAliveFilesystems(ctx, aliveFilesystemTags, aliveFilesystemResults); err != nil { 63 return errors.Annotate(err, "provisioning filesystems") 64 } 65 return nil 66 } 67 68 // filesystemAttachmentsChanged is called when the lifecycle states of the filesystem 69 // attachments with the provided IDs have been seen to have changed. 70 func filesystemAttachmentsChanged(ctx *context, watcherIds []watcher.MachineStorageId) error { 71 ids := copyMachineStorageIds(watcherIds) 72 alive, dying, dead, gone, err := attachmentLife(ctx, ids) 73 if err != nil { 74 return errors.Trace(err) 75 } 76 ctx.config.Logger.Debugf("filesystem attachment alive: %v, dying: %v, dead: %v", alive, dying, dead) 77 if len(dead) != 0 { 78 // We should not see dead filesystem attachments; 79 // attachments go directly from Dying to removed. 80 ctx.config.Logger.Warningf("unexpected dead filesystem attachments: %v", dead) 81 } 82 // Clean up any attachments which have been removed. 83 for _, id := range gone { 84 delete(ctx.filesystemAttachments, id) 85 } 86 if len(alive)+len(dying) == 0 { 87 return nil 88 } 89 90 // Get filesystem information for alive and dying filesystem attachments, so 91 // we can attach/detach. 92 ids = append(alive, dying...) 93 filesystemAttachmentResults, err := ctx.config.Filesystems.FilesystemAttachments(ids) 94 if err != nil { 95 return errors.Annotatef(err, "getting filesystem attachment information") 96 } 97 98 // Deprovision Dying filesystem attachments. 99 dyingFilesystemAttachmentResults := filesystemAttachmentResults[len(alive):] 100 if err := processDyingFilesystemAttachments(ctx, dying, dyingFilesystemAttachmentResults); err != nil { 101 return errors.Annotate(err, "destroying filesystem attachments") 102 } 103 104 // Provision Alive filesystem attachments. 105 aliveFilesystemAttachmentResults := filesystemAttachmentResults[:len(alive)] 106 if err := processAliveFilesystemAttachments(ctx, alive, aliveFilesystemAttachmentResults); err != nil { 107 return errors.Annotate(err, "creating filesystem attachments") 108 } 109 110 return nil 111 } 112 113 // processDyingFilesystems processes the FilesystemResults for Dying filesystems, 114 // removing them from provisioning-pending as necessary. 115 func processDyingFilesystems(ctx *context, tags []names.FilesystemTag, filesystemResults []params.FilesystemResult) error { 116 for _, tag := range tags { 117 removePendingFilesystem(ctx, tag) 118 } 119 return nil 120 } 121 122 func updateFilesystem(ctx *context, info storage.Filesystem) { 123 ctx.filesystems[info.Tag] = info 124 for id, params := range ctx.incompleteFilesystemAttachmentParams { 125 if params.FilesystemId == "" && id.AttachmentTag == info.Tag.String() { 126 updatePendingFilesystemAttachment(ctx, id, params) 127 } 128 } 129 } 130 131 func updatePendingFilesystem(ctx *context, params storage.FilesystemParams) { 132 if params.Volume != (names.VolumeTag{}) { 133 // The filesystem is volume-backed: we must watch for 134 // the corresponding block device. This will trigger a 135 // one-time (for the volume) forced update of block 136 // devices. If the block device is not immediately 137 // available, then we rely on the watcher. The forced 138 // update is necessary in case the block device was 139 // added to state already, and we didn't observe it. 140 if _, ok := ctx.volumeBlockDevices[params.Volume]; !ok { 141 ctx.pendingVolumeBlockDevices.Add(params.Volume) 142 ctx.incompleteFilesystemParams[params.Tag] = params 143 return 144 } 145 } 146 delete(ctx.incompleteFilesystemParams, params.Tag) 147 scheduleOperations(ctx, &createFilesystemOp{args: params}) 148 } 149 150 func removePendingFilesystem(ctx *context, tag names.FilesystemTag) { 151 delete(ctx.incompleteFilesystemParams, tag) 152 ctx.schedule.Remove(tag) 153 } 154 155 // updatePendingFilesystemAttachment adds the given filesystem attachment params to 156 // either the incomplete set or the schedule. If the params are incomplete 157 // due to a missing instance ID, updatePendingFilesystemAttachment will request 158 // that the machine be watched so its instance ID can be learned. 159 func updatePendingFilesystemAttachment( 160 ctx *context, 161 id params.MachineStorageId, 162 params storage.FilesystemAttachmentParams, 163 ) { 164 var incomplete bool 165 filesystem, ok := ctx.filesystems[params.Filesystem] 166 if !ok { 167 incomplete = true 168 } else { 169 params.FilesystemId = filesystem.FilesystemId 170 if filesystem.Volume != (names.VolumeTag{}) { 171 // The filesystem is volume-backed: if the filesystem 172 // was created in another session, then the block device 173 // may not have been seen yet. We must wait for the block 174 // device watcher to trigger. 175 if _, ok := ctx.volumeBlockDevices[filesystem.Volume]; !ok { 176 incomplete = true 177 } 178 } 179 } 180 if params.InstanceId == "" { 181 watchMachine(ctx, params.Machine.(names.MachineTag)) 182 incomplete = true 183 } 184 if params.FilesystemId == "" { 185 incomplete = true 186 } 187 if incomplete { 188 ctx.incompleteFilesystemAttachmentParams[id] = params 189 return 190 } 191 delete(ctx.incompleteFilesystemAttachmentParams, id) 192 scheduleOperations(ctx, &attachFilesystemOp{args: params}) 193 } 194 195 // removePendingFilesystemAttachment removes the specified pending filesystem 196 // attachment from the incomplete set and/or the schedule if it exists 197 // there. 198 func removePendingFilesystemAttachment(ctx *context, id params.MachineStorageId) { 199 delete(ctx.incompleteFilesystemAttachmentParams, id) 200 ctx.schedule.Remove(id) 201 } 202 203 // processDeadFilesystems processes the FilesystemResults for Dead filesystems, 204 // deprovisioning filesystems and removing from state as necessary. 205 func processDeadFilesystems(ctx *context, tags []names.FilesystemTag, filesystemResults []params.FilesystemResult) error { 206 for _, tag := range tags { 207 removePendingFilesystem(ctx, tag) 208 } 209 var destroy []names.FilesystemTag 210 var remove []names.Tag 211 for i, result := range filesystemResults { 212 tag := tags[i] 213 if result.Error == nil { 214 ctx.config.Logger.Debugf("filesystem %s is provisioned, queuing for deprovisioning", tag.Id()) 215 filesystem, err := filesystemFromParams(result.Result) 216 if err != nil { 217 return errors.Annotate(err, "getting filesystem info") 218 } 219 updateFilesystem(ctx, filesystem) 220 destroy = append(destroy, tag) 221 continue 222 } 223 if params.IsCodeNotProvisioned(result.Error) { 224 ctx.config.Logger.Debugf("filesystem %s is not provisioned, queuing for removal", tag.Id()) 225 remove = append(remove, tag) 226 continue 227 } 228 return errors.Annotatef(result.Error, "getting filesystem information for filesystem %s", tag.Id()) 229 } 230 if len(destroy) > 0 { 231 ops := make([]scheduleOp, len(destroy)) 232 for i, tag := range destroy { 233 ops[i] = &removeFilesystemOp{tag: tag} 234 } 235 scheduleOperations(ctx, ops...) 236 } 237 if err := removeEntities(ctx, remove); err != nil { 238 return errors.Annotate(err, "removing filesystems from state") 239 } 240 return nil 241 } 242 243 // processDyingFilesystemAttachments processes the FilesystemAttachmentResults for 244 // Dying filesystem attachments, detaching filesystems and updating state as necessary. 245 func processDyingFilesystemAttachments( 246 ctx *context, 247 ids []params.MachineStorageId, 248 filesystemAttachmentResults []params.FilesystemAttachmentResult, 249 ) error { 250 for _, id := range ids { 251 removePendingFilesystemAttachment(ctx, id) 252 } 253 detach := make([]params.MachineStorageId, 0, len(ids)) 254 remove := make([]params.MachineStorageId, 0, len(ids)) 255 for i, result := range filesystemAttachmentResults { 256 id := ids[i] 257 if result.Error == nil { 258 detach = append(detach, id) 259 continue 260 } 261 if params.IsCodeNotProvisioned(result.Error) { 262 remove = append(remove, id) 263 continue 264 } 265 return errors.Annotatef(result.Error, "getting information for filesystem attachment %v", id) 266 } 267 if len(detach) > 0 { 268 attachmentParams, err := filesystemAttachmentParams(ctx, detach) 269 if err != nil { 270 return errors.Trace(err) 271 } 272 ops := make([]scheduleOp, len(attachmentParams)) 273 for i, p := range attachmentParams { 274 ops[i] = &detachFilesystemOp{args: p} 275 } 276 scheduleOperations(ctx, ops...) 277 } 278 if err := removeAttachments(ctx, remove); err != nil { 279 return errors.Annotate(err, "removing attachments from state") 280 } 281 return nil 282 } 283 284 // processAliveFilesystems processes the FilesystemResults for Alive filesystems, 285 // provisioning filesystems and setting the info in state as necessary. 286 func processAliveFilesystems(ctx *context, tags []names.FilesystemTag, filesystemResults []params.FilesystemResult) error { 287 // Filter out the already-provisioned filesystems. 288 pending := make([]names.FilesystemTag, 0, len(tags)) 289 for i, result := range filesystemResults { 290 tag := tags[i] 291 if result.Error == nil { 292 // Filesystem is already provisioned: skip. 293 ctx.config.Logger.Debugf("filesystem %q is already provisioned, nothing to do", tag.Id()) 294 filesystem, err := filesystemFromParams(result.Result) 295 if err != nil { 296 return errors.Annotate(err, "getting filesystem info") 297 } 298 updateFilesystem(ctx, filesystem) 299 if !ctx.isApplicationKind() { 300 if filesystem.Volume != (names.VolumeTag{}) { 301 // Ensure that volume-backed filesystems' block 302 // devices are present even after creating the 303 // filesystem, so that attachments can be made. 304 maybeAddPendingVolumeBlockDevice(ctx, filesystem.Volume) 305 } 306 } 307 continue 308 } 309 if !params.IsCodeNotProvisioned(result.Error) { 310 return errors.Annotatef( 311 result.Error, "getting filesystem information for filesystem %q", tag.Id(), 312 ) 313 } 314 // The filesystem has not yet been provisioned, so record its tag 315 // to enquire about parameters below. 316 pending = append(pending, tag) 317 } 318 if len(pending) == 0 { 319 return nil 320 } 321 params, err := filesystemParams(ctx, pending) 322 if err != nil { 323 return errors.Annotate(err, "getting filesystem params") 324 } 325 for _, params := range params { 326 if ctx.isApplicationKind() { 327 ctx.config.Logger.Debugf("not queuing filesystem for %v unit", ctx.config.Scope.Id()) 328 continue 329 } 330 updatePendingFilesystem(ctx, params) 331 } 332 return nil 333 } 334 335 func maybeAddPendingVolumeBlockDevice(ctx *context, v names.VolumeTag) { 336 if _, ok := ctx.volumeBlockDevices[v]; !ok { 337 ctx.pendingVolumeBlockDevices.Add(v) 338 } 339 } 340 341 // processAliveFilesystemAttachments processes the FilesystemAttachmentResults 342 // for Alive filesystem attachments, attaching filesystems and setting the info 343 // in state as necessary. 344 func processAliveFilesystemAttachments( 345 ctx *context, 346 ids []params.MachineStorageId, 347 filesystemAttachmentResults []params.FilesystemAttachmentResult, 348 ) error { 349 // Filter out the already-attached. 350 pending := make([]params.MachineStorageId, 0, len(ids)) 351 for i, result := range filesystemAttachmentResults { 352 if result.Error == nil { 353 // Filesystem attachment is already provisioned: if we 354 // didn't (re)attach in this session, then we must do 355 // so now. 356 action := "nothing to do" 357 if _, ok := ctx.filesystemAttachments[ids[i]]; !ok { 358 // Not yet (re)attached in this session. 359 pending = append(pending, ids[i]) 360 action = "will reattach" 361 } 362 ctx.config.Logger.Debugf( 363 "%s is already attached to %s, %s", 364 ids[i].AttachmentTag, ids[i].MachineTag, action, 365 ) 366 removePendingFilesystemAttachment(ctx, ids[i]) 367 continue 368 } 369 if !params.IsCodeNotProvisioned(result.Error) { 370 return errors.Annotatef( 371 result.Error, "getting information for attachment %v", ids[i], 372 ) 373 } 374 // The filesystem has not yet been attached, so 375 // record its tag to enquire about parameters below. 376 pending = append(pending, ids[i]) 377 } 378 if len(pending) == 0 { 379 return nil 380 } 381 params, err := filesystemAttachmentParams(ctx, pending) 382 if err != nil { 383 return errors.Trace(err) 384 } 385 for i, params := range params { 386 if params.Machine != nil && params.Machine.Kind() != names.MachineTagKind { 387 ctx.config.Logger.Debugf("not queuing filesystem attachment for non-machine %v", params.Machine) 388 continue 389 } 390 updatePendingFilesystemAttachment(ctx, pending[i], params) 391 } 392 return nil 393 } 394 395 // filesystemAttachmentParams obtains the specified attachments' parameters. 396 func filesystemAttachmentParams( 397 ctx *context, ids []params.MachineStorageId, 398 ) ([]storage.FilesystemAttachmentParams, error) { 399 paramsResults, err := ctx.config.Filesystems.FilesystemAttachmentParams(ids) 400 if err != nil { 401 return nil, errors.Annotate(err, "getting filesystem attachment params") 402 } 403 attachmentParams := make([]storage.FilesystemAttachmentParams, len(ids)) 404 for i, result := range paramsResults { 405 if result.Error != nil { 406 return nil, errors.Annotate(result.Error, "getting filesystem attachment parameters") 407 } 408 params, err := filesystemAttachmentParamsFromParams(result.Result) 409 if err != nil { 410 return nil, errors.Annotate(err, "getting filesystem attachment parameters") 411 } 412 attachmentParams[i] = params 413 } 414 return attachmentParams, nil 415 } 416 417 // filesystemParams obtains the specified filesystems' parameters. 418 func filesystemParams(ctx *context, tags []names.FilesystemTag) ([]storage.FilesystemParams, error) { 419 paramsResults, err := ctx.config.Filesystems.FilesystemParams(tags) 420 if err != nil { 421 return nil, errors.Annotate(err, "getting filesystem params") 422 } 423 allParams := make([]storage.FilesystemParams, len(tags)) 424 for i, result := range paramsResults { 425 if result.Error != nil { 426 return nil, errors.Annotate(result.Error, "getting filesystem parameters") 427 } 428 params, err := filesystemParamsFromParams(result.Result) 429 if err != nil { 430 return nil, errors.Annotate(err, "getting filesystem parameters") 431 } 432 allParams[i] = params 433 } 434 return allParams, nil 435 } 436 437 // removeFilesystemParams obtains the specified filesystems' destruction parameters. 438 func removeFilesystemParams(ctx *context, tags []names.FilesystemTag) ([]params.RemoveFilesystemParams, error) { 439 paramsResults, err := ctx.config.Filesystems.RemoveFilesystemParams(tags) 440 if err != nil { 441 return nil, errors.Annotate(err, "getting filesystem params") 442 } 443 allParams := make([]params.RemoveFilesystemParams, len(tags)) 444 for i, result := range paramsResults { 445 if result.Error != nil { 446 return nil, errors.Annotate(result.Error, "getting filesystem removal parameters") 447 } 448 allParams[i] = result.Result 449 } 450 return allParams, nil 451 } 452 453 func filesystemFromParams(in params.Filesystem) (storage.Filesystem, error) { 454 filesystemTag, err := names.ParseFilesystemTag(in.FilesystemTag) 455 if err != nil { 456 return storage.Filesystem{}, errors.Trace(err) 457 } 458 var volumeTag names.VolumeTag 459 if in.VolumeTag != "" { 460 volumeTag, err = names.ParseVolumeTag(in.VolumeTag) 461 if err != nil { 462 return storage.Filesystem{}, errors.Trace(err) 463 } 464 } 465 return storage.Filesystem{ 466 filesystemTag, 467 volumeTag, 468 storage.FilesystemInfo{ 469 in.Info.FilesystemId, 470 in.Info.Size, 471 }, 472 }, nil 473 } 474 475 func filesystemParamsFromParams(in params.FilesystemParams) (storage.FilesystemParams, error) { 476 filesystemTag, err := names.ParseFilesystemTag(in.FilesystemTag) 477 if err != nil { 478 return storage.FilesystemParams{}, errors.Trace(err) 479 } 480 var volumeTag names.VolumeTag 481 if in.VolumeTag != "" { 482 volumeTag, err = names.ParseVolumeTag(in.VolumeTag) 483 if err != nil { 484 return storage.FilesystemParams{}, errors.Trace(err) 485 } 486 } 487 providerType := storage.ProviderType(in.Provider) 488 return storage.FilesystemParams{ 489 Tag: filesystemTag, 490 Volume: volumeTag, 491 Size: in.Size, 492 Provider: providerType, 493 Attributes: in.Attributes, 494 ResourceTags: in.Tags, 495 }, nil 496 } 497 498 func filesystemAttachmentParamsFromParams(in params.FilesystemAttachmentParams) (storage.FilesystemAttachmentParams, error) { 499 hostTag, err := names.ParseTag(in.MachineTag) 500 if err != nil { 501 return storage.FilesystemAttachmentParams{}, errors.Trace(err) 502 } 503 filesystemTag, err := names.ParseFilesystemTag(in.FilesystemTag) 504 if err != nil { 505 return storage.FilesystemAttachmentParams{}, errors.Trace(err) 506 } 507 return storage.FilesystemAttachmentParams{ 508 AttachmentParams: storage.AttachmentParams{ 509 Provider: storage.ProviderType(in.Provider), 510 Machine: hostTag, 511 InstanceId: instance.Id(in.InstanceId), 512 ReadOnly: in.ReadOnly, 513 }, 514 Filesystem: filesystemTag, 515 FilesystemId: in.FilesystemId, 516 Path: in.MountPoint, 517 }, nil 518 }