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 }