// Copyright (C) 2020 Marius Schellenberger package pve import ( "context" "errors" "fmt" "sort" ) var ( ErrNodesOffline = errors.New("one or more nodes are offline") ErrNodesNotSearched = errors.New("one or more nodes could not be searched") ErrUnschedulable = errors.New("no schedulable node found") ErrTooManyServersFound = errors.New("too many servers found with the same name") ) type NodeStatus string const ( NodeStatusOnline NodeStatus = "online" NodeStatusOffline NodeStatus = "offline" NodeStatusUnknown NodeStatus = "unknown" ) type Node struct { Name string `json:"node"` Status NodeStatus `json:"status"` CPU float64 `json:"cpu"` MaxCPU float64 `json:"maxcpu"` Mem uint64 `json:"mem"` MaxMem uint64 `json:"maxmem"` Disk uint64 `json:"disk"` MaxDisk uint64 `json:"maxdisk"` serverList ServerRefList `json:"-"` } func (n *Node) memFreePercent() float64 { return float64(n.Mem) / float64(n.MaxMem) * 100 } func (n *Node) memUsedPercent() float64 { return float64(n.MaxMem-n.Mem) / float64(n.MaxMem) * 100 } func (n *Node) weight() int { return int(n.memUsedPercent()) + (len(n.serverList) * 10) } type NodeList []*Node func (nl NodeList) sortByWeight() { sort.Sort(weightSorter(nl)) } func (nl *NodeList) remove(i int) { (*nl)[i] = (*nl)[len(*nl)-1] *nl = (*nl)[:len(*nl)-1] } func (nl *NodeList) filter(filters ...NodeFilter) { for _, f := range filters { f(nl) } } func (nl *NodeList) getServerList(ctx context.Context, c *NodeClient) (err error) { for _, n := range *nl { n.serverList, err = c.ListServers(ctx, n) if err != nil { return } } return } type weightSorter NodeList func (s weightSorter) Len() int { return len(s) } func (s weightSorter) Less(i, j int) bool { return s[i].weight() < s[j].weight() } func (s weightSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } type NodeFilter func(*NodeList) func FilterOnlineNode() NodeFilter { return func(nl *NodeList) { for i, n := range *nl { if n.Status != NodeStatusOnline { nl.remove(i) } } } } func FilterNodeName(name string) NodeFilter { return func(nl *NodeList) { for i, n := range *nl { if n.Name == name { nl.remove(i) return } } } } func FilterMaxCores(cores uint64) NodeFilter { return func(nl *NodeList) { for i, n := range *nl { if cores > uint64(n.MaxCPU) { nl.remove(i) } } } } const ( mib = 1024 * 1024 gib = mib * 1024 ) func FilterFreeMem(mem uint64) NodeFilter { return func(nl *NodeList) { for i, n := range *nl { if (mem * gib) >= n.MaxMem-n.Mem { nl.remove(i) } } } } // Useful? only local disk? func FilterFreeDisk(disk uint64) NodeFilter { return func(nl *NodeList) { for i, n := range *nl { if (disk * gib) >= n.MaxDisk-n.Disk { nl.remove(i) } } } } func FilterVMAntiAffinity(url string) NodeFilter { return func(nl *NodeList) { _, _, id, err := ParseURL(url) if err != nil { return } for i, n := range *nl { for _, s := range n.serverList { if s.ID == id { nl.remove(i) return } } } } } type NodeClient struct { client *Client } func (c *NodeClient) Schedule(ctx context.Context, filters ...NodeFilter) (n *Node, err error) { nl, err := c.List(ctx) if err != nil { return } err = nl.getServerList(ctx, c) if err != nil { return } nl.filter(filters...) nl.sortByWeight() if len(nl) == 0 { err = ErrUnschedulable return } n = nl[0] return } func (c *NodeClient) List(ctx context.Context) (nl NodeList, err error) { req, err := c.client.NewRequest(ctx, "GET", "/nodes", nil) if err != nil { return } _, err = c.client.Do(req, &nl) return } func (c *NodeClient) ListServers(ctx context.Context, n *Node) (sl ServerRefList, err error) { req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/nodes/%s/qemu", n.Name), nil) if err != nil { return } _, err = c.client.Do(req, &sl) if err == nil { for i := range sl { sl[i].Node = n.Name } } return } func (c *NodeClient) FindServer(ctx context.Context, name string, id int) (ref *ServerRef, err error) { valid := ValidateID(id) == nil nl, err := c.List(ctx) if err != nil { return } var ( nodeOffline bool nodeNotSearched bool ) for _, node := range nl { if node.Status != NodeStatusOnline { nodeOffline = true continue } sl, err := c.ListServers(ctx, node) if err != nil { nodeNotSearched = true continue } for _, s := range sl { if valid && s.ID == id { return s, nil } if name != "" && s.Name == name { if ref != nil { return nil, ErrTooManyServersFound } ref = s } } } if nodeOffline { return nil, ErrNodesOffline } if nodeNotSearched { return nil, ErrNodesNotSearched } if ref == nil { err = ErrServerNotFound } return } func (c *NodeClient) FindServerByName(ctx context.Context, name string) (s *ServerRef, err error) { return c.FindServer(ctx, name, InvalidID) } func (c *NodeClient) FindServerByID(ctx context.Context, id int) (s *ServerRef, err error) { err = ValidateID(id) if err != nil { return } return c.FindServer(ctx, "", id) } func (c *NodeClient) FindServerByURL(ctx context.Context, url string) (s *ServerRef, err error) { _, _, id, err := ParseURL(url) if err != nil { return } return c.FindServerByID(ctx, id) }