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

     1  package hypervisors
     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/constants"
    13  	"github.com/Cloud-Foundations/Dominator/lib/format"
    14  	"github.com/Cloud-Foundations/Dominator/lib/html"
    15  	"github.com/Cloud-Foundations/Dominator/lib/json"
    16  	"github.com/Cloud-Foundations/Dominator/lib/stringutil"
    17  	"github.com/Cloud-Foundations/Dominator/lib/tags/tagmatcher"
    18  	"github.com/Cloud-Foundations/Dominator/lib/url"
    19  	"github.com/Cloud-Foundations/Dominator/lib/verstr"
    20  	fm_proto "github.com/Cloud-Foundations/Dominator/proto/fleetmanager"
    21  	hyper_proto "github.com/Cloud-Foundations/Dominator/proto/hypervisor"
    22  )
    23  
    24  const commonStyleSheet string = `<style>
    25  table, th, td {
    26  border-collapse: collapse;
    27  }
    28  </style>
    29  `
    30  
    31  type ownerTotalsType struct {
    32  	MemoryInMiB uint64
    33  	MilliCPUs   uint
    34  	NumVMs      uint
    35  	NumVolumes  uint
    36  	VirtualCPUs uint
    37  	VolumeSize  uint64
    38  }
    39  
    40  func getTotalsByOwner(vms []*vmInfoType) map[string]*ownerTotalsType {
    41  	totalsByOwner := make(map[string]*ownerTotalsType)
    42  	for _, vm := range vms {
    43  		ownerTotals := totalsByOwner[vm.OwnerUsers[0]]
    44  		if ownerTotals == nil {
    45  			ownerTotals = &ownerTotalsType{}
    46  			totalsByOwner[vm.OwnerUsers[0]] = ownerTotals
    47  		}
    48  		ownerTotals.MemoryInMiB += vm.MemoryInMiB
    49  		ownerTotals.MilliCPUs += vm.MilliCPUs
    50  		ownerTotals.NumVMs++
    51  		ownerTotals.NumVolumes += uint(len(vm.Volumes))
    52  		if vm.VirtualCPUs < 1 {
    53  			ownerTotals.VirtualCPUs++
    54  		} else {
    55  			ownerTotals.VirtualCPUs += vm.VirtualCPUs
    56  		}
    57  		for _, volume := range vm.Volumes {
    58  			ownerTotals.VolumeSize += volume.Size
    59  		}
    60  	}
    61  	return totalsByOwner
    62  }
    63  
    64  func getVmListFromMap(vmMap map[string]*vmInfoType, doSort bool) []*vmInfoType {
    65  	vms := make([]*vmInfoType, 0, len(vmMap))
    66  	if doSort {
    67  		ipAddrs := make([]string, 0, len(vmMap))
    68  		for ipAddr := range vmMap {
    69  			ipAddrs = append(ipAddrs, ipAddr)
    70  		}
    71  		verstr.Sort(ipAddrs)
    72  		for _, ipAddr := range ipAddrs {
    73  			vms = append(vms, vmMap[ipAddr])
    74  		}
    75  	} else {
    76  		for _, vm := range vmMap {
    77  			vms = append(vms, vm)
    78  		}
    79  	}
    80  	return vms
    81  }
    82  
    83  // numSpecifiedVirtualCPUs calculates the number of virtual CPUs required for
    84  // the specified request. The request must be correct (i.e. sufficient vCPUs).
    85  func numSpecifiedVirtualCPUs(milliCPUs, vCPUs uint) uint {
    86  	nCpus := milliCPUs / 1000
    87  	if nCpus < 1 {
    88  		nCpus = 1
    89  	}
    90  	if nCpus*1000 < milliCPUs {
    91  		nCpus++
    92  	}
    93  	if nCpus < vCPUs {
    94  		nCpus = vCPUs
    95  	}
    96  	return nCpus
    97  }
    98  
    99  func safeDivide(numerator, denominator uint64) uint64 {
   100  	if denominator > 0 {
   101  		return numerator / denominator
   102  	}
   103  	return 0
   104  }
   105  
   106  func sumOwnerTotals(totalsByOwner map[string]*ownerTotalsType) ownerTotalsType {
   107  	var totals ownerTotalsType
   108  	for _, ownerTotals := range totalsByOwner {
   109  		totals.MemoryInMiB += ownerTotals.MemoryInMiB
   110  		totals.MilliCPUs += ownerTotals.MilliCPUs
   111  		totals.NumVMs += ownerTotals.NumVMs
   112  		totals.NumVolumes += ownerTotals.NumVolumes
   113  		totals.VirtualCPUs += ownerTotals.VirtualCPUs
   114  		totals.VolumeSize += ownerTotals.VolumeSize
   115  	}
   116  	return totals
   117  }
   118  
   119  func (m *Manager) getVMs(doSort bool) []*vmInfoType {
   120  	m.mutex.RLock()
   121  	defer m.mutex.RUnlock()
   122  	return getVmListFromMap(m.vms, doSort)
   123  }
   124  
   125  func (m *Manager) listVMs(writer io.Writer, vms []*vmInfoType,
   126  	capacity *hyper_proto.GetCapacityResponse,
   127  	locationFilter, primaryOwnerFilter string, outputType uint) error {
   128  	topology, err := m.getTopology()
   129  	if err != nil {
   130  		return err
   131  	}
   132  	var tw *html.TableWriter
   133  	if outputType == url.OutputTypeHtml {
   134  		fmt.Fprintln(writer, `<table border="1" style="width:100%">`)
   135  		tw, _ = html.NewTableWriter(writer, true, "IP Addr", "Name(tag)",
   136  			"State", "RAM", "CPU", "vCPU", "Num Volumes", "Storage",
   137  			"Primary Owner", "Hypervisor", "Location")
   138  	}
   139  	var vmsToShow []*vmInfoType
   140  	for _, vm := range vms {
   141  		if !testInLocation(vm.Location, locationFilter) {
   142  			continue
   143  		}
   144  		if primaryOwnerFilter != "" &&
   145  			vm.OwnerUsers[0] != primaryOwnerFilter {
   146  			continue
   147  		}
   148  		vmsToShow = append(vmsToShow, vm)
   149  		switch outputType {
   150  		case url.OutputTypeText:
   151  			fmt.Fprintln(writer, vm.ipAddr)
   152  		case url.OutputTypeHtml:
   153  			var background, foreground string
   154  			if vm.hypervisor.probeStatus == probeStatusOff {
   155  				foreground = "#ff8080"
   156  			} else if vm.hypervisor.probeStatus == probeStatusConnected &&
   157  				vm.hypervisor.disabled {
   158  				foreground = "grey"
   159  			} else if vm.hypervisor.probeStatus != probeStatusConnected {
   160  				foreground = "red"
   161  			} else if vm.hypervisor.healthStatus == "at risk" {
   162  				foreground = "#c00000"
   163  			} else if vm.hypervisor.healthStatus == "marginal" {
   164  				foreground = "#800000"
   165  			}
   166  			if vm.Uncommitted {
   167  				background = "yellow"
   168  			} else if topology.CheckIfIpIsHost(vm.ipAddr) ||
   169  				topology.CheckIfIpIsReserved(vm.ipAddr) {
   170  				background = "orange"
   171  			}
   172  			vCPUs := strconv.Itoa(int(numSpecifiedVirtualCPUs(vm.MilliCPUs,
   173  				vm.VirtualCPUs)))
   174  			if vm.VirtualCPUs < 1 {
   175  				vCPUs = `<font color="grey">` + vCPUs + `</font>`
   176  			}
   177  			tw.WriteRow(foreground, background,
   178  				fmt.Sprintf("<a href=\"showVM?%s\">%s</a>",
   179  					vm.ipAddr, vm.ipAddr),
   180  				vm.Tags["Name"],
   181  				vm.State.String(),
   182  				format.FormatBytes(vm.MemoryInMiB<<20),
   183  				format.FormatMilli(uint64(vm.MilliCPUs)),
   184  				vCPUs,
   185  				vm.numVolumesTableEntry(),
   186  				vm.storageTotalTableEntry(),
   187  				vm.OwnerUsers[0],
   188  				fmt.Sprintf("<a href=\"http://%s:%d/\">%s</a>",
   189  					vm.hypervisor.machine.Hostname,
   190  					constants.HypervisorPortNumber,
   191  					vm.hypervisor.machine.Hostname),
   192  				vm.hypervisor.location,
   193  			)
   194  		}
   195  	}
   196  	switch outputType {
   197  	case url.OutputTypeHtml:
   198  		totalsByOwner := getTotalsByOwner(vmsToShow)
   199  		totals := sumOwnerTotals(totalsByOwner)
   200  		tw.WriteRow("", "",
   201  			"<b>TOTAL</b>",
   202  			"",
   203  			"",
   204  			format.FormatBytes(totals.MemoryInMiB<<20),
   205  			format.FormatMilli(uint64(totals.MilliCPUs)),
   206  			strconv.FormatUint(uint64(totals.VirtualCPUs), 10),
   207  			strconv.FormatUint(uint64(totals.NumVolumes), 10),
   208  			format.FormatBytes(totals.VolumeSize),
   209  			primaryOwnerFilter,
   210  			"",
   211  			"")
   212  		if capacity != nil {
   213  			tw.WriteRow("", "",
   214  				"<b>CAPACITY</b>",
   215  				"",
   216  				"",
   217  				format.FormatBytes(capacity.MemoryInMiB<<20),
   218  				strconv.Itoa(int(capacity.NumCPUs)),
   219  				"",
   220  				"",
   221  				format.FormatBytes(capacity.TotalVolumeBytes),
   222  				"",
   223  				"",
   224  				"",
   225  			)
   226  			tw.WriteRow("", "",
   227  				"<b>USAGE</b>",
   228  				"",
   229  				"",
   230  				fmt.Sprintf("%d%%",
   231  					safeDivide(totals.MemoryInMiB*100, capacity.MemoryInMiB)),
   232  				fmt.Sprintf("%d%%",
   233  					safeDivide(uint64(totals.MilliCPUs),
   234  						uint64(capacity.NumCPUs)*10)),
   235  				"",
   236  				"",
   237  				fmt.Sprintf("%d%%",
   238  					safeDivide(totals.VolumeSize*100,
   239  						capacity.TotalVolumeBytes)),
   240  				"",
   241  				"",
   242  				"",
   243  			)
   244  		}
   245  		tw.Close()
   246  	case url.OutputTypeJson:
   247  		json.WriteWithIndent(writer, "   ", vmsToShow)
   248  	}
   249  	return nil
   250  }
   251  
   252  func (m *Manager) listVMsByPrimaryOwner(writer io.Writer, vms []*vmInfoType,
   253  	outputType uint) error {
   254  	totalsByOwner := getTotalsByOwner(vms)
   255  	ownersList := make([]string, 0, len(totalsByOwner))
   256  	for owner := range totalsByOwner {
   257  		ownersList = append(ownersList, owner)
   258  	}
   259  	sort.Strings(ownersList)
   260  	switch outputType {
   261  	case url.OutputTypeHtml:
   262  		fmt.Fprintln(writer, `<table border="1" style="width:100%">`)
   263  		tw, _ := html.NewTableWriter(writer, true, "Owner", "Num VMs", "RAM",
   264  			"CPU", "vCPU", "Num Volumes", "Storage")
   265  		for _, owner := range ownersList {
   266  			ownerTotals := totalsByOwner[owner]
   267  			tw.WriteRow("", "",
   268  				fmt.Sprintf("<a href=\"listVMs?primaryOwner=%s\">%s</a>",
   269  					owner, owner),
   270  				strconv.FormatUint(uint64(ownerTotals.NumVMs), 10),
   271  				format.FormatBytes(ownerTotals.MemoryInMiB<<20),
   272  				format.FormatMilli(uint64(ownerTotals.MilliCPUs)),
   273  				strconv.FormatUint(uint64(ownerTotals.VirtualCPUs), 10),
   274  				strconv.FormatUint(uint64(ownerTotals.NumVolumes), 10),
   275  				format.FormatBytes(ownerTotals.VolumeSize))
   276  		}
   277  		totals := sumOwnerTotals(totalsByOwner)
   278  		tw.WriteRow("", "",
   279  			"<b>TOTAL</b>",
   280  			strconv.FormatUint(uint64(totals.NumVMs), 10),
   281  			format.FormatBytes(totals.MemoryInMiB<<20),
   282  			format.FormatMilli(uint64(totals.MilliCPUs)),
   283  			strconv.FormatUint(uint64(totals.VirtualCPUs), 10),
   284  			strconv.FormatUint(uint64(totals.NumVolumes), 10),
   285  			format.FormatBytes(totals.VolumeSize))
   286  		tw.Close()
   287  	case url.OutputTypeJson:
   288  		json.WriteWithIndent(writer, "   ", totalsByOwner)
   289  	case url.OutputTypeText:
   290  		for _, owner := range ownersList {
   291  			ownerTotals := totalsByOwner[owner]
   292  			fmt.Fprintf(writer, "%s %d\n", owner, ownerTotals.NumVMs)
   293  		}
   294  	}
   295  	return nil
   296  }
   297  
   298  func (m *Manager) listVMsByPrimaryOwnerHandler(w http.ResponseWriter,
   299  	req *http.Request) {
   300  	writer := bufio.NewWriter(w)
   301  	defer writer.Flush()
   302  	parsedQuery := url.ParseQuery(req.URL)
   303  	vms := m.getVMs(true)
   304  	switch parsedQuery.OutputType() {
   305  	case url.OutputTypeHtml:
   306  		fmt.Fprintf(writer, "<title>List of VMs By Primary Owner</title>\n")
   307  		writer.WriteString(commonStyleSheet)
   308  		fmt.Fprintln(writer, "<body>")
   309  	}
   310  	m.listVMsByPrimaryOwner(writer, vms, parsedQuery.OutputType())
   311  	switch parsedQuery.OutputType() {
   312  	case url.OutputTypeHtml:
   313  		fmt.Fprintln(writer, "</body>")
   314  	}
   315  }
   316  
   317  func (m *Manager) listVMsHandler(w http.ResponseWriter,
   318  	req *http.Request) {
   319  	writer := bufio.NewWriter(w)
   320  	defer writer.Flush()
   321  	parsedQuery := url.ParseQuery(req.URL)
   322  	switch parsedQuery.OutputType() {
   323  	case url.OutputTypeHtml:
   324  		fmt.Fprintf(writer, "<title>List of VMs</title>\n")
   325  		writer.WriteString(commonStyleSheet)
   326  		fmt.Fprintln(writer, "<body>")
   327  	}
   328  	vms := m.getVMs(true)
   329  	err := m.listVMs(writer, vms, nil, parsedQuery.Table["location"],
   330  		parsedQuery.Table["primaryOwner"], parsedQuery.OutputType())
   331  	if err != nil {
   332  		fmt.Fprintln(writer, err)
   333  		return
   334  	}
   335  	switch parsedQuery.OutputType() {
   336  	case url.OutputTypeHtml:
   337  		fmt.Fprintln(writer, "</body>")
   338  	}
   339  }
   340  
   341  func (m *Manager) listVMsInLocation(request fm_proto.ListVMsInLocationRequest) (
   342  	[]net.IP, error) {
   343  	hypervisors, err := m.listHypervisors(request.Location, showAll, "",
   344  		tagmatcher.New(request.HypervisorTagsToMatch, false))
   345  	if err != nil {
   346  		return nil, err
   347  	}
   348  	ownerGroups := stringutil.ConvertListToMap(request.OwnerGroups, false)
   349  	ownerUsers := stringutil.ConvertListToMap(request.OwnerUsers, false)
   350  	addresses := make([]net.IP, 0)
   351  	vmTagMatcher := tagmatcher.New(request.VmTagsToMatch, false)
   352  	for _, hypervisor := range hypervisors {
   353  		hypervisor.mutex.RLock()
   354  		for _, vm := range hypervisor.vms {
   355  			if vm.checkOwnerGroups(ownerGroups) &&
   356  				vm.checkOwnerUsers(ownerUsers) &&
   357  				vmTagMatcher.MatchEach(vm.Tags) {
   358  				addresses = append(addresses, vm.Address.IpAddress)
   359  			}
   360  		}
   361  		hypervisor.mutex.RUnlock()
   362  	}
   363  	return addresses, nil
   364  }
   365  
   366  // checkOwnerGroups returns true if one of the specified ownerGroups owns the
   367  // VM. If ownerGroups is nil, checkOwnerGroups returns true.
   368  func (vm *vmInfoType) checkOwnerGroups(ownerGroups map[string]struct{}) bool {
   369  	if ownerGroups == nil {
   370  		return true
   371  	}
   372  	for _, ownerGroup := range vm.OwnerGroups {
   373  		if _, ok := ownerGroups[ownerGroup]; ok {
   374  			return true
   375  		}
   376  	}
   377  	return false
   378  }
   379  
   380  // checkOwnerUsers returns true if one of the specified ownerUsers owns the VM.
   381  // If ownerUsers is nil, checkOwnerUsers returns true.
   382  func (vm *vmInfoType) checkOwnerUsers(ownerUsers map[string]struct{}) bool {
   383  	if ownerUsers == nil {
   384  		return true
   385  	}
   386  	for _, ownerUser := range vm.OwnerUsers {
   387  		if _, ok := ownerUsers[ownerUser]; ok {
   388  			return true
   389  		}
   390  	}
   391  	return false
   392  }
   393  
   394  func (vm *vmInfoType) numVolumesTableEntry() string {
   395  	var comment string
   396  	for _, volume := range vm.Volumes {
   397  		if comment == "" && volume.Format != hyper_proto.VolumeFormatRaw {
   398  			comment = `<font style="color:grey;font-size:12px"> (!RAW)</font>`
   399  		}
   400  	}
   401  	return fmt.Sprintf("%d%s", len(vm.Volumes), comment)
   402  }
   403  
   404  func (vm *vmInfoType) storageTotalTableEntry() string {
   405  	var storage uint64
   406  	for _, volume := range vm.Volumes {
   407  		storage += volume.Size
   408  	}
   409  	return format.FormatBytes(storage)
   410  }