github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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 environment manager, which 9 // manages environment-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 "github.com/juju/names" 33 "github.com/juju/utils/clock" 34 "github.com/juju/utils/set" 35 "launchpad.net/tomb" 36 37 apiwatcher "github.com/juju/juju/api/watcher" 38 "github.com/juju/juju/apiserver/params" 39 "github.com/juju/juju/environs/config" 40 "github.com/juju/juju/state/watcher" 41 "github.com/juju/juju/storage" 42 "github.com/juju/juju/storage/provider" 43 "github.com/juju/juju/worker" 44 "github.com/juju/juju/worker/storageprovisioner/internal/schedule" 45 ) 46 47 var logger = loggo.GetLogger("juju.worker.storageprovisioner") 48 49 var newManagedFilesystemSource = provider.NewManagedFilesystemSource 50 51 // VolumeAccessor defines an interface used to allow a storage provisioner 52 // worker to perform volume related operations. 53 type VolumeAccessor interface { 54 // WatchBlockDevices watches for changes to the block devices of the 55 // specified machine. 56 WatchBlockDevices(names.MachineTag) (apiwatcher.NotifyWatcher, error) 57 58 // WatchVolumes watches for changes to volumes that this storage 59 // provisioner is responsible for. 60 WatchVolumes() (apiwatcher.StringsWatcher, error) 61 62 // WatchVolumeAttachments watches for changes to volume attachments 63 // that this storage provisioner is responsible for. 64 WatchVolumeAttachments() (apiwatcher.MachineStorageIdsWatcher, error) 65 66 // Volumes returns details of volumes with the specified tags. 67 Volumes([]names.VolumeTag) ([]params.VolumeResult, error) 68 69 // VolumeBlockDevices returns details of block devices corresponding to 70 // the specified volume attachment IDs. 71 VolumeBlockDevices([]params.MachineStorageId) ([]params.BlockDeviceResult, error) 72 73 // VolumeAttachments returns details of volume attachments with 74 // the specified tags. 75 VolumeAttachments([]params.MachineStorageId) ([]params.VolumeAttachmentResult, error) 76 77 // VolumeParams returns the parameters for creating the volumes 78 // with the specified tags. 79 VolumeParams([]names.VolumeTag) ([]params.VolumeParamsResult, error) 80 81 // VolumeAttachmentParams returns the parameters for creating the 82 // volume attachments with the specified tags. 83 VolumeAttachmentParams([]params.MachineStorageId) ([]params.VolumeAttachmentParamsResult, error) 84 85 // SetVolumeInfo records the details of newly provisioned volumes. 86 SetVolumeInfo([]params.Volume) ([]params.ErrorResult, error) 87 88 // SetVolumeAttachmentInfo records the details of newly provisioned 89 // volume attachments. 90 SetVolumeAttachmentInfo([]params.VolumeAttachment) ([]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() (apiwatcher.StringsWatcher, error) 99 100 // WatchFilesystemAttachments watches for changes to filesystem attachments 101 // that this storage provisioner is responsible for. 102 WatchFilesystemAttachments() (apiwatcher.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 // FilesystemAttachmentParams returns the parameters for creating the 116 // filesystem attachments with the specified tags. 117 FilesystemAttachmentParams([]params.MachineStorageId) ([]params.FilesystemAttachmentParamsResult, error) 118 119 // SetFilesystemInfo records the details of newly provisioned filesystems. 120 SetFilesystemInfo([]params.Filesystem) ([]params.ErrorResult, error) 121 122 // SetFilesystemAttachmentInfo records the details of newly provisioned 123 // filesystem attachments. 124 SetFilesystemAttachmentInfo([]params.FilesystemAttachment) ([]params.ErrorResult, error) 125 } 126 127 // MachineAccessor defines an interface used to allow a storage provisioner 128 // worker to perform machine related operations. 129 type MachineAccessor interface { 130 // WatchMachine watches for changes to the specified machine. 131 WatchMachine(names.MachineTag) (apiwatcher.NotifyWatcher, error) 132 133 // InstanceIds returns the instance IDs of each machine. 134 InstanceIds([]names.MachineTag) ([]params.StringResult, error) 135 } 136 137 // LifecycleManager defines an interface used to enable a storage provisioner 138 // worker to perform lifcycle-related operations on storage entities and 139 // attachments. 140 type LifecycleManager interface { 141 // Life returns the lifecycle state of the specified entities. 142 Life([]names.Tag) ([]params.LifeResult, error) 143 144 // Remove removes the specified entities from state. 145 Remove([]names.Tag) ([]params.ErrorResult, error) 146 147 // AttachmentLife returns the lifecycle state of the specified 148 // machine/entity attachments. 149 AttachmentLife([]params.MachineStorageId) ([]params.LifeResult, error) 150 151 // RemoveAttachments removes the specified machine/entity attachments 152 // from state. 153 RemoveAttachments([]params.MachineStorageId) ([]params.ErrorResult, error) 154 } 155 156 // StatusSetter defines an interface used to set the status of entities. 157 type StatusSetter interface { 158 SetStatus([]params.EntityStatusArgs) error 159 } 160 161 // EnvironAccessor defines an interface used to enable a storage provisioner 162 // worker to watch changes to and read environment config, to use when 163 // provisioning storage. 164 type EnvironAccessor interface { 165 // WatchForEnvironConfigChanges returns a watcher that will be notified 166 // whenever the environment config changes in state. 167 WatchForEnvironConfigChanges() (apiwatcher.NotifyWatcher, error) 168 169 // EnvironConfig returns the current environment config. 170 EnvironConfig() (*config.Config, error) 171 } 172 173 // NewStorageProvisioner returns a Worker which manages 174 // provisioning (deprovisioning), and attachment (detachment) 175 // of first-class volumes and filesystems. 176 // 177 // Machine-scoped storage workers will be provided with 178 // a storage directory, while environment-scoped workers 179 // will not. If the directory path is non-empty, then it 180 // will be passed to the storage source via its config. 181 func NewStorageProvisioner( 182 scope names.Tag, 183 storageDir string, 184 v VolumeAccessor, 185 f FilesystemAccessor, 186 l LifecycleManager, 187 e EnvironAccessor, 188 m MachineAccessor, 189 s StatusSetter, 190 clock clock.Clock, 191 ) worker.Worker { 192 w := &storageprovisioner{ 193 scope: scope, 194 storageDir: storageDir, 195 volumes: v, 196 filesystems: f, 197 life: l, 198 environ: e, 199 machines: m, 200 status: s, 201 clock: clock, 202 } 203 go func() { 204 defer w.tomb.Done() 205 err := w.loop() 206 if err != tomb.ErrDying { 207 logger.Errorf("%s", err) 208 } 209 w.tomb.Kill(err) 210 }() 211 return w 212 } 213 214 type storageprovisioner struct { 215 tomb tomb.Tomb 216 scope names.Tag 217 storageDir string 218 volumes VolumeAccessor 219 filesystems FilesystemAccessor 220 life LifecycleManager 221 environ EnvironAccessor 222 machines MachineAccessor 223 status StatusSetter 224 clock clock.Clock 225 } 226 227 // Kill implements Worker.Kill(). 228 func (w *storageprovisioner) Kill() { 229 w.tomb.Kill(nil) 230 } 231 232 // Wait implements Worker.Wait(). 233 func (w *storageprovisioner) Wait() error { 234 return w.tomb.Wait() 235 } 236 237 func (w *storageprovisioner) loop() error { 238 var environConfigChanges <-chan struct{} 239 var volumesWatcher apiwatcher.StringsWatcher 240 var filesystemsWatcher apiwatcher.StringsWatcher 241 var volumesChanges <-chan []string 242 var filesystemsChanges <-chan []string 243 var volumeAttachmentsWatcher apiwatcher.MachineStorageIdsWatcher 244 var filesystemAttachmentsWatcher apiwatcher.MachineStorageIdsWatcher 245 var volumeAttachmentsChanges <-chan []params.MachineStorageId 246 var filesystemAttachmentsChanges <-chan []params.MachineStorageId 247 var machineBlockDevicesWatcher apiwatcher.NotifyWatcher 248 var machineBlockDevicesChanges <-chan struct{} 249 machineChanges := make(chan names.MachineTag) 250 251 environConfigWatcher, err := w.environ.WatchForEnvironConfigChanges() 252 if err != nil { 253 return errors.Annotate(err, "watching environ config") 254 } 255 defer watcher.Stop(environConfigWatcher, &w.tomb) 256 environConfigChanges = environConfigWatcher.Changes() 257 258 // Machine-scoped provisioners need to watch block devices, to create 259 // volume-backed filesystems. 260 if machineTag, ok := w.scope.(names.MachineTag); ok { 261 machineBlockDevicesWatcher, err = w.volumes.WatchBlockDevices(machineTag) 262 if err != nil { 263 return errors.Annotate(err, "watching block devices") 264 } 265 defer watcher.Stop(machineBlockDevicesWatcher, &w.tomb) 266 machineBlockDevicesChanges = machineBlockDevicesWatcher.Changes() 267 } 268 269 // The other watchers are started dynamically; stop only if started. 270 defer w.maybeStopWatcher(volumesWatcher) 271 defer w.maybeStopWatcher(volumeAttachmentsWatcher) 272 defer w.maybeStopWatcher(filesystemsWatcher) 273 defer w.maybeStopWatcher(filesystemAttachmentsWatcher) 274 275 startWatchers := func() error { 276 var err error 277 volumesWatcher, err = w.volumes.WatchVolumes() 278 if err != nil { 279 return errors.Annotate(err, "watching volumes") 280 } 281 filesystemsWatcher, err = w.filesystems.WatchFilesystems() 282 if err != nil { 283 return errors.Annotate(err, "watching filesystems") 284 } 285 volumeAttachmentsWatcher, err = w.volumes.WatchVolumeAttachments() 286 if err != nil { 287 return errors.Annotate(err, "watching volume attachments") 288 } 289 filesystemAttachmentsWatcher, err = w.filesystems.WatchFilesystemAttachments() 290 if err != nil { 291 return errors.Annotate(err, "watching filesystem attachments") 292 } 293 volumesChanges = volumesWatcher.Changes() 294 filesystemsChanges = filesystemsWatcher.Changes() 295 volumeAttachmentsChanges = volumeAttachmentsWatcher.Changes() 296 filesystemAttachmentsChanges = filesystemAttachmentsWatcher.Changes() 297 return nil 298 } 299 300 ctx := context{ 301 scope: w.scope, 302 storageDir: w.storageDir, 303 volumeAccessor: w.volumes, 304 filesystemAccessor: w.filesystems, 305 life: w.life, 306 machineAccessor: w.machines, 307 statusSetter: w.status, 308 time: w.clock, 309 volumes: make(map[names.VolumeTag]storage.Volume), 310 volumeAttachments: make(map[params.MachineStorageId]storage.VolumeAttachment), 311 volumeBlockDevices: make(map[names.VolumeTag]storage.BlockDevice), 312 filesystems: make(map[names.FilesystemTag]storage.Filesystem), 313 filesystemAttachments: make(map[params.MachineStorageId]storage.FilesystemAttachment), 314 machines: make(map[names.MachineTag]*machineWatcher), 315 machineChanges: machineChanges, 316 schedule: schedule.NewSchedule(w.clock), 317 pendingVolumeBlockDevices: make(set.Tags), 318 incompleteVolumeParams: make(map[names.VolumeTag]storage.VolumeParams), 319 incompleteVolumeAttachmentParams: make(map[params.MachineStorageId]storage.VolumeAttachmentParams), 320 pendingFilesystems: make(map[names.FilesystemTag]storage.FilesystemParams), 321 pendingFilesystemAttachments: make(map[params.MachineStorageId]storage.FilesystemAttachmentParams), 322 pendingDyingFilesystemAttachments: make(map[params.MachineStorageId]storage.FilesystemAttachmentParams), 323 } 324 ctx.managedFilesystemSource = newManagedFilesystemSource( 325 ctx.volumeBlockDevices, ctx.filesystems, 326 ) 327 defer func() { 328 for _, w := range ctx.machines { 329 w.stop() 330 } 331 }() 332 333 for { 334 // Check if any pending operations can be fulfilled. 335 if err := processPending(&ctx); err != nil { 336 return errors.Trace(err) 337 } 338 339 select { 340 case <-w.tomb.Dying(): 341 return tomb.ErrDying 342 case _, ok := <-environConfigChanges: 343 if !ok { 344 return watcher.EnsureErr(environConfigWatcher) 345 } 346 environConfig, err := w.environ.EnvironConfig() 347 if err != nil { 348 return errors.Annotate(err, "getting environ config") 349 } 350 if ctx.environConfig == nil { 351 // We've received the initial environ config, 352 // so we can begin provisioning storage. 353 if err := startWatchers(); err != nil { 354 return err 355 } 356 } 357 ctx.environConfig = environConfig 358 case changes, ok := <-volumesChanges: 359 if !ok { 360 return watcher.EnsureErr(volumesWatcher) 361 } 362 if err := volumesChanged(&ctx, changes); err != nil { 363 return errors.Trace(err) 364 } 365 case changes, ok := <-volumeAttachmentsChanges: 366 if !ok { 367 return watcher.EnsureErr(volumeAttachmentsWatcher) 368 } 369 if err := volumeAttachmentsChanged(&ctx, changes); err != nil { 370 return errors.Trace(err) 371 } 372 case changes, ok := <-filesystemsChanges: 373 if !ok { 374 return watcher.EnsureErr(filesystemsWatcher) 375 } 376 if err := filesystemsChanged(&ctx, changes); err != nil { 377 return errors.Trace(err) 378 } 379 case changes, ok := <-filesystemAttachmentsChanges: 380 if !ok { 381 return watcher.EnsureErr(filesystemAttachmentsWatcher) 382 } 383 if err := filesystemAttachmentsChanged(&ctx, changes); err != nil { 384 return errors.Trace(err) 385 } 386 case _, ok := <-machineBlockDevicesChanges: 387 if !ok { 388 return watcher.EnsureErr(machineBlockDevicesWatcher) 389 } 390 if err := machineBlockDevicesChanged(&ctx); err != nil { 391 return errors.Trace(err) 392 } 393 case machineTag := <-machineChanges: 394 if err := refreshMachine(&ctx, machineTag); err != nil { 395 return errors.Trace(err) 396 } 397 case <-ctx.schedule.Next(): 398 // Ready to pick something(s) off the pending queue. 399 if err := processSchedule(&ctx); err != nil { 400 return errors.Trace(err) 401 } 402 } 403 } 404 } 405 406 // processPending checks if the pending operations' prerequisites have 407 // been met, and processes them if so. 408 func processPending(ctx *context) error { 409 if err := processPendingVolumeBlockDevices(ctx); err != nil { 410 return errors.Annotate(err, "processing pending block devices") 411 } 412 // TODO(axw) below should be handled by processSchedule. 413 if err := processPendingFilesystems(ctx); err != nil { 414 return errors.Annotate(err, "processing pending filesystems") 415 } 416 if err := processPendingDyingFilesystemAttachments(ctx); err != nil { 417 return errors.Annotate(err, "processing pending, dying filesystem attachments") 418 } 419 if err := processPendingFilesystemAttachments(ctx); err != nil { 420 return errors.Annotate(err, "processing pending filesystem attachments") 421 } 422 return nil 423 } 424 425 // processSchedule executes scheduled operations. 426 func processSchedule(ctx *context) error { 427 ready := ctx.schedule.Ready(ctx.time.Now()) 428 createVolumeOps := make(map[names.VolumeTag]*createVolumeOp) 429 destroyVolumeOps := make(map[names.VolumeTag]*destroyVolumeOp) 430 attachVolumeOps := make(map[params.MachineStorageId]*attachVolumeOp) 431 detachVolumeOps := make(map[params.MachineStorageId]*detachVolumeOp) 432 for _, item := range ready { 433 op := item.(scheduleOp) 434 key := op.key() 435 switch op := op.(type) { 436 case *createVolumeOp: 437 createVolumeOps[key.(names.VolumeTag)] = op 438 case *destroyVolumeOp: 439 destroyVolumeOps[key.(names.VolumeTag)] = op 440 case *attachVolumeOp: 441 attachVolumeOps[key.(params.MachineStorageId)] = op 442 case *detachVolumeOp: 443 detachVolumeOps[key.(params.MachineStorageId)] = op 444 } 445 } 446 if len(destroyVolumeOps) > 0 { 447 if err := destroyVolumes(ctx, destroyVolumeOps); err != nil { 448 return errors.Annotate(err, "destroying volumes") 449 } 450 } 451 if len(createVolumeOps) > 0 { 452 if err := createVolumes(ctx, createVolumeOps); err != nil { 453 return errors.Annotate(err, "creating volumes") 454 } 455 } 456 if len(detachVolumeOps) > 0 { 457 if err := detachVolumes(ctx, detachVolumeOps); err != nil { 458 return errors.Annotate(err, "detaching volumes") 459 } 460 } 461 if len(attachVolumeOps) > 0 { 462 if err := attachVolumes(ctx, attachVolumeOps); err != nil { 463 return errors.Annotate(err, "attaching volumes") 464 } 465 } 466 return nil 467 } 468 469 func (p *storageprovisioner) maybeStopWatcher(w watcher.Stopper) { 470 if w != nil { 471 watcher.Stop(w, &p.tomb) 472 } 473 } 474 475 type context struct { 476 scope names.Tag 477 environConfig *config.Config 478 storageDir string 479 volumeAccessor VolumeAccessor 480 filesystemAccessor FilesystemAccessor 481 life LifecycleManager 482 machineAccessor MachineAccessor 483 statusSetter StatusSetter 484 time clock.Clock 485 486 // volumes contains information about provisioned volumes. 487 volumes map[names.VolumeTag]storage.Volume 488 489 // volumeAttachments contains information about attached volumes. 490 volumeAttachments map[params.MachineStorageId]storage.VolumeAttachment 491 492 // volumeBlockDevices contains information about block devices 493 // corresponding to volumes attached to the scope-machine. This 494 // is only used by the machine-scoped storage provisioner. 495 volumeBlockDevices map[names.VolumeTag]storage.BlockDevice 496 497 // filesystems contains information about provisioned filesystems. 498 filesystems map[names.FilesystemTag]storage.Filesystem 499 500 // filesystemAttachments contains information about attached filesystems. 501 filesystemAttachments map[params.MachineStorageId]storage.FilesystemAttachment 502 503 // machines contains information about machines in the worker's scope. 504 machines map[names.MachineTag]*machineWatcher 505 506 // machineChanges is a channel that machine watchers will send to once 507 // their machine is known to have been provisioned. 508 machineChanges chan<- names.MachineTag 509 510 // schedule is the schedule of storage operations. 511 schedule *schedule.Schedule 512 513 // incompleteVolumeParams contains incomplete parameters for volumes. 514 // 515 // Volume parameters are incomplete when they lack information about 516 // the initial attachment. Once the initial attachment information is 517 // available, the parameters are removed from this map and a volume 518 // creation operation is scheduled. 519 incompleteVolumeParams map[names.VolumeTag]storage.VolumeParams 520 521 // incompleteVolumeAttachmentParams contains incomplete parameters 522 // for volume attachments 523 // 524 // Volume attachment parameters are incomplete when they lack 525 // information about the associated volume or machine. Once this 526 // information is available, the parameters are removed from this 527 // map and a volume attachment operation is scheduled. 528 incompleteVolumeAttachmentParams map[params.MachineStorageId]storage.VolumeAttachmentParams 529 530 // pendingVolumeBlockDevices contains the tags of volumes about whose 531 // block devices we wish to enquire. 532 pendingVolumeBlockDevices set.Tags 533 534 // pendingFilesystems contains parameters for filesystems that are 535 // yet to be created. 536 pendingFilesystems map[names.FilesystemTag]storage.FilesystemParams 537 538 // pendingFilesystemAttachments contains parameters for filesystem attachments 539 // that are yet to be created. 540 pendingFilesystemAttachments map[params.MachineStorageId]storage.FilesystemAttachmentParams 541 542 // pendingDyingFilesystemAttachments contains parameters for filesystem attachments 543 // that are to be destroyed. 544 pendingDyingFilesystemAttachments map[params.MachineStorageId]storage.FilesystemAttachmentParams 545 546 // managedFilesystemSource is a storage.FilesystemSource that 547 // manages filesystems backed by volumes attached to the host 548 // machine. 549 managedFilesystemSource storage.FilesystemSource 550 }