github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/storageprovisioner/storageprovisioner.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 "time" 8 9 "github.com/juju/errors" 10 "github.com/juju/names/v5" 11 "github.com/juju/worker/v3" 12 "github.com/juju/worker/v3/catacomb" 13 14 "github.com/juju/juju/core/watcher" 15 "github.com/juju/juju/rpc/params" 16 "github.com/juju/juju/storage" 17 "github.com/juju/juju/storage/provider" 18 "github.com/juju/juju/worker/storageprovisioner/internal/schedule" 19 ) 20 21 var ( 22 // defaultDependentChangesTimeout is the default timeout for waiting for any 23 // dependent changes to occur before proceeding with a storage provisioner. 24 // This is a variable so it can be overridden in tests (sigh). 25 defaultDependentChangesTimeout = time.Second 26 ) 27 28 // logger is here to stop the desire of creating a package level logger. 29 // Don't do this, instead use the one passed as manifold config. 30 type logger interface{} 31 32 var _ logger = struct{}{} 33 34 var newManagedFilesystemSource = provider.NewManagedFilesystemSource 35 36 // VolumeAccessor defines an interface used to allow a storage provisioner 37 // worker to perform volume related operations. 38 type VolumeAccessor interface { 39 // WatchBlockDevices watches for changes to the block devices of the 40 // specified machine. 41 WatchBlockDevices(names.MachineTag) (watcher.NotifyWatcher, error) 42 43 // WatchVolumes watches for changes to volumes that this storage 44 // provisioner is responsible for. 45 WatchVolumes(scope names.Tag) (watcher.StringsWatcher, error) 46 47 // WatchVolumeAttachments watches for changes to volume attachments 48 // that this storage provisioner is responsible for. 49 WatchVolumeAttachments(scope names.Tag) (watcher.MachineStorageIdsWatcher, error) 50 51 // WatchVolumeAttachmentPlans watches for changes to volume attachments 52 // destined for this machine. It allows the machine agent to do any extra 53 // initialization of the attachment, such as logging into the iSCSI target 54 WatchVolumeAttachmentPlans(scope names.Tag) (watcher.MachineStorageIdsWatcher, error) 55 56 // Volumes returns details of volumes with the specified tags. 57 Volumes([]names.VolumeTag) ([]params.VolumeResult, error) 58 59 // VolumeBlockDevices returns details of block devices corresponding to 60 // the specified volume attachment IDs. 61 VolumeBlockDevices([]params.MachineStorageId) ([]params.BlockDeviceResult, error) 62 63 // VolumeAttachments returns details of volume attachments with 64 // the specified tags. 65 VolumeAttachments([]params.MachineStorageId) ([]params.VolumeAttachmentResult, error) 66 67 VolumeAttachmentPlans([]params.MachineStorageId) ([]params.VolumeAttachmentPlanResult, error) 68 69 // VolumeParams returns the parameters for creating the volumes 70 // with the specified tags. 71 VolumeParams([]names.VolumeTag) ([]params.VolumeParamsResult, error) 72 73 // RemoveVolumeParams returns the parameters for destroying or 74 // releasing the volumes with the specified tags. 75 RemoveVolumeParams([]names.VolumeTag) ([]params.RemoveVolumeParamsResult, error) 76 77 // VolumeAttachmentParams returns the parameters for creating the 78 // volume attachments with the specified tags. 79 VolumeAttachmentParams([]params.MachineStorageId) ([]params.VolumeAttachmentParamsResult, error) 80 81 // SetVolumeInfo records the details of newly provisioned volumes. 82 SetVolumeInfo([]params.Volume) ([]params.ErrorResult, error) 83 84 // SetVolumeAttachmentInfo records the details of newly provisioned 85 // volume attachments. 86 SetVolumeAttachmentInfo([]params.VolumeAttachment) ([]params.ErrorResult, error) 87 88 CreateVolumeAttachmentPlans(volumeAttachmentPlans []params.VolumeAttachmentPlan) ([]params.ErrorResult, error) 89 RemoveVolumeAttachmentPlan([]params.MachineStorageId) ([]params.ErrorResult, error) 90 SetVolumeAttachmentPlanBlockInfo(volumeAttachmentPlans []params.VolumeAttachmentPlan) ([]params.ErrorResult, error) 91 } 92 93 // FilesystemAccessor defines an interface used to allow a storage provisioner 94 // worker to perform filesystem related operations. 95 type FilesystemAccessor interface { 96 // WatchFilesystems watches for changes to filesystems that this 97 // storage provisioner is responsible for. 98 WatchFilesystems(scope names.Tag) (watcher.StringsWatcher, error) 99 100 // WatchFilesystemAttachments watches for changes to filesystem attachments 101 // that this storage provisioner is responsible for. 102 WatchFilesystemAttachments(scope names.Tag) (watcher.MachineStorageIdsWatcher, error) 103 104 // Filesystems returns details of filesystems with the specified tags. 105 Filesystems([]names.FilesystemTag) ([]params.FilesystemResult, error) 106 107 // FilesystemAttachments returns details of filesystem attachments with 108 // the specified tags. 109 FilesystemAttachments([]params.MachineStorageId) ([]params.FilesystemAttachmentResult, error) 110 111 // FilesystemParams returns the parameters for creating the filesystems 112 // with the specified tags. 113 FilesystemParams([]names.FilesystemTag) ([]params.FilesystemParamsResult, error) 114 115 // RemoveFilesystemParams returns the parameters for destroying or 116 // releasing the filesystems with the specified tags. 117 RemoveFilesystemParams([]names.FilesystemTag) ([]params.RemoveFilesystemParamsResult, error) 118 119 // FilesystemAttachmentParams returns the parameters for creating the 120 // filesystem attachments with the specified tags. 121 FilesystemAttachmentParams([]params.MachineStorageId) ([]params.FilesystemAttachmentParamsResult, error) 122 123 // SetFilesystemInfo records the details of newly provisioned filesystems. 124 SetFilesystemInfo([]params.Filesystem) ([]params.ErrorResult, error) 125 126 // SetFilesystemAttachmentInfo records the details of newly provisioned 127 // filesystem attachments. 128 SetFilesystemAttachmentInfo([]params.FilesystemAttachment) ([]params.ErrorResult, error) 129 } 130 131 // MachineAccessor defines an interface used to allow a storage provisioner 132 // worker to perform machine related operations. 133 type MachineAccessor interface { 134 // WatchMachine watches for changes to the specified machine. 135 WatchMachine(names.MachineTag) (watcher.NotifyWatcher, error) 136 137 // InstanceIds returns the instance IDs of each machine. 138 InstanceIds([]names.MachineTag) ([]params.StringResult, error) 139 } 140 141 // LifecycleManager defines an interface used to enable a storage provisioner 142 // worker to perform lifcycle-related operations on storage entities and 143 // attachments. 144 type LifecycleManager interface { 145 // Life returns the lifecycle state of the specified entities. 146 Life([]names.Tag) ([]params.LifeResult, error) 147 148 // Remove removes the specified entities from state. 149 Remove([]names.Tag) ([]params.ErrorResult, error) 150 151 // AttachmentLife returns the lifecycle state of the specified 152 // machine/entity attachments. 153 AttachmentLife([]params.MachineStorageId) ([]params.LifeResult, error) 154 155 // RemoveAttachments removes the specified machine/entity attachments 156 // from state. 157 RemoveAttachments([]params.MachineStorageId) ([]params.ErrorResult, error) 158 } 159 160 // StatusSetter defines an interface used to set the status of entities. 161 type StatusSetter interface { 162 SetStatus([]params.EntityStatusArgs) error 163 } 164 165 // NewStorageProvisioner returns a Worker which manages 166 // provisioning (deprovisioning), and attachment (detachment) 167 // of first-class volumes and filesystems. 168 // 169 // Machine-scoped storage workers will be provided with 170 // a storage directory, while model-scoped workers 171 // will not. If the directory path is non-empty, then it 172 // will be passed to the storage source via its config. 173 var NewStorageProvisioner = func(config Config) (worker.Worker, error) { 174 if err := config.Validate(); err != nil { 175 return nil, errors.Trace(err) 176 } 177 w := &storageProvisioner{ 178 config: config, 179 } 180 err := catacomb.Invoke(catacomb.Plan{ 181 Site: &w.catacomb, 182 Work: w.loop, 183 }) 184 if err != nil { 185 return nil, errors.Trace(err) 186 } 187 return w, nil 188 } 189 190 type storageProvisioner struct { 191 catacomb catacomb.Catacomb 192 config Config 193 } 194 195 // Kill implements Worker.Kill(). 196 func (w *storageProvisioner) Kill() { 197 w.catacomb.Kill(nil) 198 } 199 200 // Wait implements Worker.Wait(). 201 func (w *storageProvisioner) Wait() error { 202 err := w.catacomb.Wait() 203 return err 204 } 205 206 func (w *storageProvisioner) loop() error { 207 var ( 208 volumesChanges watcher.StringsChannel 209 filesystemsChanges watcher.StringsChannel 210 volumeAttachmentsChanges watcher.MachineStorageIdsChannel 211 volumeAttachmentPlansChanges watcher.MachineStorageIdsChannel 212 filesystemAttachmentsChanges watcher.MachineStorageIdsChannel 213 machineBlockDevicesChanges <-chan struct{} 214 ) 215 machineChanges := make(chan names.MachineTag) 216 217 // Machine-scoped provisioners need to watch block devices, to create 218 // volume-backed filesystems. 219 if machineTag, ok := w.config.Scope.(names.MachineTag); ok { 220 machineBlockDevicesWatcher, err := w.config.Volumes.WatchBlockDevices(machineTag) 221 if err != nil { 222 return errors.Annotate(err, "watching block devices") 223 } 224 if err := w.catacomb.Add(machineBlockDevicesWatcher); err != nil { 225 return errors.Trace(err) 226 } 227 machineBlockDevicesChanges = machineBlockDevicesWatcher.Changes() 228 229 volumeAttachmentPlansWatcher, err := w.config.Volumes.WatchVolumeAttachmentPlans(machineTag) 230 if err != nil { 231 return errors.Annotate(err, "watching volume attachment plans") 232 } 233 if err := w.catacomb.Add(volumeAttachmentPlansWatcher); err != nil { 234 return errors.Trace(err) 235 } 236 237 volumeAttachmentPlansChanges = volumeAttachmentPlansWatcher.Changes() 238 } 239 240 ctx := context{ 241 kill: w.catacomb.Kill, 242 addWorker: w.catacomb.Add, 243 config: w.config, 244 volumes: make(map[names.VolumeTag]storage.Volume), 245 volumeAttachments: make(map[params.MachineStorageId]storage.VolumeAttachment), 246 volumeBlockDevices: make(map[names.VolumeTag]storage.BlockDevice), 247 filesystems: make(map[names.FilesystemTag]storage.Filesystem), 248 filesystemAttachments: make(map[params.MachineStorageId]storage.FilesystemAttachment), 249 machines: make(map[names.MachineTag]*machineWatcher), 250 machineChanges: machineChanges, 251 schedule: schedule.NewSchedule(w.config.Clock), 252 incompleteVolumeParams: make(map[names.VolumeTag]storage.VolumeParams), 253 incompleteVolumeAttachmentParams: make(map[params.MachineStorageId]storage.VolumeAttachmentParams), 254 incompleteFilesystemParams: make(map[names.FilesystemTag]storage.FilesystemParams), 255 incompleteFilesystemAttachmentParams: make(map[params.MachineStorageId]storage.FilesystemAttachmentParams), 256 pendingVolumeBlockDevices: names.NewSet(), 257 } 258 ctx.managedFilesystemSource = newManagedFilesystemSource( 259 ctx.volumeBlockDevices, ctx.filesystems, 260 ) 261 // Units don't use managed volume backed filesystems. 262 if ctx.isApplicationKind() { 263 ctx.managedFilesystemSource = &noopFilesystemSource{} 264 } 265 266 // Units don't have unit-scoped volumes - all volumes are 267 // associated with the model (namespace). 268 if !ctx.isApplicationKind() { 269 volumesWatcher, err := w.config.Volumes.WatchVolumes(w.config.Scope) 270 if err != nil { 271 return errors.Annotate(err, "watching volumes") 272 } 273 if err := w.catacomb.Add(volumesWatcher); err != nil { 274 return errors.Trace(err) 275 } 276 volumesChanges = volumesWatcher.Changes() 277 } 278 279 filesystemsWatcher, err := w.config.Filesystems.WatchFilesystems(w.config.Scope) 280 if err != nil { 281 return errors.Annotate(err, "watching filesystems") 282 } 283 if err := w.catacomb.Add(filesystemsWatcher); err != nil { 284 return errors.Trace(err) 285 } 286 filesystemsChanges = filesystemsWatcher.Changes() 287 288 volumeAttachmentsWatcher, err := w.config.Volumes.WatchVolumeAttachments(w.config.Scope) 289 if err != nil { 290 return errors.Annotate(err, "watching volume attachments") 291 } 292 if err := w.catacomb.Add(volumeAttachmentsWatcher); err != nil { 293 return errors.Trace(err) 294 } 295 volumeAttachmentsChanges = volumeAttachmentsWatcher.Changes() 296 297 filesystemAttachmentsWatcher, err := w.config.Filesystems.WatchFilesystemAttachments(w.config.Scope) 298 if err != nil { 299 return errors.Annotate(err, "watching filesystem attachments") 300 } 301 if err := w.catacomb.Add(filesystemAttachmentsWatcher); err != nil { 302 return errors.Trace(err) 303 } 304 filesystemAttachmentsChanges = filesystemAttachmentsWatcher.Changes() 305 306 for { 307 308 // Check if block devices need to be refreshed. 309 if err := processPendingVolumeBlockDevices(&ctx); err != nil { 310 return errors.Annotate(err, "processing pending block devices") 311 } 312 313 select { 314 case <-w.catacomb.Dying(): 315 return w.catacomb.ErrDying() 316 case changes, ok := <-volumesChanges: 317 if !ok { 318 return errors.New("volumes watcher closed") 319 } 320 if err := volumesChanged(&ctx, changes); err != nil { 321 return errors.Trace(err) 322 } 323 case changes, ok := <-volumeAttachmentsChanges: 324 if !ok { 325 return errors.New("volume attachments watcher closed") 326 } 327 // Process volume changes before volume attachments changes. 328 // This is because volume attachments are dependent on 329 // volumes, and reveals itself during a reboot of a machine. All 330 // the watcher changes come at once, but the order of the select 331 // case statements is not guaranteed. 332 if err := w.processDependentChanges(&ctx, volumesChanges, volumesChanged); err != nil { 333 return errors.Trace(err) 334 } 335 if err := volumeAttachmentsChanged(&ctx, changes); err != nil { 336 return errors.Trace(err) 337 } 338 case changes, ok := <-volumeAttachmentPlansChanges: 339 if !ok { 340 return errors.New("volume attachment plans watcher closed") 341 } 342 if err := volumeAttachmentPlansChanged(&ctx, changes); err != nil { 343 return errors.Trace(err) 344 } 345 case changes, ok := <-filesystemsChanges: 346 if !ok { 347 return errors.New("filesystems watcher closed") 348 } 349 if err := filesystemsChanged(&ctx, changes); err != nil { 350 return errors.Trace(err) 351 } 352 case changes, ok := <-filesystemAttachmentsChanges: 353 if !ok { 354 return errors.New("filesystem attachments watcher closed") 355 } 356 // Process filesystem changes before filesystem attachments changes. 357 // This is because filesystem attachments are dependent on 358 // filesystems, and reveals itself during a reboot of a machine. All 359 // the watcher changes come at once, but the order of the select 360 // case statements is not guaranteed. 361 if err := w.processDependentChanges(&ctx, filesystemsChanges, filesystemsChanged); err != nil { 362 return errors.Trace(err) 363 } 364 if err := filesystemAttachmentsChanged(&ctx, changes); err != nil { 365 return errors.Trace(err) 366 } 367 case _, ok := <-machineBlockDevicesChanges: 368 if !ok { 369 return errors.New("machine block devices watcher closed") 370 } 371 if err := machineBlockDevicesChanged(&ctx); err != nil { 372 return errors.Trace(err) 373 } 374 case machineTag := <-machineChanges: 375 if err := refreshMachine(&ctx, machineTag); err != nil { 376 return errors.Trace(err) 377 } 378 case <-ctx.schedule.Next(): 379 // Ready to pick something(s) off the pending queue. 380 if err := processSchedule(&ctx); err != nil { 381 return errors.Trace(err) 382 } 383 } 384 } 385 } 386 387 // processDependentChanges processes changes from a watcher strings channel. If 388 // there are any changes, it calls the given function, repeating until there are 389 // no more changes. 390 // If there are no changes, it returns with no error. 391 func (w *storageProvisioner) processDependentChanges(ctx *context, source watcher.StringsChannel, fn func(*context, []string) error) error { 392 for { 393 select { 394 case <-w.catacomb.Dying(): 395 return w.catacomb.ErrDying() 396 case changes, ok := <-source: 397 if !ok { 398 return errors.New("watcher closed") 399 } 400 if err := fn(ctx, changes); err != nil { 401 return errors.Trace(err) 402 } 403 case <-time.After(defaultDependentChangesTimeout): 404 // Nothing to do, we've waited long enough. 405 return nil 406 } 407 } 408 } 409 410 // processSchedule executes scheduled operations. 411 func processSchedule(ctx *context) error { 412 ready := ctx.schedule.Ready(ctx.config.Clock.Now()) 413 createVolumeOps := make(map[names.VolumeTag]*createVolumeOp) 414 removeVolumeOps := make(map[names.VolumeTag]*removeVolumeOp) 415 attachVolumeOps := make(map[params.MachineStorageId]*attachVolumeOp) 416 detachVolumeOps := make(map[params.MachineStorageId]*detachVolumeOp) 417 createFilesystemOps := make(map[names.FilesystemTag]*createFilesystemOp) 418 removeFilesystemOps := make(map[names.FilesystemTag]*removeFilesystemOp) 419 attachFilesystemOps := make(map[params.MachineStorageId]*attachFilesystemOp) 420 detachFilesystemOps := make(map[params.MachineStorageId]*detachFilesystemOp) 421 for _, item := range ready { 422 op := item.(scheduleOp) 423 key := op.key() 424 switch op := op.(type) { 425 case *createVolumeOp: 426 createVolumeOps[key.(names.VolumeTag)] = op 427 case *removeVolumeOp: 428 removeVolumeOps[key.(names.VolumeTag)] = op 429 case *attachVolumeOp: 430 attachVolumeOps[key.(params.MachineStorageId)] = op 431 case *detachVolumeOp: 432 detachVolumeOps[key.(params.MachineStorageId)] = op 433 case *createFilesystemOp: 434 createFilesystemOps[key.(names.FilesystemTag)] = op 435 case *removeFilesystemOp: 436 removeFilesystemOps[key.(names.FilesystemTag)] = op 437 case *attachFilesystemOp: 438 attachFilesystemOps[key.(params.MachineStorageId)] = op 439 case *detachFilesystemOp: 440 detachFilesystemOps[key.(params.MachineStorageId)] = op 441 } 442 } 443 if len(removeVolumeOps) > 0 { 444 if err := removeVolumes(ctx, removeVolumeOps); err != nil { 445 return errors.Annotate(err, "removing volumes") 446 } 447 } 448 if len(createVolumeOps) > 0 { 449 if err := createVolumes(ctx, createVolumeOps); err != nil { 450 return errors.Annotate(err, "creating volumes") 451 } 452 } 453 if len(detachVolumeOps) > 0 { 454 if err := detachVolumes(ctx, detachVolumeOps); err != nil { 455 return errors.Annotate(err, "detaching volumes") 456 } 457 } 458 if len(attachVolumeOps) > 0 { 459 if err := attachVolumes(ctx, attachVolumeOps); err != nil { 460 return errors.Annotate(err, "attaching volumes") 461 } 462 } 463 if len(removeFilesystemOps) > 0 { 464 if err := removeFilesystems(ctx, removeFilesystemOps); err != nil { 465 return errors.Annotate(err, "removing filesystems") 466 } 467 } 468 if len(createFilesystemOps) > 0 { 469 if err := createFilesystems(ctx, createFilesystemOps); err != nil { 470 return errors.Annotate(err, "creating filesystems") 471 } 472 } 473 if len(detachFilesystemOps) > 0 { 474 if err := detachFilesystems(ctx, detachFilesystemOps); err != nil { 475 return errors.Annotate(err, "detaching filesystems") 476 } 477 } 478 if len(attachFilesystemOps) > 0 { 479 if err := attachFilesystems(ctx, attachFilesystemOps); err != nil { 480 return errors.Annotate(err, "attaching filesystems") 481 } 482 } 483 return nil 484 } 485 486 type context struct { 487 kill func(error) 488 addWorker func(worker.Worker) error 489 config Config 490 491 // volumes contains information about provisioned volumes. 492 volumes map[names.VolumeTag]storage.Volume 493 494 // volumeAttachments contains information about attached volumes. 495 volumeAttachments map[params.MachineStorageId]storage.VolumeAttachment 496 497 // volumeBlockDevices contains information about block devices 498 // corresponding to volumes attached to the scope-machine. This 499 // is only used by the machine-scoped storage provisioner. 500 volumeBlockDevices map[names.VolumeTag]storage.BlockDevice 501 502 // filesystems contains information about provisioned filesystems. 503 filesystems map[names.FilesystemTag]storage.Filesystem 504 505 // filesystemAttachments contains information about attached filesystems. 506 filesystemAttachments map[params.MachineStorageId]storage.FilesystemAttachment 507 508 // machines contains information about machines in the worker's scope. 509 machines map[names.MachineTag]*machineWatcher 510 511 // machineChanges is a channel that machine watchers will send to once 512 // their machine is known to have been provisioned. 513 machineChanges chan<- names.MachineTag 514 515 // schedule is the schedule of storage operations. 516 schedule *schedule.Schedule 517 518 // incompleteVolumeParams contains incomplete parameters for volumes. 519 // 520 // Volume parameters are incomplete when they lack information about 521 // the initial attachment. Once the initial attachment information is 522 // available, the parameters are removed from this map and a volume 523 // creation operation is scheduled. 524 incompleteVolumeParams map[names.VolumeTag]storage.VolumeParams 525 526 // incompleteVolumeAttachmentParams contains incomplete parameters 527 // for volume attachments 528 // 529 // Volume attachment parameters are incomplete when they lack 530 // information about the associated volume or machine. Once this 531 // information is available, the parameters are removed from this 532 // map and a volume attachment operation is scheduled. 533 incompleteVolumeAttachmentParams map[params.MachineStorageId]storage.VolumeAttachmentParams 534 535 // incompleteFilesystemParams contains incomplete parameters for 536 // filesystems. 537 // 538 // Filesystem parameters are incomplete when they lack information 539 // about the initial attachment. Once the initial attachment 540 // information is available, the parameters are removed from this 541 // map and a filesystem creation operation is scheduled. 542 incompleteFilesystemParams map[names.FilesystemTag]storage.FilesystemParams 543 544 // incompleteFilesystemAttachmentParams contains incomplete parameters 545 // for filesystem attachments 546 // 547 // Filesystem attachment parameters are incomplete when they lack 548 // information about the associated filesystem or machine. Once this 549 // information is available, the parameters are removed from this 550 // map and a filesystem attachment operation is scheduled. 551 incompleteFilesystemAttachmentParams map[params.MachineStorageId]storage.FilesystemAttachmentParams 552 553 // pendingVolumeBlockDevices contains the tags of volumes about whose 554 // block devices we wish to enquire. 555 pendingVolumeBlockDevices names.Set 556 557 // managedFilesystemSource is a storage.FilesystemSource that 558 // manages filesystems backed by volumes attached to the host 559 // machine. 560 managedFilesystemSource storage.FilesystemSource 561 } 562 563 func (c *context) isApplicationKind() bool { 564 return c.config.Scope.Kind() == names.ApplicationTagKind 565 }