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