github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/storage/listformatters.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 "io" 8 "sort" 9 "strconv" 10 "strings" 11 12 "github.com/dustin/go-humanize" 13 14 "github.com/juju/ansiterm" 15 "github.com/juju/juju/cmd/output" 16 ) 17 18 // formatStorageInstancesListTabular writes a tabular summary of storage instances. 19 func formatStorageInstancesListTabular(writer io.Writer, s CombinedStorage) error { 20 tw := output.TabWriter(writer) 21 w := output.Wrapper{tw} 22 23 storagePool, storageSize := getStoragePoolAndSize(s) 24 units, byUnit := sortStorageInstancesByUnitId(s) 25 26 w.Print("Unit", "Storage id", "Type") 27 if len(storagePool) > 0 { 28 // Older versions of Juju do not include 29 // the pool name in the storage details. 30 // We omit the column in that case. 31 w.Print("Pool") 32 } 33 w.Println("Size", "Status", "Message") 34 35 for _, unit := range units { 36 // Then sort by storage ids 37 byStorage := byUnit[unit] 38 storageIds := make([]string, 0, len(byStorage)) 39 for storageId := range byStorage { 40 storageIds = append(storageIds, storageId) 41 } 42 sort.Strings(slashSeparatedIds(storageIds)) 43 44 for _, storageId := range storageIds { 45 info := byStorage[storageId] 46 w.Print(info.unitId) 47 w.Print(info.storageId) 48 w.Print(info.kind) 49 if len(storagePool) > 0 { 50 w.Print(storagePool[info.storageId]) 51 } 52 w.Print(humanizeStorageSize(storageSize[storageId])) 53 w.PrintStatus(info.status.Current) 54 w.Println(info.status.Message) 55 } 56 } 57 tw.Flush() 58 59 return nil 60 } 61 62 func sortStorageInstancesByUnitId(s CombinedStorage) ([]string, map[string]map[string]storageAttachmentInfo) { 63 byUnit := make(map[string]map[string]storageAttachmentInfo) 64 for storageId, storageInfo := range s.StorageInstances { 65 if storageInfo.Attachments == nil { 66 byStorage := byUnit[""] 67 if byStorage == nil { 68 byStorage = make(map[string]storageAttachmentInfo) 69 byUnit[""] = byStorage 70 } 71 byStorage[storageId] = storageAttachmentInfo{ 72 storageId: storageId, 73 kind: storageInfo.Kind, 74 status: storageInfo.Status, 75 } 76 continue 77 } 78 for unitId := range storageInfo.Attachments.Units { 79 byStorage := byUnit[unitId] 80 if byStorage == nil { 81 byStorage = make(map[string]storageAttachmentInfo) 82 byUnit[unitId] = byStorage 83 } 84 byStorage[storageId] = storageAttachmentInfo{ 85 storageId: storageId, 86 unitId: unitId, 87 kind: storageInfo.Kind, 88 status: storageInfo.Status, 89 } 90 } 91 } 92 93 // sort by units 94 units := make([]string, 0, len(s.StorageInstances)) 95 for unit := range byUnit { 96 units = append(units, unit) 97 } 98 sort.Strings(slashSeparatedIds(units)) 99 return units, byUnit 100 } 101 102 func getStoragePoolAndSize(s CombinedStorage) (map[string]string, map[string]uint64) { 103 storageSize := make(map[string]uint64) 104 storagePool := make(map[string]string) 105 for _, f := range s.Filesystems { 106 if f.Pool != "" { 107 storagePool[f.Storage] = f.Pool 108 } 109 storageSize[f.Storage] = f.Size 110 } 111 for _, v := range s.Volumes { 112 // This will intentionally override the provider ID 113 // and pool for a volume-backed filesystem. 114 if v.Pool != "" { 115 storagePool[v.Storage] = v.Pool 116 } 117 // For size, we want to use the size of the filesystem 118 // rather than the volume. 119 if _, ok := storageSize[v.Storage]; !ok { 120 storageSize[v.Storage] = v.Size 121 } 122 } 123 return storagePool, storageSize 124 } 125 126 func getFilesystemAttachment(combined CombinedStorage, attachmentInfo storageAttachmentInfo) FilesystemAttachment { 127 for _, f := range combined.Filesystems { 128 combineAllAttachments := func() map[string]FilesystemAttachment { 129 all := map[string]FilesystemAttachment{} 130 attachment := f.Attachments 131 132 if attachment == nil { 133 return all 134 } 135 for k, v := range attachment.Machines { 136 all[k] = v 137 } 138 for k, v := range attachment.Containers { 139 all[k] = v 140 } 141 return all 142 } 143 144 if f.Storage == attachmentInfo.storageId { 145 if attachment, ok := combineAllAttachments()[attachmentInfo.unitId]; ok { 146 return attachment 147 } 148 } 149 } 150 return FilesystemAttachment{} 151 } 152 153 // FormatStorageListForStatusTabular writes a tabular summary of storage for status tabular view. 154 func FormatStorageListForStatusTabular(writer *ansiterm.TabWriter, s CombinedStorage) error { 155 w := output.Wrapper{writer} 156 157 storagePool, storageSize := getStoragePoolAndSize(s) 158 units, byUnit := sortStorageInstancesByUnitId(s) 159 160 w.Println() 161 w.Print("Storage Unit", "Storage id", "Type") 162 if len(storagePool) > 0 { 163 w.Print("Pool") 164 } 165 w.Println("Mountpoint", "Size", "Status", "Message") 166 167 for _, unit := range units { 168 byStorage := byUnit[unit] 169 storageIds := make([]string, 0, len(byStorage)) 170 for storageId := range byStorage { 171 storageIds = append(storageIds, storageId) 172 } 173 sort.Strings(slashSeparatedIds(storageIds)) 174 175 for _, storageId := range storageIds { 176 info := byStorage[storageId] 177 178 w.Print(info.unitId) 179 w.Print(info.storageId) 180 w.Print(info.kind) 181 if len(storagePool) > 0 { 182 w.Print(storagePool[info.storageId]) 183 } 184 w.Print(getFilesystemAttachment(s, info).MountPoint) 185 w.Print(humanizeStorageSize(storageSize[storageId])) 186 w.PrintStatus(info.status.Current) 187 w.Println(info.status.Message) 188 } 189 } 190 w.Flush() 191 return nil 192 } 193 194 func humanizeStorageSize(size uint64) string { 195 var sizeStr string 196 if size > 0 { 197 sizeStr = humanize.IBytes(size * humanize.MiByte) 198 } 199 return sizeStr 200 } 201 202 type storageAttachmentInfo struct { 203 storageId string 204 unitId string 205 kind string 206 status EntityStatus 207 } 208 209 // slashSeparatedIds represents a list of slash separated ids. 210 type slashSeparatedIds []string 211 212 func (s slashSeparatedIds) Len() int { 213 return len(s) 214 } 215 216 func (s slashSeparatedIds) Swap(a, b int) { 217 s[a], s[b] = s[b], s[a] 218 } 219 220 func (s slashSeparatedIds) Less(a, b int) bool { 221 return compareSlashSeparated(s[a], s[b]) == -1 222 } 223 224 // compareSlashSeparated compares a with b, first the string before 225 // "/", and then the integer or string after. Empty strings are sorted 226 // after all others. 227 func compareSlashSeparated(a, b string) int { 228 switch { 229 case a == "" && b == "": 230 return 0 231 case a == "": 232 return 1 233 case b == "": 234 return -1 235 } 236 237 sa := strings.SplitN(a, "/", 2) 238 sb := strings.SplitN(b, "/", 2) 239 if sa[0] < sb[0] { 240 return -1 241 } 242 if sa[0] > sb[0] { 243 return 1 244 } 245 246 getInt := func(suffix string) (bool, int) { 247 num, err := strconv.Atoi(suffix) 248 if err != nil { 249 return false, 0 250 } 251 return true, num 252 } 253 254 naIsNumeric, na := getInt(sa[1]) 255 if !naIsNumeric { 256 return compareStrings(sa[1], sb[1]) 257 } 258 nbIsNumeric, nb := getInt(sb[1]) 259 if !nbIsNumeric { 260 return compareStrings(sa[1], sb[1]) 261 } 262 263 switch { 264 case na < nb: 265 return -1 266 case na == nb: 267 return 0 268 } 269 return 1 270 } 271 272 // compareStrings does what strings.Compare does, but without using 273 // strings.Compare as it does not exist in Go 1.2. 274 func compareStrings(a, b string) int { 275 if a == b { 276 return 0 277 } 278 if a < b { 279 return -1 280 } 281 return 1 282 }