github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/storageprovisioner/volume_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/life" 12 "github.com/juju/juju/core/watcher" 13 "github.com/juju/juju/rpc/params" 14 "github.com/juju/juju/storage" 15 "github.com/juju/juju/storage/plans" 16 "github.com/juju/juju/wrench" 17 ) 18 19 // volumesChanged is called when the lifecycle states of the volumes 20 // with the provided IDs have been seen to have changed. 21 func volumesChanged(ctx *context, changes []string) error { 22 tags := make([]names.Tag, len(changes)) 23 for i, change := range changes { 24 tags[i] = names.NewVolumeTag(change) 25 } 26 alive, dying, dead, err := storageEntityLife(ctx, tags) 27 if err != nil { 28 return errors.Trace(err) 29 } 30 ctx.config.Logger.Debugf("volumes alive: %v, dying: %v, dead: %v", alive, dying, dead) 31 if err := processDyingVolumes(ctx, dying); err != nil { 32 return errors.Annotate(err, "processing dying volumes") 33 } 34 if len(alive)+len(dead) == 0 { 35 return nil 36 } 37 38 // Get volume information for alive and dead volumes, so 39 // we can provision/deprovision. 40 volumeTags := make([]names.VolumeTag, 0, len(alive)+len(dead)) 41 for _, tag := range alive { 42 volumeTags = append(volumeTags, tag.(names.VolumeTag)) 43 } 44 for _, tag := range dead { 45 volumeTags = append(volumeTags, tag.(names.VolumeTag)) 46 } 47 volumeResults, err := ctx.config.Volumes.Volumes(volumeTags) 48 if err != nil { 49 return errors.Annotatef(err, "getting volume information") 50 } 51 if err := processDeadVolumes(ctx, volumeTags[len(alive):], volumeResults[len(alive):]); err != nil { 52 return errors.Annotate(err, "deprovisioning volumes") 53 } 54 if err := processAliveVolumes(ctx, alive, volumeResults[:len(alive)]); err != nil { 55 return errors.Annotate(err, "provisioning volumes") 56 } 57 return nil 58 } 59 60 func sortVolumeAttachmentPlans(ctx *context, ids []params.MachineStorageId) ( 61 alive, dying, dead []params.VolumeAttachmentPlanResult, err error) { 62 plans, err := ctx.config.Volumes.VolumeAttachmentPlans(ids) 63 if err != nil { 64 return nil, nil, nil, errors.Trace(err) 65 } 66 ctx.config.Logger.Debugf("Found plans: %v", plans) 67 for _, plan := range plans { 68 switch plan.Result.Life { 69 case life.Alive: 70 alive = append(alive, plan) 71 case life.Dying: 72 dying = append(dying, plan) 73 case life.Dead: 74 dead = append(dead, plan) 75 } 76 } 77 return 78 } 79 80 func volumeAttachmentPlansChanged(ctx *context, watcherIds []watcher.MachineStorageId) error { 81 ctx.config.Logger.Debugf("Got machine storage ids: %v", watcherIds) 82 ids := copyMachineStorageIds(watcherIds) 83 alive, dying, dead, err := sortVolumeAttachmentPlans(ctx, ids) 84 if err != nil { 85 return errors.Trace(err) 86 } 87 ctx.config.Logger.Debugf("volume attachment plans alive: %v, dying: %v, dead: %v", alive, dying, dead) 88 89 if err := processAliveVolumePlans(ctx, alive); err != nil { 90 return err 91 } 92 93 if err := processDyingVolumePlans(ctx, dying); err != nil { 94 return err 95 } 96 return nil 97 } 98 99 func processAliveVolumePlans(ctx *context, volumePlans []params.VolumeAttachmentPlanResult) error { 100 volumeAttachmentPlans := make([]params.VolumeAttachmentPlan, len(volumePlans)) 101 volumeTags := make([]names.VolumeTag, len(volumePlans)) 102 for i, val := range volumePlans { 103 volumeAttachmentPlans[i] = val.Result 104 tag, err := names.ParseVolumeTag(val.Result.VolumeTag) 105 if err != nil { 106 return errors.Trace(err) 107 } 108 volumeTags[i] = tag 109 } 110 111 for idx, val := range volumeAttachmentPlans { 112 volPlan, err := plans.PlanByType(val.PlanInfo.DeviceType) 113 if err != nil { 114 if !errors.IsNotFound(err) { 115 return errors.Trace(err) 116 } 117 continue 118 } 119 if blockDeviceInfo, err := volPlan.AttachVolume(val.PlanInfo.DeviceAttributes); err != nil { 120 return errors.Trace(err) 121 } else { 122 volumeAttachmentPlans[idx].BlockDevice = blockDeviceInfo 123 } 124 } 125 126 results, err := ctx.config.Volumes.SetVolumeAttachmentPlanBlockInfo(volumeAttachmentPlans) 127 if err != nil { 128 return errors.Trace(err) 129 } 130 for _, result := range results { 131 if result.Error != nil { 132 return errors.Errorf("failed to publish block info to state: %s", result.Error) 133 } 134 } 135 _, err = refreshVolumeBlockDevices(ctx, volumeTags) 136 return err 137 } 138 139 func processDyingVolumePlans(ctx *context, volumePlans []params.VolumeAttachmentPlanResult) error { 140 ids := volumePlansToMachineIds(volumePlans) 141 for _, val := range volumePlans { 142 volPlan, err := plans.PlanByType(val.Result.PlanInfo.DeviceType) 143 if err != nil { 144 if !errors.IsNotFound(err) { 145 return errors.Trace(err) 146 } 147 continue 148 } 149 if err := volPlan.DetachVolume(val.Result.PlanInfo.DeviceAttributes); err != nil { 150 return errors.Trace(err) 151 } 152 if wrench.IsActive("storageprovisioner", "DetachVolume") { 153 return errors.New("wrench active") 154 } 155 } 156 results, err := ctx.config.Volumes.RemoveVolumeAttachmentPlan(ids) 157 if err != nil { 158 return err 159 } 160 for _, result := range results { 161 if result.Error != nil { 162 return errors.Annotate(result.Error, "removing volume plan") 163 } 164 } 165 return nil 166 } 167 168 func volumePlansToMachineIds(plans []params.VolumeAttachmentPlanResult) []params.MachineStorageId { 169 storageIds := make([]params.MachineStorageId, len(plans)) 170 for i, plan := range plans { 171 storageIds[i] = params.MachineStorageId{ 172 MachineTag: plan.Result.MachineTag, 173 AttachmentTag: plan.Result.VolumeTag, 174 } 175 } 176 return storageIds 177 } 178 179 // volumeAttachmentsChanged is called when the lifecycle states of the volume 180 // attachments with the provided IDs have been seen to have changed. 181 func volumeAttachmentsChanged(ctx *context, watcherIds []watcher.MachineStorageId) error { 182 ids := copyMachineStorageIds(watcherIds) 183 alive, dying, dead, gone, err := attachmentLife(ctx, ids) 184 if err != nil { 185 return errors.Trace(err) 186 } 187 ctx.config.Logger.Debugf("volume attachments alive: %v, dying: %v, dead: %v", alive, dying, dead) 188 if len(dead) != 0 { 189 // We should not see dead volume attachments; 190 // attachments go directly from Dying to removed. 191 ctx.config.Logger.Warningf("unexpected dead volume attachments: %v", dead) 192 } 193 // Clean up any attachments which have been removed. 194 for _, id := range gone { 195 delete(ctx.volumeAttachments, id) 196 } 197 if len(alive)+len(dying) == 0 { 198 return nil 199 } 200 201 // Get volume information for alive and dying volume attachments, so 202 // we can attach/detach. 203 ids = append(alive, dying...) 204 volumeAttachmentResults, err := ctx.config.Volumes.VolumeAttachments(ids) 205 if err != nil { 206 return errors.Annotatef(err, "getting volume attachment information") 207 } 208 209 // Deprovision Dying volume attachments. 210 dyingVolumeAttachmentResults := volumeAttachmentResults[len(alive):] 211 if err := processDyingVolumeAttachments(ctx, dying, dyingVolumeAttachmentResults); err != nil { 212 return errors.Annotate(err, "deprovisioning volume attachments") 213 } 214 215 // Provision Alive volume attachments. 216 aliveVolumeAttachmentResults := volumeAttachmentResults[:len(alive)] 217 if err := processAliveVolumeAttachments(ctx, alive, aliveVolumeAttachmentResults); err != nil { 218 return errors.Annotate(err, "provisioning volumes") 219 } 220 221 return nil 222 } 223 224 // processDyingVolumes processes the VolumeResults for Dying volumes, 225 // removing them from provisioning-pending as necessary. 226 func processDyingVolumes(ctx *context, tags []names.Tag) error { 227 if ctx.isApplicationKind() { 228 // only care dead for application. 229 return nil 230 } 231 for _, tag := range tags { 232 removePendingVolume(ctx, tag.(names.VolumeTag)) 233 } 234 return nil 235 } 236 237 // updateVolume updates the context with the given volume info. 238 func updateVolume(ctx *context, info storage.Volume) { 239 ctx.volumes[info.Tag] = info 240 for id, params := range ctx.incompleteVolumeAttachmentParams { 241 if params.VolumeId == "" && id.AttachmentTag == info.Tag.String() { 242 params.VolumeId = info.VolumeId 243 updatePendingVolumeAttachment(ctx, id, params) 244 } 245 } 246 } 247 248 // updatePendingVolume adds the given volume params to either the incomplete 249 // set or the schedule. If the params are incomplete due to a missing instance 250 // ID, updatePendingVolume will request that the machine be watched so its 251 // instance ID can be learned. 252 func updatePendingVolume(ctx *context, params storage.VolumeParams) { 253 if params.Attachment == nil { 254 // NOTE(axw) this would only happen if the model is 255 // in an incoherent state; we should never have an 256 // alive, unprovisioned, and unattached volume. 257 ctx.config.Logger.Warningf( 258 "%s is in an incoherent state, ignoring", 259 names.ReadableString(params.Tag), 260 ) 261 return 262 } 263 if params.Attachment.InstanceId == "" { 264 watchMachine(ctx, params.Attachment.Machine.(names.MachineTag)) 265 ctx.incompleteVolumeParams[params.Tag] = params 266 } else { 267 delete(ctx.incompleteVolumeParams, params.Tag) 268 scheduleOperations(ctx, &createVolumeOp{args: params}) 269 } 270 } 271 272 // removePendingVolume removes the specified pending volume from the 273 // incomplete set and/or the schedule if it exists there. 274 func removePendingVolume(ctx *context, tag names.VolumeTag) { 275 delete(ctx.incompleteVolumeParams, tag) 276 ctx.schedule.Remove(tag) 277 } 278 279 // updatePendingVolumeAttachment adds the given volume attachment params to 280 // either the incomplete set or the schedule. If the params are incomplete 281 // due to a missing instance ID, updatePendingVolumeAttachment will request 282 // that the machine be watched so its instance ID can be learned. 283 func updatePendingVolumeAttachment( 284 ctx *context, 285 id params.MachineStorageId, 286 params storage.VolumeAttachmentParams, 287 ) { 288 if params.InstanceId == "" { 289 watchMachine(ctx, params.Machine.(names.MachineTag)) 290 } else if params.VolumeId != "" { 291 delete(ctx.incompleteVolumeAttachmentParams, id) 292 scheduleOperations(ctx, &attachVolumeOp{args: params}) 293 return 294 } 295 ctx.incompleteVolumeAttachmentParams[id] = params 296 } 297 298 // removePendingVolumeAttachment removes the specified pending volume 299 // attachment from the incomplete set and/or the schedule if it exists 300 // there. 301 func removePendingVolumeAttachment(ctx *context, id params.MachineStorageId) { 302 delete(ctx.incompleteVolumeAttachmentParams, id) 303 ctx.schedule.Remove(id) 304 } 305 306 // processDeadVolumes processes the VolumeResults for Dead volumes, 307 // deprovisioning volumes and removing from state as necessary. 308 func processDeadVolumes(ctx *context, tags []names.VolumeTag, volumeResults []params.VolumeResult) error { 309 for _, tag := range tags { 310 removePendingVolume(ctx, tag) 311 } 312 var destroy []names.VolumeTag 313 var remove []names.Tag 314 for i, result := range volumeResults { 315 tag := tags[i] 316 if result.Error == nil { 317 ctx.config.Logger.Debugf("volume %s is provisioned, queuing for deprovisioning", tag.Id()) 318 volume, err := volumeFromParams(result.Result) 319 if err != nil { 320 return errors.Annotate(err, "getting volume info") 321 } 322 updateVolume(ctx, volume) 323 destroy = append(destroy, tag) 324 continue 325 } 326 if params.IsCodeNotProvisioned(result.Error) { 327 ctx.config.Logger.Debugf("volume %s is not provisioned, queuing for removal", tag.Id()) 328 remove = append(remove, tag) 329 continue 330 } 331 return errors.Annotatef(result.Error, "getting volume information for volume %s", tag.Id()) 332 } 333 if len(destroy) > 0 { 334 ops := make([]scheduleOp, len(destroy)) 335 for i, tag := range destroy { 336 ops[i] = &removeVolumeOp{tag: tag} 337 } 338 scheduleOperations(ctx, ops...) 339 } 340 if err := removeEntities(ctx, remove); err != nil { 341 return errors.Annotate(err, "removing volumes from state") 342 } 343 return nil 344 } 345 346 // processDyingVolumeAttachments processes the VolumeAttachmentResults for 347 // Dying volume attachments, detaching volumes and updating state as necessary. 348 func processDyingVolumeAttachments( 349 ctx *context, 350 ids []params.MachineStorageId, 351 volumeAttachmentResults []params.VolumeAttachmentResult, 352 ) error { 353 for _, id := range ids { 354 removePendingVolumeAttachment(ctx, id) 355 } 356 detach := make([]params.MachineStorageId, 0, len(ids)) 357 remove := make([]params.MachineStorageId, 0, len(ids)) 358 for i, result := range volumeAttachmentResults { 359 id := ids[i] 360 if result.Error == nil { 361 detach = append(detach, id) 362 continue 363 } 364 if params.IsCodeNotProvisioned(result.Error) { 365 remove = append(remove, id) 366 continue 367 } 368 return errors.Annotatef(result.Error, "getting information for volume attachment %v", id) 369 } 370 if len(detach) > 0 { 371 attachmentParams, err := volumeAttachmentParams(ctx, detach) 372 if err != nil { 373 return errors.Trace(err) 374 } 375 ops := make([]scheduleOp, len(attachmentParams)) 376 for i, p := range attachmentParams { 377 ops[i] = &detachVolumeOp{args: p} 378 } 379 scheduleOperations(ctx, ops...) 380 } 381 if err := removeAttachments(ctx, remove); err != nil { 382 return errors.Annotate(err, "removing attachments from state") 383 } 384 for _, id := range remove { 385 delete(ctx.volumeAttachments, id) 386 } 387 return nil 388 } 389 390 // processAliveVolumes processes the VolumeResults for Alive volumes, 391 // provisioning volumes and setting the info in state as necessary. 392 func processAliveVolumes(ctx *context, tags []names.Tag, volumeResults []params.VolumeResult) error { 393 if ctx.isApplicationKind() { 394 // only care dead for application kind. 395 return nil 396 } 397 398 // Filter out the already-provisioned volumes. 399 pending := make([]names.VolumeTag, 0, len(tags)) 400 for i, result := range volumeResults { 401 volumeTag := tags[i].(names.VolumeTag) 402 if result.Error == nil { 403 // Volume is already provisioned: skip. 404 ctx.config.Logger.Debugf("volume %q is already provisioned, nothing to do", tags[i].Id()) 405 volume, err := volumeFromParams(result.Result) 406 if err != nil { 407 return errors.Annotate(err, "getting volume info") 408 } 409 updateVolume(ctx, volume) 410 removePendingVolume(ctx, volumeTag) 411 continue 412 } 413 if !params.IsCodeNotProvisioned(result.Error) { 414 return errors.Annotatef( 415 result.Error, "getting volume information for volume %q", tags[i].Id(), 416 ) 417 } 418 // The volume has not yet been provisioned, so record its tag 419 // to enquire about parameters below. 420 pending = append(pending, volumeTag) 421 } 422 if len(pending) == 0 { 423 return nil 424 } 425 volumeParams, err := volumeParams(ctx, pending) 426 if err != nil { 427 return errors.Annotate(err, "getting volume params") 428 } 429 for _, params := range volumeParams { 430 if params.Attachment != nil && params.Attachment.Machine.Kind() != names.MachineTagKind { 431 ctx.config.Logger.Debugf("not queuing volume for non-machine %v", params.Attachment.Machine) 432 continue 433 } 434 updatePendingVolume(ctx, params) 435 } 436 return nil 437 } 438 439 // processAliveVolumeAttachments processes the VolumeAttachmentResults 440 // for Alive volume attachments, attaching volumes and setting the info 441 // in state as necessary. 442 func processAliveVolumeAttachments( 443 ctx *context, 444 ids []params.MachineStorageId, 445 volumeAttachmentResults []params.VolumeAttachmentResult, 446 ) error { 447 // Filter out the already-attached. 448 pending := make([]params.MachineStorageId, 0, len(ids)) 449 for i, result := range volumeAttachmentResults { 450 if result.Error == nil { 451 // Volume attachment is already provisioned: if we 452 // didn't (re)attach in this session, then we must 453 // do so now. 454 action := "nothing to do" 455 if _, ok := ctx.volumeAttachments[ids[i]]; !ok { 456 // Not yet (re)attached in this session. 457 pending = append(pending, ids[i]) 458 action = "will reattach" 459 } 460 ctx.config.Logger.Debugf( 461 "%s is already attached to %s, %s", 462 ids[i].AttachmentTag, ids[i].MachineTag, action, 463 ) 464 removePendingVolumeAttachment(ctx, ids[i]) 465 continue 466 } 467 if !params.IsCodeNotProvisioned(result.Error) { 468 return errors.Annotatef( 469 result.Error, "getting information for attachment %v", ids[i], 470 ) 471 } 472 // The volume has not yet been provisioned, so record its tag 473 // to enquire about parameters below. 474 pending = append(pending, ids[i]) 475 } 476 if len(pending) == 0 { 477 return nil 478 } 479 params, err := volumeAttachmentParams(ctx, pending) 480 if err != nil { 481 return errors.Trace(err) 482 } 483 for i, params := range params { 484 if params.Machine.Kind() != names.MachineTagKind { 485 ctx.config.Logger.Debugf("not queuing volume attachment for non-machine %v", params.Machine) 486 continue 487 } 488 if volume, ok := ctx.volumes[params.Volume]; ok { 489 params.VolumeId = volume.VolumeId 490 } 491 updatePendingVolumeAttachment(ctx, pending[i], params) 492 } 493 return nil 494 } 495 496 // volumeAttachmentParams obtains the specified attachments' parameters. 497 func volumeAttachmentParams( 498 ctx *context, ids []params.MachineStorageId, 499 ) ([]storage.VolumeAttachmentParams, error) { 500 paramsResults, err := ctx.config.Volumes.VolumeAttachmentParams(ids) 501 if err != nil { 502 return nil, errors.Annotate(err, "getting volume attachment params") 503 } 504 attachmentParams := make([]storage.VolumeAttachmentParams, len(ids)) 505 for i, result := range paramsResults { 506 if result.Error != nil { 507 return nil, errors.Annotate(result.Error, "getting volume attachment parameters") 508 } 509 params, err := volumeAttachmentParamsFromParams(result.Result) 510 if err != nil { 511 return nil, errors.Annotate(err, "getting volume attachment parameters") 512 } 513 attachmentParams[i] = params 514 } 515 return attachmentParams, nil 516 } 517 518 // volumeParams obtains the specified volumes' parameters. 519 func volumeParams(ctx *context, tags []names.VolumeTag) ([]storage.VolumeParams, error) { 520 paramsResults, err := ctx.config.Volumes.VolumeParams(tags) 521 if err != nil { 522 return nil, errors.Annotate(err, "getting volume params") 523 } 524 allParams := make([]storage.VolumeParams, len(tags)) 525 for i, result := range paramsResults { 526 if result.Error != nil { 527 return nil, errors.Annotate(result.Error, "getting volume parameters") 528 } 529 params, err := volumeParamsFromParams(result.Result) 530 if err != nil { 531 return nil, errors.Annotate(err, "getting volume parameters") 532 } 533 allParams[i] = params 534 } 535 return allParams, nil 536 } 537 538 // removeVolumeParams obtains the specified volumes' destruction parameters. 539 func removeVolumeParams(ctx *context, tags []names.VolumeTag) ([]params.RemoveVolumeParams, error) { 540 paramsResults, err := ctx.config.Volumes.RemoveVolumeParams(tags) 541 if err != nil { 542 return nil, errors.Annotate(err, "getting volume params") 543 } 544 allParams := make([]params.RemoveVolumeParams, len(tags)) 545 for i, result := range paramsResults { 546 if result.Error != nil { 547 return nil, errors.Annotate(result.Error, "getting volume removal parameters") 548 } 549 allParams[i] = result.Result 550 } 551 return allParams, nil 552 } 553 554 func volumesFromStorage(in []storage.Volume) []params.Volume { 555 out := make([]params.Volume, len(in)) 556 for i, v := range in { 557 out[i] = params.Volume{ 558 v.Tag.String(), 559 params.VolumeInfo{ 560 v.VolumeId, 561 v.HardwareId, 562 v.WWN, 563 "", // pool 564 v.Size, 565 v.Persistent, 566 }, 567 } 568 } 569 return out 570 } 571 572 func volumeAttachmentsFromStorage(in []storage.VolumeAttachment) []params.VolumeAttachment { 573 out := make([]params.VolumeAttachment, len(in)) 574 for i, v := range in { 575 planInfo := ¶ms.VolumeAttachmentPlanInfo{} 576 if v.PlanInfo != nil { 577 planInfo.DeviceType = v.PlanInfo.DeviceType 578 planInfo.DeviceAttributes = v.PlanInfo.DeviceAttributes 579 } else { 580 planInfo = nil 581 } 582 out[i] = params.VolumeAttachment{ 583 v.Volume.String(), 584 v.Machine.String(), 585 params.VolumeAttachmentInfo{ 586 v.DeviceName, 587 v.DeviceLink, 588 v.BusAddress, 589 v.ReadOnly, 590 planInfo, 591 }, 592 } 593 } 594 return out 595 } 596 597 func volumeFromParams(in params.Volume) (storage.Volume, error) { 598 volumeTag, err := names.ParseVolumeTag(in.VolumeTag) 599 if err != nil { 600 return storage.Volume{}, errors.Trace(err) 601 } 602 return storage.Volume{ 603 volumeTag, 604 storage.VolumeInfo{ 605 in.Info.VolumeId, 606 in.Info.HardwareId, 607 in.Info.WWN, 608 in.Info.Size, 609 in.Info.Persistent, 610 }, 611 }, nil 612 } 613 614 func volumeParamsFromParams(in params.VolumeParams) (storage.VolumeParams, error) { 615 volumeTag, err := names.ParseVolumeTag(in.VolumeTag) 616 if err != nil { 617 return storage.VolumeParams{}, errors.Trace(err) 618 } 619 providerType := storage.ProviderType(in.Provider) 620 621 var attachment *storage.VolumeAttachmentParams 622 if in.Attachment != nil { 623 if in.Attachment.Provider != in.Provider { 624 return storage.VolumeParams{}, errors.Errorf( 625 "storage provider mismatch: volume (%q), attachment (%q)", 626 in.Provider, in.Attachment.Provider, 627 ) 628 } 629 if in.Attachment.VolumeTag != in.VolumeTag { 630 return storage.VolumeParams{}, errors.Errorf( 631 "volume tag mismatch: volume (%q), attachment (%q)", 632 in.VolumeTag, in.Attachment.VolumeTag, 633 ) 634 } 635 hostTag, err := names.ParseTag(in.Attachment.MachineTag) 636 if err != nil { 637 return storage.VolumeParams{}, errors.Annotate( 638 err, "parsing attachment machine tag", 639 ) 640 } 641 attachment = &storage.VolumeAttachmentParams{ 642 AttachmentParams: storage.AttachmentParams{ 643 Provider: providerType, 644 Machine: hostTag, 645 InstanceId: instance.Id(in.Attachment.InstanceId), 646 ReadOnly: in.Attachment.ReadOnly, 647 }, 648 Volume: volumeTag, 649 } 650 } 651 return storage.VolumeParams{ 652 volumeTag, 653 in.Size, 654 providerType, 655 in.Attributes, 656 in.Tags, 657 attachment, 658 }, nil 659 } 660 661 func volumeAttachmentParamsFromParams(in params.VolumeAttachmentParams) (storage.VolumeAttachmentParams, error) { 662 hostTag, err := names.ParseTag(in.MachineTag) 663 if err != nil { 664 return storage.VolumeAttachmentParams{}, errors.Trace(err) 665 } 666 volumeTag, err := names.ParseVolumeTag(in.VolumeTag) 667 if err != nil { 668 return storage.VolumeAttachmentParams{}, errors.Trace(err) 669 } 670 return storage.VolumeAttachmentParams{ 671 AttachmentParams: storage.AttachmentParams{ 672 Provider: storage.ProviderType(in.Provider), 673 Machine: hostTag, 674 InstanceId: instance.Id(in.InstanceId), 675 ReadOnly: in.ReadOnly, 676 }, 677 Volume: volumeTag, 678 VolumeId: in.VolumeId, 679 }, nil 680 }