github.com/Cloud-Foundations/Dominator@v0.3.4/hypervisor/httpd/listVMs.go (about)

     1  package httpd
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"net"
     8  	"net/http"
     9  	"sort"
    10  	"strconv"
    11  
    12  	"github.com/Cloud-Foundations/Dominator/lib/format"
    13  	"github.com/Cloud-Foundations/Dominator/lib/html"
    14  	"github.com/Cloud-Foundations/Dominator/lib/url"
    15  	proto "github.com/Cloud-Foundations/Dominator/proto/hypervisor"
    16  )
    17  
    18  type ownerTotalsType struct {
    19  	MemoryInMiB uint64
    20  	MilliCPUs   uint
    21  	NumVMs      uint
    22  	NumVolumes  uint
    23  	VirtualCPUs uint
    24  	VolumeSize  uint64
    25  }
    26  
    27  func addVmToOwnersTotals(totalsByOwner map[string]*ownerTotalsType,
    28  	vm *proto.VmInfo) {
    29  	ownerTotals := totalsByOwner[vm.OwnerUsers[0]]
    30  	if ownerTotals == nil {
    31  		ownerTotals = &ownerTotalsType{}
    32  		totalsByOwner[vm.OwnerUsers[0]] = ownerTotals
    33  	}
    34  	ownerTotals.MemoryInMiB += vm.MemoryInMiB
    35  	ownerTotals.MilliCPUs += vm.MilliCPUs
    36  	ownerTotals.NumVMs++
    37  	ownerTotals.NumVolumes += uint(len(vm.Volumes))
    38  	if vm.VirtualCPUs < 1 {
    39  		ownerTotals.VirtualCPUs++
    40  	} else {
    41  		ownerTotals.VirtualCPUs += vm.VirtualCPUs
    42  	}
    43  	for _, volume := range vm.Volumes {
    44  		ownerTotals.VolumeSize += volume.Size
    45  	}
    46  }
    47  
    48  func listVMsByPrimaryOwner(writer io.Writer,
    49  	totalsByOwner map[string]*ownerTotalsType) error {
    50  	ownersList := make([]string, 0, len(totalsByOwner))
    51  	for owner := range totalsByOwner {
    52  		ownersList = append(ownersList, owner)
    53  	}
    54  	sort.Strings(ownersList)
    55  	fmt.Fprintln(writer, `<table border="1" style="width:100%">`)
    56  	tw, _ := html.NewTableWriter(writer, true, "Owner", "Num VMs", "RAM",
    57  		"CPU", "vCPU", "Num Volumes", "Storage")
    58  	for _, owner := range ownersList {
    59  		ownerTotals := totalsByOwner[owner]
    60  		tw.WriteRow("", "",
    61  			fmt.Sprintf("<a href=\"listVMs?primaryOwner=%s\">%s</a>",
    62  				owner, owner),
    63  			strconv.FormatUint(uint64(ownerTotals.NumVMs), 10),
    64  			format.FormatBytes(ownerTotals.MemoryInMiB<<20),
    65  			format.FormatMilli(uint64(ownerTotals.MilliCPUs)),
    66  			strconv.FormatUint(uint64(ownerTotals.VirtualCPUs), 10),
    67  			strconv.FormatUint(uint64(ownerTotals.NumVolumes), 10),
    68  			format.FormatBytes(ownerTotals.VolumeSize))
    69  	}
    70  	totals := sumOwnerTotals(totalsByOwner)
    71  	tw.WriteRow("", "",
    72  		"<b>TOTAL</b>",
    73  		strconv.FormatUint(uint64(totals.NumVMs), 10),
    74  		format.FormatBytes(totals.MemoryInMiB<<20),
    75  		format.FormatMilli(uint64(totals.MilliCPUs)),
    76  		strconv.FormatUint(uint64(totals.VirtualCPUs), 10),
    77  		strconv.FormatUint(uint64(totals.NumVolumes), 10),
    78  		format.FormatBytes(totals.VolumeSize))
    79  	tw.Close()
    80  	return nil
    81  }
    82  
    83  func sumOwnerTotals(totalsByOwner map[string]*ownerTotalsType) ownerTotalsType {
    84  	var totals ownerTotalsType
    85  	for _, ownerTotals := range totalsByOwner {
    86  		totals.MemoryInMiB += ownerTotals.MemoryInMiB
    87  		totals.MilliCPUs += ownerTotals.MilliCPUs
    88  		totals.NumVMs += ownerTotals.NumVMs
    89  		totals.NumVolumes += ownerTotals.NumVolumes
    90  		totals.VirtualCPUs += ownerTotals.VirtualCPUs
    91  		totals.VolumeSize += ownerTotals.VolumeSize
    92  	}
    93  	return totals
    94  }
    95  
    96  func (s state) listVMsHandler(w http.ResponseWriter, req *http.Request) {
    97  	parsedQuery := url.ParseQuery(req.URL)
    98  	writer := bufio.NewWriter(w)
    99  	defer writer.Flush()
   100  	ipAddrs := s.manager.ListVMs(proto.ListVMsRequest{Sort: true})
   101  	matchState := parsedQuery.Table["state"]
   102  	if parsedQuery.OutputType() == url.OutputTypeText && matchState == "" {
   103  		for _, ipAddr := range ipAddrs {
   104  			fmt.Fprintln(writer, ipAddr)
   105  		}
   106  		return
   107  	}
   108  	var tw *html.TableWriter
   109  	if parsedQuery.OutputType() == url.OutputTypeHtml {
   110  		fmt.Fprintf(writer, "<title>List of VMs</title>\n")
   111  		fmt.Fprintln(writer, `<style>
   112                            table, th, td {
   113                            border-collapse: collapse;
   114                            }
   115                            </style>`)
   116  		fmt.Fprintln(writer, "<body>")
   117  		fmt.Fprintln(writer, `<table border="1" style="width:100%">`)
   118  		tw, _ = html.NewTableWriter(writer, true, "IP Addr", "MAC Addr",
   119  			"Name(tag)", "State", "RAM", "CPU", "vCPU", "Num Volumes",
   120  			"Storage", "Primary Owner")
   121  	}
   122  	var allocatedMemoryInMiB uint64
   123  	var allocatedMilliCPUs, allocatedVirtualCPUs, numVolumes uint
   124  	var allocatedVolumeSize uint64
   125  	totalsByOwner := make(map[string]*ownerTotalsType)
   126  	for _, ipAddr := range ipAddrs {
   127  		vm, err := s.manager.GetVmInfo(net.ParseIP(ipAddr))
   128  		if err != nil {
   129  			continue
   130  		}
   131  		if matchState != "" && matchState != vm.State.String() {
   132  			continue
   133  		}
   134  		switch parsedQuery.OutputType() {
   135  		case url.OutputTypeText:
   136  			fmt.Fprintln(writer, ipAddr)
   137  		case url.OutputTypeHtml:
   138  			allocatedMemoryInMiB += vm.MemoryInMiB
   139  			allocatedMilliCPUs += vm.MilliCPUs
   140  			var background string
   141  			if vm.Uncommitted {
   142  				background = "yellow"
   143  			}
   144  			vCPUs := strconv.Itoa(int(numSpecifiedVirtualCPUs(vm.MilliCPUs,
   145  				vm.VirtualCPUs)))
   146  			if vm.VirtualCPUs < 1 {
   147  				vCPUs = `<font color="grey">` + vCPUs + `</font>`
   148  				allocatedVirtualCPUs++
   149  			} else {
   150  				allocatedVirtualCPUs += vm.VirtualCPUs
   151  			}
   152  			numVolumes += uint(len(vm.Volumes))
   153  			volumeSize, volumeString := storageTotalTableEntry(vm)
   154  			allocatedVolumeSize += volumeSize
   155  			tw.WriteRow("", background,
   156  				fmt.Sprintf("<a href=\"showVM?%s\">%s</a>", ipAddr, ipAddr),
   157  				vm.Address.MacAddress,
   158  				vm.Tags["Name"],
   159  				vm.State.String(),
   160  				format.FormatBytes(vm.MemoryInMiB<<20),
   161  				format.FormatMilli(uint64(vm.MilliCPUs)),
   162  				vCPUs,
   163  				numVolumesTableEntry(vm),
   164  				volumeString,
   165  				vm.OwnerUsers[0],
   166  			)
   167  			addVmToOwnersTotals(totalsByOwner, &vm)
   168  		}
   169  	}
   170  	switch parsedQuery.OutputType() {
   171  	case url.OutputTypeHtml:
   172  		tw.WriteRow("", "",
   173  			"<b>TOTAL</b>",
   174  			"",
   175  			"",
   176  			"",
   177  			format.FormatBytes(allocatedMemoryInMiB<<20),
   178  			format.FormatMilli(uint64(allocatedMilliCPUs)),
   179  			strconv.Itoa(int(allocatedVirtualCPUs)),
   180  			strconv.Itoa(int(numVolumes)),
   181  			format.FormatBytes(allocatedVolumeSize),
   182  			"",
   183  		)
   184  		capacity := s.manager.GetCapacity()
   185  		tw.WriteRow("", "",
   186  			"<b>CAPACITY</b>",
   187  			"",
   188  			"",
   189  			"",
   190  			format.FormatBytes(capacity.MemoryInMiB<<20),
   191  			strconv.Itoa(int(capacity.NumCPUs)),
   192  			"",
   193  			"",
   194  			format.FormatBytes(capacity.TotalVolumeBytes),
   195  			"",
   196  		)
   197  		tw.WriteRow("", "",
   198  			"<b>USAGE</b>",
   199  			"",
   200  			"",
   201  			"",
   202  			fmt.Sprintf("%d%%", allocatedMemoryInMiB*100/capacity.MemoryInMiB),
   203  			fmt.Sprintf("%d%%", allocatedMilliCPUs/capacity.NumCPUs/10),
   204  			"",
   205  			"",
   206  			fmt.Sprintf("%d%%",
   207  				allocatedVolumeSize*100/capacity.TotalVolumeBytes),
   208  			"",
   209  		)
   210  		tw.Close()
   211  		fmt.Fprintln(writer, "<p>")
   212  		fmt.Fprintln(writer, "VMs by primary owner:<br>")
   213  		listVMsByPrimaryOwner(writer, totalsByOwner)
   214  		fmt.Fprintln(writer, "</body>")
   215  	}
   216  }
   217  
   218  // numSpecifiedVirtualCPUs calculates the number of virtual CPUs required for
   219  // the specified request. The request must be correct (i.e. sufficient vCPUs).
   220  func numSpecifiedVirtualCPUs(milliCPUs, vCPUs uint) uint {
   221  	nCpus := milliCPUs / 1000
   222  	if nCpus < 1 {
   223  		nCpus = 1
   224  	}
   225  	if nCpus*1000 < milliCPUs {
   226  		nCpus++
   227  	}
   228  	if nCpus < vCPUs {
   229  		nCpus = vCPUs
   230  	}
   231  	return nCpus
   232  }
   233  
   234  func numVolumesTableEntry(vm proto.VmInfo) string {
   235  	var comment string
   236  	for _, volume := range vm.Volumes {
   237  		if comment == "" && volume.Format != proto.VolumeFormatRaw {
   238  			comment = `<font style="color:grey;font-size:12px"> (!RAW)</font>`
   239  		}
   240  	}
   241  	return fmt.Sprintf("%d%s", len(vm.Volumes), comment)
   242  }
   243  
   244  func storageTotalTableEntry(vm proto.VmInfo) (uint64, string) {
   245  	var storage uint64
   246  	for _, volume := range vm.Volumes {
   247  		storage += volume.Size
   248  	}
   249  	return storage, format.FormatBytes(storage)
   250  }