diff --git a/src/server/vms/vms.go b/src/server/vms/vms.go index 72a0cbb92507472cebde7b3fc75753b57d1a31c7..8ddf6c8750eea3d27755d751e1d6f900490498a1 100644 --- a/src/server/vms/vms.go +++ b/src/server/vms/vms.go @@ -29,7 +29,7 @@ type ( m map[string]*VM dir string // Base directory where VMs are stored rwlock *sync.RWMutex // RWlock to ensure the VMs' map (m) coherency - usedPorts [65536]bool // Ports used by VMs + usedPorts [65536]bool // Ports used by VMs for spice usedRAM int // RAM used by running VMs (in MB) } ) @@ -51,6 +51,9 @@ func GetVMsInstance() *VMs { func InitVMs() error { vmsDir := conf.VMsDir vms = &VMs { m: make(map[string]*VM), dir: vmsDir, rwlock: new(sync.RWMutex), usedRAM: 0 } + for i, _ := range vms.usedPorts { + vms.usedPorts[i] = false + } vms.usedPorts[conf.Core.APIDefaultPort] = true errMsg := "Failed reading VMs directory: " @@ -324,19 +327,40 @@ func (vms *VMs)StartVMWithCreds(vmID uuid.UUID, port int, checkPort bool, pwd st return nil } -// Allocates and returns a free port randomly chosen within [VMSpiceMinPort,VMSpiceMaxPort]. +// Allocates and returns a free port, randomly chosen between [VMSpiceMinPort,VMSpiceMaxPort]. +// When the number of free port becomes very small, +// randomly picking a port is very time consuming (many attempts). +// TODO: A better approach would be to do this instead: +// If there are more than 20% of free ports: pick one randomly in the range. +// Otherwise: pick the first available one either from the beginning or the end (randomly). // REMARK: this function updates the vms map. // Concurrency: safe -func (vms *VMs)allocateFreeRandomPort() int { +func (vms *VMs)allocateFreeRandomPort() (int, error) { vms.rwlock.Lock() defer vms.rwlock.Unlock() + minPort := conf.Core.VMSpiceMinPort + maxPort := conf.Core.VMSpiceMaxPort + + isFreePortLeft := func() bool { + for i := 0; i < maxPort-minPort+1; i++ { + port := minPort+i + if !vms.usedPorts[port] && utils.IsPortAvailable(port) { + return true + } + } + return false + } + for { - port := utils.Rand(conf.Core.VMSpiceMinPort, conf.Core.VMSpiceMaxPort) + if !isFreePortLeft() { + return -1, errors.New("No free port left") + } + port := utils.Rand(minPort, maxPort) if !vms.usedPorts[port] { if utils.IsPortAvailable(port) { vms.usedPorts[port] = true - return port + return port, nil } } } @@ -346,13 +370,18 @@ func (vms *VMs)allocateFreeRandomPort() int { // Returns the port on which the VM is running and the access password. // Concurrency: safe func (vms *VMs)StartVM(vmID uuid.UUID) (int, string, error) { - port := vms.allocateFreeRandomPort() + port, err := vms.allocateFreeRandomPort() + if err != nil { + msg := "Failed starting VM "+vmID.String()+": "+err.Error() + log.Error(msg) + return -1, "", errors.New(msg) + } // Randomly generates a 8 characters long password with 4 digits, 0 symbols, // allowing upper and lower case letters, disallowing repeat characters. pwd, err := passwordGen.Generate(conf.VMPwdLength, conf.VMPwdDigitCount, conf.VMPwdSymbolCount, false, conf.VMPwdRepeatChars) if err != nil { - msg := "Failed starting VM: password generation error: "+err.Error() + msg := "Failed starting VM "+vmID.String()+": password generation error: "+err.Error() log.Error(msg) return -1, "", errors.New(msg) }