github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/juju/storage/volume.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package storage 5 6 import ( 7 "github.com/juju/cmd" 8 "github.com/juju/errors" 9 "github.com/juju/names" 10 11 "github.com/juju/juju/apiserver/params" 12 jujucmd "github.com/juju/juju/cmd" 13 "github.com/juju/juju/cmd/envcmd" 14 "github.com/juju/juju/cmd/juju/common" 15 ) 16 17 const volumeCmdDoc = ` 18 "juju storage volume" is used to manage storage volumes in 19 the Juju environment. 20 ` 21 22 const volumeCmdPurpose = "manage storage volumes" 23 24 // NewVolumeSuperCommand creates the storage volume super subcommand and 25 // registers the subcommands that it supports. 26 func NewVolumeSuperCommand() cmd.Command { 27 supercmd := jujucmd.NewSubSuperCommand(cmd.SuperCommandParams{ 28 Name: "volume", 29 Doc: volumeCmdDoc, 30 UsagePrefix: "juju storage", 31 Purpose: volumeCmdPurpose, 32 }) 33 supercmd.Register(envcmd.Wrap(&VolumeListCommand{})) 34 return supercmd 35 } 36 37 // VolumeCommandBase is a helper base structure for volume commands. 38 type VolumeCommandBase struct { 39 StorageCommandBase 40 } 41 42 // VolumeInfo defines the serialization behaviour for storage volume. 43 type VolumeInfo struct { 44 // from params.Volume. This is provider-supplied unique volume id. 45 ProviderVolumeId string `yaml:"provider-id,omitempty" json:"provider-id,omitempty"` 46 47 // Storage is the ID of the storage instance that the volume is 48 // assigned to, if any. 49 Storage string `yaml:"storage,omitempty" json:"storage,omitempty"` 50 51 // Attachments is the set of entities attached to the volume. 52 Attachments *VolumeAttachments `yaml:"attachments,omitempty" json:"attachments,omitempty"` 53 54 // from params.Volume 55 HardwareId string `yaml:"hardware-id,omitempty" json:"hardware-id,omitempty"` 56 57 // from params.Volume 58 Size uint64 `yaml:"size" json:"size"` 59 60 // from params.Volume 61 Persistent bool `yaml:"persistent" json:"persistent"` 62 63 // from params.Volume 64 Status EntityStatus `yaml:"status,omitempty" json:"status,omitempty"` 65 } 66 67 type EntityStatus struct { 68 Current params.Status `json:"current,omitempty" yaml:"current,omitempty"` 69 Message string `json:"message,omitempty" yaml:"message,omitempty"` 70 Since string `json:"since,omitempty" yaml:"since,omitempty"` 71 } 72 73 type VolumeAttachments struct { 74 Machines map[string]MachineVolumeAttachment `yaml:"machines,omitempty" json:"machines,omitempty"` 75 Units map[string]UnitStorageAttachment `yaml:"units,omitempty" json:"units,omitempty"` 76 } 77 78 type MachineVolumeAttachment struct { 79 DeviceName string `yaml:"device,omitempty" json:"device,omitempty"` 80 DeviceLink string `yaml:"device-link,omitempty" json:"device-link,omitempty"` 81 BusAddress string `yaml:"bus-address,omitempty" json:"bus-address,omitempty"` 82 ReadOnly bool `yaml:"read-only" json:"read-only"` 83 // TODO(axw) add machine volume attachment status when we have it 84 } 85 86 // convertToVolumeInfo returns a map of volume IDs to volume info. 87 func convertToVolumeInfo(all []params.VolumeDetailsResult) (map[string]VolumeInfo, error) { 88 result := make(map[string]VolumeInfo) 89 for _, one := range all { 90 volumeTag, info, err := createVolumeInfo(one) 91 if err != nil { 92 return nil, errors.Trace(err) 93 } 94 result[volumeTag.Id()] = info 95 } 96 return result, nil 97 } 98 99 var idFromTag = func(s string) (string, error) { 100 tag, err := names.ParseTag(s) 101 if err != nil { 102 return "", errors.Annotatef(err, "invalid tag %v", tag) 103 } 104 return tag.Id(), nil 105 } 106 107 func createVolumeInfo(result params.VolumeDetailsResult) (names.VolumeTag, VolumeInfo, error) { 108 details := result.Details 109 if details == nil { 110 details = volumeDetailsFromLegacy(result) 111 } 112 113 volumeTag, err := names.ParseVolumeTag(details.VolumeTag) 114 if err != nil { 115 return names.VolumeTag{}, VolumeInfo{}, errors.Trace(err) 116 } 117 118 var info VolumeInfo 119 info.ProviderVolumeId = details.Info.VolumeId 120 info.HardwareId = details.Info.HardwareId 121 info.Size = details.Info.Size 122 info.Persistent = details.Info.Persistent 123 info.Status = EntityStatus{ 124 details.Status.Status, 125 details.Status.Info, 126 // TODO(axw) we should support formatting as ISO time 127 common.FormatTime(details.Status.Since, false), 128 } 129 130 if len(details.MachineAttachments) > 0 { 131 machineAttachments := make(map[string]MachineVolumeAttachment) 132 for machineTag, attachment := range details.MachineAttachments { 133 machineId, err := idFromTag(machineTag) 134 if err != nil { 135 return names.VolumeTag{}, VolumeInfo{}, errors.Trace(err) 136 } 137 machineAttachments[machineId] = MachineVolumeAttachment{ 138 attachment.DeviceName, 139 attachment.DeviceLink, 140 attachment.BusAddress, 141 attachment.ReadOnly, 142 } 143 } 144 info.Attachments = &VolumeAttachments{ 145 Machines: machineAttachments, 146 } 147 } 148 149 if details.Storage != nil { 150 storageTag, storageInfo, err := createStorageInfo(*details.Storage) 151 if err != nil { 152 return names.VolumeTag{}, VolumeInfo{}, errors.Trace(err) 153 } 154 info.Storage = storageTag.Id() 155 if storageInfo.Attachments != nil { 156 info.Attachments.Units = storageInfo.Attachments.Units 157 } 158 } 159 160 return volumeTag, info, nil 161 } 162 163 // volumeDetailsFromLegacy converts from legacy data structures 164 // to params.VolumeDetails. This exists only for backwards- 165 // compatibility. Please think long and hard before changing it. 166 func volumeDetailsFromLegacy(result params.VolumeDetailsResult) *params.VolumeDetails { 167 details := ¶ms.VolumeDetails{ 168 VolumeTag: result.LegacyVolume.VolumeTag, 169 Status: result.LegacyVolume.Status, 170 } 171 details.Info.VolumeId = result.LegacyVolume.VolumeId 172 details.Info.HardwareId = result.LegacyVolume.HardwareId 173 details.Info.Size = result.LegacyVolume.Size 174 details.Info.Persistent = result.LegacyVolume.Persistent 175 if len(result.LegacyAttachments) > 0 { 176 attachments := make(map[string]params.VolumeAttachmentInfo) 177 for _, attachment := range result.LegacyAttachments { 178 attachments[attachment.MachineTag] = attachment.Info 179 } 180 details.MachineAttachments = attachments 181 } 182 if result.LegacyVolume.StorageTag != "" { 183 details.Storage = ¶ms.StorageDetails{ 184 StorageTag: result.LegacyVolume.StorageTag, 185 Status: details.Status, 186 } 187 if result.LegacyVolume.UnitTag != "" { 188 // Servers with legacy storage do not support shared 189 // storage, so there will only be one attachment, and 190 // the owner is always a unit. 191 details.Storage.OwnerTag = result.LegacyVolume.UnitTag 192 if len(result.LegacyAttachments) == 1 { 193 details.Storage.Attachments = map[string]params.StorageAttachmentDetails{ 194 result.LegacyVolume.UnitTag: params.StorageAttachmentDetails{ 195 StorageTag: result.LegacyVolume.StorageTag, 196 UnitTag: result.LegacyVolume.UnitTag, 197 MachineTag: result.LegacyAttachments[0].MachineTag, 198 // Don't set Location, because we can't infer that 199 // from the legacy volume details. 200 Location: "", 201 }, 202 } 203 } 204 } 205 } 206 return details 207 }