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  }