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  }