github.com/Cloud-Foundations/Dominator@v0.3.4/cmd/vm-control/placement.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "math/rand" 8 "os" 9 "os/exec" 10 "sort" 11 "time" 12 13 "github.com/Cloud-Foundations/Dominator/lib/constants" 14 "github.com/Cloud-Foundations/Dominator/lib/json" 15 "github.com/Cloud-Foundations/Dominator/lib/srpc" 16 fm_proto "github.com/Cloud-Foundations/Dominator/proto/fleetmanager" 17 hyper_proto "github.com/Cloud-Foundations/Dominator/proto/hypervisor" 18 ) 19 20 type placementMessage struct { 21 Hypervisors []fm_proto.Hypervisor `json:",omitempty"` 22 VmInfo hyper_proto.VmInfo 23 } 24 25 type placementType uint 26 27 const ( 28 placementChoiceAny = iota 29 placementChoiceCommand 30 placementChoiceEmptiest 31 placmentChoiceFullest 32 placementChoiceRandom 33 34 placementTypeUnknown = "UNKNOWN placementType" 35 ) 36 37 var ( 38 placementTypeToText = map[placementType]string{ 39 placementChoiceAny: "any", 40 placementChoiceCommand: "command", 41 placementChoiceEmptiest: "emptiest", 42 placmentChoiceFullest: "fullest", 43 placementChoiceRandom: "random", 44 } 45 textToPlacementType map[string]placementType 46 ) 47 48 func init() { 49 rand.Seed(time.Now().Unix() + time.Now().UnixNano()) 50 textToPlacementType = make(map[string]placementType, 51 len(placementTypeToText)) 52 for placementType, text := range placementTypeToText { 53 textToPlacementType[text] = placementType 54 } 55 } 56 57 // Returns true if [i] has less free CPU than [j]. 58 func compareCPU(hypervisors []fm_proto.Hypervisor, i, j int) bool { 59 return uint64(hypervisors[i].NumCPUs)*1000- 60 hypervisors[i].AllocatedMilliCPUs < 61 uint64(hypervisors[j].NumCPUs)*1000-hypervisors[j].AllocatedMilliCPUs 62 } 63 64 // Returns true if [i] has less free memory than [j]. 65 func compareMemory(hypervisors []fm_proto.Hypervisor, i, j int) bool { 66 return hypervisors[i].MemoryInMiB-hypervisors[i].AllocatedMemory < 67 hypervisors[j].MemoryInMiB-hypervisors[j].AllocatedMemory 68 } 69 70 // Returns true if [i] has less free storage space than [j]. 71 func compareStorage(hypervisors []fm_proto.Hypervisor, i, j int) bool { 72 return hypervisors[i].TotalVolumeBytes-hypervisors[i].AllocatedVolumeBytes < 73 hypervisors[j].TotalVolumeBytes-hypervisors[j].AllocatedVolumeBytes 74 } 75 76 func findHypervisorsWithCapacity(inputHypervisors []fm_proto.Hypervisor, 77 vmInfo hyper_proto.VmInfo) []fm_proto.Hypervisor { 78 outputHypervisors := make([]fm_proto.Hypervisor, 0, len(inputHypervisors)) 79 for _, h := range inputHypervisors { 80 if vmInfo.MemoryInMiB+h.AllocatedMemory > h.MemoryInMiB { 81 continue 82 } 83 if uint64(vmInfo.MilliCPUs)+h.AllocatedMilliCPUs > 84 uint64(h.NumCPUs*1000) { 85 continue 86 } 87 var totalVolumeSize uint64 88 for _, volume := range vmInfo.Volumes { 89 totalVolumeSize += volume.Size 90 } 91 if totalVolumeSize+h.AllocatedVolumeBytes > h.TotalVolumeBytes { 92 continue 93 } 94 outputHypervisors = append(outputHypervisors, h) 95 } 96 return outputHypervisors 97 } 98 99 func getHypervisorAddress(vmInfo hyper_proto.VmInfo) (string, error) { 100 if *hypervisorHostname != "" { 101 return fmt.Sprintf("%s:%d", *hypervisorHostname, *hypervisorPortNum), 102 nil 103 } 104 client, err := dialFleetManager(fmt.Sprintf("%s:%d", 105 *fleetManagerHostname, *fleetManagerPortNum)) 106 if err != nil { 107 return "", err 108 } 109 defer client.Close() 110 if *adjacentVM != "" { 111 if adjacentVmIpAddr, err := lookupIP(*adjacentVM); err != nil { 112 return "", err 113 } else { 114 return findHypervisorClient(client, adjacentVmIpAddr) 115 } 116 } 117 if placement == placementChoiceAny { // Really dumb placement. 118 return selectAnyHypervisor(client) 119 } 120 request := fm_proto.GetHypervisorsInLocationRequest{ 121 HypervisorTagsToMatch: hypervisorTagsToMatch, 122 IncludeVMs: placement == placementChoiceCommand, 123 Location: *location, 124 SubnetId: *subnetId, 125 } 126 var reply fm_proto.GetHypervisorsInLocationResponse 127 err = client.RequestReply("FleetManager.GetHypervisorsInLocation", 128 request, &reply) 129 if err != nil { 130 return "", err 131 } 132 if reply.Error != "" { 133 return "", errors.New(reply.Error) 134 } 135 hypervisors := findHypervisorsWithCapacity(reply.Hypervisors, vmInfo) 136 hypervisor, err := selectHypervisor(client, hypervisors, vmInfo) 137 if err != nil { 138 return "", err 139 } 140 return fmt.Sprintf("%s:%d", 141 hypervisor.Hostname, constants.HypervisorPortNumber), nil 142 } 143 144 func selectAnyHypervisor(client *srpc.Client) (string, error) { 145 request := fm_proto.ListHypervisorsInLocationRequest{ 146 HypervisorTagsToMatch: hypervisorTagsToMatch, 147 Location: *location, 148 SubnetId: *subnetId, 149 } 150 var reply fm_proto.ListHypervisorsInLocationResponse 151 err := client.RequestReply("FleetManager.ListHypervisorsInLocation", 152 request, &reply) 153 if err != nil { 154 return "", err 155 } 156 if reply.Error != "" { 157 return "", errors.New(reply.Error) 158 } 159 numHyper := len(reply.HypervisorAddresses) 160 if numHyper < 1 { 161 return "", errors.New("no active Hypervisors in location") 162 } else if numHyper < 2 { 163 return reply.HypervisorAddresses[0], nil 164 } 165 return reply.HypervisorAddresses[rand.Intn(numHyper)], nil 166 } 167 168 func selectHypervisor(client *srpc.Client, hypervisors []fm_proto.Hypervisor, 169 vmInfo hyper_proto.VmInfo) (*fm_proto.Hypervisor, error) { 170 numHyper := len(hypervisors) 171 if numHyper < 1 { 172 return nil, errors.New("no Hypervisors in location with capacity") 173 } else if numHyper < 2 { 174 return &hypervisors[0], nil 175 } 176 switch placement { 177 case placementChoiceCommand: 178 return selectHypervisorUsingCommand(hypervisors, vmInfo) 179 case placementChoiceEmptiest: 180 sortHypervisors(hypervisors) 181 return &hypervisors[len(hypervisors)-1], nil 182 case placmentChoiceFullest: 183 sortHypervisors(hypervisors) 184 return &hypervisors[0], nil 185 case placementChoiceRandom: 186 return &hypervisors[rand.Intn(numHyper)], nil 187 } 188 return nil, errors.New(placementTypeUnknown) 189 } 190 191 func selectHypervisorUsingCommand(hypervisors []fm_proto.Hypervisor, 192 vmInfo hyper_proto.VmInfo) (*fm_proto.Hypervisor, error) { 193 if *placementCommand == "" { 194 return nil, errors.New("no placementCommand") 195 } 196 cmd := exec.Command(*placementCommand) 197 buffer := &bytes.Buffer{} 198 writer, err := cmd.StdinPipe() 199 defer writer.Close() 200 cmd.Stdout = buffer 201 cmd.Stderr = os.Stderr 202 if err != nil { 203 return nil, err 204 } 205 if err := cmd.Start(); err != nil { 206 return nil, err 207 } 208 msg := placementMessage{hypervisors, vmInfo} 209 if err := json.WriteWithIndent(writer, " ", msg); err != nil { 210 return nil, err 211 } 212 writer.Close() 213 if err := cmd.Wait(); err != nil { 214 return nil, err 215 } 216 output := string(bytes.TrimSpace(buffer.Bytes())) 217 if output == "" { 218 return nil, errors.New("no output from command") 219 } 220 var h fm_proto.Hypervisor 221 h.Hostname = output 222 return &h, nil 223 } 224 225 // Returns true if [i] has less free capacity than [j]. 226 func sortHypervisors(hypervisors []fm_proto.Hypervisor) { 227 sort.SliceStable(hypervisors, func(i, j int) bool { 228 return compareStorage(hypervisors, i, j) 229 }) 230 sort.SliceStable(hypervisors, func(i, j int) bool { 231 return compareMemory(hypervisors, i, j) 232 }) 233 sort.SliceStable(hypervisors, func(i, j int) bool { 234 return compareCPU(hypervisors, i, j) 235 }) 236 } 237 238 func (p *placementType) Set(value string) error { 239 if val, ok := textToPlacementType[value]; !ok { 240 return errors.New(placementTypeUnknown) 241 } else { 242 *p = val 243 return nil 244 } 245 } 246 247 func (p placementType) String() string { 248 if str, ok := placementTypeToText[p]; !ok { 249 return placementTypeUnknown 250 } else { 251 return str 252 } 253 }