1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-02-08 05:57:39 +03:00

Proxmox is now working

This commit is contained in:
Adolfo Gómez García 2020-02-24 17:02:33 +01:00
parent 56492ccd2d
commit 52437afcf8
7 changed files with 137 additions and 67 deletions

View File

@ -83,7 +83,7 @@ class OVirtDeferredRemoval(jobs.Job):
logger.debug('Looking for deferred vm removals')
provider: Provider
# Look for Providers of type VCServiceProvider
# Look for Providers of type Ovirt
for provider in Provider.objects.filter(maintenance_mode=False, data_type=OVirtProvider.typeType):
logger.debug('Provider %s if os type ovirt', provider)

View File

@ -34,7 +34,10 @@ class ProxmoxAuthError(ProxmoxError):
pass
class PromxmoxNotFound(ProxmoxError):
class ProxmoxNotFound(ProxmoxError):
pass
class ProxmoxNodeUnavailableError(ProxmoxConnectionError):
pass
# caching helper
@ -87,6 +90,19 @@ class ProxmoxClient:
'CSRFPreventionToken': self._csrf
}
@staticmethod
def checkError(response: requests.Response) -> typing.Any:
if not response.ok:
if response.status_code == 595:
raise ProxmoxNodeUnavailableError()
if response.status_code // 100 == 4:
raise ProxmoxAuthError()
raise ProxmoxError()
return response.json()
def _getPath(self, path: str) -> str:
return self._url + path
@ -101,10 +117,7 @@ class ProxmoxClient:
logger.debug('GET result to %s: %s -- %s', path, result.status_code, result.content)
if not result.ok:
raise ProxmoxAuthError()
return result.json()
return ProxmoxClient.checkError(result)
def _post(self, path: str, data: typing.Optional[typing.Iterable[typing.Tuple[str, str]]] = None) -> typing.Any:
result = requests.post(
@ -118,10 +131,7 @@ class ProxmoxClient:
logger.debug('POST result to %s: %s -- %s', path, result.status_code, result.content)
if not result.ok:
raise ProxmoxError(result.content)
return result.json()
return ProxmoxClient.checkError(result)
def _delete(self, path: str, data: typing.Optional[typing.Iterable[typing.Tuple[str, str]]] = None) -> typing.Any:
result = requests.delete(
@ -133,16 +143,21 @@ class ProxmoxClient:
timeout=self._timeout
)
logger.debug('POST result to %s: %s -- %s', path, result.status_code, result.content)
logger.debug('DELETE result to %s: %s -- %s', path, result.status_code, result.content)
if not result.ok:
raise ProxmoxError(result.content)
return ProxmoxClient.checkError(result)
return result.json()
def connect(self) -> None:
def connect(self, force=False) -> None:
if self._ticket:
return
return # Already connected
# we could cache this for a while, we know that at least for 30 minutes
if self.cache and not force:
dc = self.cache.get(self._host + 'conn')
if dc: # Stored on cache
self._ticket, self._csrf = dc
return
try:
result = requests.post(
url=self._getPath('access/ticket'),
@ -156,6 +171,9 @@ class ProxmoxClient:
data = result.json()['data']
self._ticket = data['ticket']
self._csrf = data['CSRFPreventionToken']
if self.cache:
self.cache.put(self._host + 'conn', (self._ticket, self._csrf), validity=1800) # 30 minutes
except requests.RequestException as e:
raise ProxmoxConnectionError from e
@ -188,15 +206,16 @@ class ProxmoxClient:
weightFnc = lambda x: (x.mem /x .maxmem) + x.cpu
for node in self.getNodesStats():
if node.status != 'online':
if node.status != 'online': # Offline nodes are not "the best"
continue
if minMemory and node.mem < minMemory + 512000000: # 512 MB reserved
continue # Skips nodes with not enouhg memory
if weightFnc(node) < weightFnc(best):
best = node
print(node.name, node.mem / node.maxmem * 100, node.cpu)
# logger.debug('Node values for best: %s %f %f', node.name, node.mem / node.maxmem * 100, node.cpu)
return best if best.status == 'online' else None
@ -209,6 +228,7 @@ class ProxmoxClient:
linkedClone: bool,
toNode: typing.Optional[str] = None,
toStorage: typing.Optional[str] = None,
toPool: typing.Optional[str] = None,
memory: int = 0
) -> types.VmCreationResult:
newVmId = self.getNextVMId()
@ -217,6 +237,7 @@ class ProxmoxClient:
fromNode = vmInfo.node
if not toNode:
logger.debug('Selecting best node')
# If storage is not shared, must be done on same as origin
if toStorage and self.getStorage(toStorage, vmInfo.node).shared:
node = self.getBestNodeForVm(minMemory=-1)
@ -243,6 +264,9 @@ class ProxmoxClient:
if toStorage and linkedClone is False:
params.append(('storage', toStorage))
if toPool:
params.append(('pool', toPool))
logger.debug('PARAMS: %s', params)
return types.VmCreationResult(
@ -274,7 +298,7 @@ class ProxmoxClient:
def listVms(self, node: typing.Union[None, str, typing.Iterable[str]] = None) -> typing.List[types.VMInfo]:
nodeList: typing.Iterable[str]
if node is None:
nodeList = [n.name for n in self.getClusterInfo().nodes]
nodeList = [n.name for n in self.getClusterInfo().nodes if n.online]
elif isinstance(node, str):
nodeList = [node]
else:
@ -289,22 +313,30 @@ class ProxmoxClient:
return sorted(result, key=lambda x: '{}{}'.format(x.node, x.name))
@ensureConected
@allowCache('vmi', CACHE_DURATION, cachingArgs=[1, 2], cachingKWArgs=['vmId', 'node'], cachingKeyFnc=cachingKeyHelper)
# @allowCache('vmi', CACHE_DURATION, cachingArgs=[1, 2], cachingKWArgs=['vmId', 'node'], cachingKeyFnc=cachingKeyHelper)
def getVmInfo(self, vmId: int, node: typing.Optional[str] = None, **kwargs) -> types.VMInfo:
# TODO: try first form cache?
nodes = [types.Node(node, False, False, 0, '', '', '')] if node else self.getClusterInfo().nodes
anyNodeIsDown = False
for n in nodes:
try:
vm = self._get('nodes/{}/qemu/{}/status/current'.format(n.name, vmId))['data']
vm['node'] = n.name
return types.VMInfo.fromDict(vm)
except ProxmoxConnectionError:
anyNodeIsDown = True
except ProxmoxAuthError:
raise
except ProxmoxError:
pass # Not found, try next
raise PromxmoxNotFound()
pass # Any other error, ignore this node (not found in that node)
if anyNodeIsDown:
raise ProxmoxNodeUnavailableError()
raise ProxmoxNotFound()
@ensureConected
@allowCache('vmc', CACHE_DURATION, cachingArgs=[1, 2], cachingKWArgs=['vmId', 'node'], cachingKeyFnc=cachingKeyHelper)
# @allowCache('vmc', CACHE_DURATION, cachingArgs=[1, 2], cachingKWArgs=['vmId', 'node'], cachingKeyFnc=cachingKeyHelper)
def getVmConfiguration(self, vmId: int, node: typing.Optional[str] = None, **kwargs):
node = node or self.getVmInfo(vmId).node
return types.VMConfiguration.fromDict(self._get('nodes/{}/qemu/{}/config'.format(node, vmId))['data'])
@ -364,6 +396,11 @@ class ProxmoxClient:
return result
@ensureConected
@allowCache('nodeStats', CACHE_DURATION, cachingKeyFnc=cachingKeyHelper)
@allowCache('nodeStats', CACHE_DURATION//6, cachingKeyFnc=cachingKeyHelper)
def getNodesStats(self, **kwargs) -> typing.List[types.NodeStats]:
return [types.NodeStats.fromDict(nodeStat) for nodeStat in self._get('cluster/resources?type=node')['data']]
@ensureConected
@allowCache('pools', CACHE_DURATION//6, cachingKeyFnc=cachingKeyHelper)
def listPools(self) -> typing.List[types.PoolInfo]:
return [types.PoolInfo.fromDict(nodeStat) for nodeStat in self._get('pools')['data']]

View File

@ -219,3 +219,12 @@ class StorageInfo(typing.NamedTuple):
@staticmethod
def fromDict(dictionary: typing.MutableMapping[str, typing.Any]) -> 'StorageInfo':
return convertFromDict(StorageInfo, dictionary)
class PoolInfo(typing.NamedTuple):
poolid: str
comments: str
@staticmethod
def fromDict(dictionary: typing.MutableMapping[str, typing.Any]) -> 'PoolInfo':
return convertFromDict(PoolInfo, dictionary)

View File

@ -67,8 +67,8 @@ class ProxmoxDeployment(services.UserDeployment):
The logic for managing ovirt deployments (user machines in this case) is here.
"""
# : Recheck every six seconds by default (for task methods)
suggestedTime = 6
# : Recheck every this seconds by default (for task methods)
suggestedTime = 15
# own vars
_name: str
@ -278,6 +278,7 @@ if sys.platform == 'win32':
opSuspend: self.__suspendMachine,
opWait: self.__wait,
opRemove: self.__remove,
opGetMac: self.__getMac
}
try:
@ -324,8 +325,7 @@ if sys.platform == 'win32':
self.__setTask(taskResult.upid)
if self._vmid is None:
raise Exception('Can\'t create machine')
self._vmid = str(taskResult.vmid)
return State.RUNNING
@ -336,11 +336,12 @@ if sys.platform == 'win32':
try:
vmInfo = self.service().getMachineInfo(int(self._vmid))
except Exception:
raise Exception('Machine not found')
raise Exception('Machine not found on remove machine')
if vmInfo.status != 'stopped':
self.__pushFrontOp(opStop)
self.__executeQueue()
logger.debug('Info status: %s', vmInfo)
self._queue = [opStop, opRemove, opFinish]
return self.__executeQueue()
else:
self.__setTask(self.service().removeMachine(int(self._vmid)))
@ -350,7 +351,7 @@ if sys.platform == 'win32':
try:
vmInfo = self.service().getMachineInfo(int(self._vmid))
except Exception:
raise Exception('Machine not found')
raise Exception('Machine not found on start machine')
if vmInfo.status == 'stopped':
self.__setTask(self.service().startMachine(int(self._vmid)))
@ -361,10 +362,11 @@ if sys.platform == 'win32':
try:
vmInfo = self.service().getMachineInfo(int(self._vmid))
except Exception:
raise Exception('Machine not found')
raise Exception('Machine not found on stop machine')
if vmInfo.status == 'stopped':
self.__setTask(self.service().startMachine(int(self._vmid)))
if vmInfo.status != 'stopped':
logger.debug('Stopping machine %s', vmInfo)
self.__setTask(self.service().stopMachine(int(self._vmid)))
return State.RUNNING
@ -372,18 +374,19 @@ if sys.platform == 'win32':
try:
vmInfo = self.service().getMachineInfo(int(self._vmid))
except Exception:
raise Exception('Machine not found')
raise Exception('Machine not found on suspend machine')
if vmInfo.status == 'stopped':
if vmInfo.status != 'stopped':
self.__setTask(self.service().suspendMachine(int(self._vmid)))
return State.RUNNING
def __changeMac(self) -> str:
def __getMac(self) -> str:
try:
self._mac = self.service().getMac(int(self._vmid))
except Exception:
raise Exception('Machine not found')
logger.exception('Getting MAC on proxmox')
raise Exception('Machine not found getting mac')
return State.RUNNING
# Check methods
@ -553,7 +556,7 @@ if sys.platform == 'win32':
opError: 'error',
opFinish: 'finish',
opRetry: 'retry',
opGetMac: 'changing mac'
opGetMac: 'getting mac'
}.get(op, '????')
def __debug(self, txt):

View File

@ -36,10 +36,9 @@ from uds.core import jobs
from uds.models import Provider
from . import provider
from . import client
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from . import client
logger = logging.getLogger(__name__)
@ -60,8 +59,7 @@ class ProxmoxDeferredRemoval(jobs.Job):
ProxmoxDeferredRemoval.waitForTaskFinish(providerInstance, providerInstance.stopMachine(vmId))
ProxmoxDeferredRemoval.waitForTaskFinish(providerInstance, providerInstance.removeMachine(vmId))
except client.PromxmoxNotFound:
except client.ProxmoxNotFound:
return # Machine does not exists
except Exception as e:
providerInstance.storage.saveData('tr' + str(vmId), str(vmId), attr1='tRm')
@ -78,7 +76,7 @@ class ProxmoxDeferredRemoval(jobs.Job):
def run(self) -> None:
dbProvider: Provider
# Look for Providers of type VCServiceProvider
# Look for Providers of type proxmox
for dbProvider in Provider.objects.filter(maintenance_mode=False, data_type=provider.ProxmoxProvider.typeType):
logger.debug('Provider %s if os type proxmox', dbProvider)
@ -103,10 +101,10 @@ class ProxmoxDeferredRemoval(jobs.Job):
# It this is reached, remove check
storage.remove('tr' + str(vmId))
except client.PromxmoxNotFound:
except client.ProxmoxNotFound:
storage.remove('tr' + str(vmId)) # VM does not exists anymore
except Exception as e: # Any other exception wil be threated again
# instance.doLog('Delayed removal of %s has failed: %s. Will retry later', vmId, e)
logger.error('Delayed removal of %s failed: %s', i, e)
logger.debug('Deferred removal finished')
logger.debug('Deferred removal for proxmox finished')

View File

@ -60,13 +60,13 @@ class ProxmoxProvider(services.ServiceProvider): # pylint: disable=too-many-pub
host = gui.TextField(length=64, label=_('Host'), order=1, tooltip=_('Proxmox Server IP or Hostname'), required=True)
port = gui.NumericField(lengh=5, label=_('Port'), order=2, tooltip=_('Proxmox API port (default is 8006)'), required=True, defvalue='8006')
username = gui.TextField(length=32, label=_('Username'), order=3, tooltip=_('User with valid privileges on Proxmox, (use "user@domain" form)'), required=True, defvalue='root@pam')
username = gui.TextField(length=32, label=_('Username'), order=3, tooltip=_('User with valid privileges on Proxmox, (use "user@authenticator" form)'), required=True, defvalue='root@pam')
password = gui.PasswordField(lenth=32, label=_('Password'), order=4, tooltip=_('Password of the user of oVirt'), required=True)
maxPreparingServices = gui.NumericField(length=3, label=_('Creation concurrency'), defvalue='10', minValue=1, maxValue=65536, order=50, tooltip=_('Maximum number of concurrently creating VMs'), required=True, tab=gui.ADVANCED_TAB)
maxRemovingServices = gui.NumericField(length=3, label=_('Removal concurrency'), defvalue='5', minValue=1, maxValue=65536, order=51, tooltip=_('Maximum number of concurrently removing VMs'), required=True, tab=gui.ADVANCED_TAB)
timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=90, tooltip=_('Timeout in seconds of connection to oVirt'), required=True, tab=gui.ADVANCED_TAB)
timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='20', order=90, tooltip=_('Timeout in seconds of connection to oVirt'), required=True, tab=gui.ADVANCED_TAB)
# Own variables
_api: typing.Optional[client.ProxmoxClient] = None
@ -108,16 +108,19 @@ class ProxmoxProvider(services.ServiceProvider): # pylint: disable=too-many-pub
return self.__getApi().listVms()
def getMachineInfo(self, vmId: int) -> client.types.VMInfo:
return self.__getApi().getVmInfo(vmId)
return self.__getApi().getVmInfo(vmId, force=True)
def getMachineConfiguration(self, vmId: int) -> client.types.VMConfiguration:
return self.__getApi().getVmConfiguration(vmId)
return self.__getApi().getVmConfiguration(vmId, force=True)
def getStorageInfo(self, storageId: str, node: str) -> client.types.StorageInfo:
return self.__getApi().getStorage(storageId, node)
def listStorages(self, node: typing.Optional[str]) -> typing.List[client.types.StorageInfo]:
return self.__getApi().listStorages(node=node)
return self.__getApi().listStorages(node=node, content='images')
def listPools(self) -> typing.List[client.types.PoolInfo]:
return self.__getApi().listPools()
def makeTemplate(self, vmId: int) -> None:
return self.__getApi().convertToTemplate(vmId)
@ -130,9 +133,10 @@ class ProxmoxProvider(services.ServiceProvider): # pylint: disable=too-many-pub
linkedClone: bool,
toNode: typing.Optional[str] = None,
toStorage: typing.Optional[str] = None,
toPool: typing.Optional[str] = None,
memory: int = 0
) -> client.types.VmCreationResult:
return self.__getApi().cloneVm(vmId, name, description, linkedClone, toNode, toStorage, memory)
return self.__getApi().cloneVm(vmId, name, description, linkedClone, toNode, toStorage, toPool, memory)
def startMachine(self,vmId: int) -> client.types.UPID:
return self.__getApi().startVm(vmId)

View File

@ -103,9 +103,18 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
# : Types of deploys (services in cache and/or assigned to users)
deployedType = ProxmoxDeployment
allowedProtocols = protocols.GENERIC + (protocols.SPICE,)
allowedProtocols = protocols.GENERIC # + (protocols.SPICE,)
servicesTypeProvided = (serviceTypes.VDI,)
pool = gui.ChoiceField(
label=_("Pool"),
order=109,
tooltip=_('Pool that will contain UDS created vms'),
# tab=_('Machine'),
# required=True,
defvalue=''
)
machine = gui.ChoiceField(
label=_("Base Machine"),
order=110,
@ -175,15 +184,10 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
self.ov.defValue = self.parent().serialize()
self.ev.defValue = self.parent().env.key
vals = []
m: client.types.VMInfo
for m in self.parent().listMachines():
if m.name and m.name[:3] != 'UDS':
vals.append(gui.choiceItem(str(m.vmid), '{}\{}'.format(m.node, m.name)))
# This is not the same case, values is not the "value" of the field, but
# the list of values shown because this is a "ChoiceField"
self.machine.setValues(vals)
self.machine.setValues([gui.choiceItem(str(m.vmid), '{}\{}'.format(m.node, m.name or m.vmid)) for m in self.parent().listMachines() if m.name and m.name[:3] != 'UDS'])
self.pool.setValues([gui.choiceItem('', _('None'))] + [gui.choiceItem(p.poolid, p.poolid) for p in self.parent().listPools()])
def parent(self) -> 'ProxmoxProvider':
return typing.cast('ProxmoxProvider', super().parent())
@ -199,11 +203,26 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
def cloneMachine(self, name: str, description: str, vmId: int = -1) -> 'client.types.VmCreationResult':
name = self.sanitizeVmName(name)
if vmId == -1:
node = self.parent().getMachineInfo(self.machine.value).node
return self.parent().cloneMachine(self.machine.value, name, description, linkedClone=False, toNode=node, toStorage=self.datastore.value)
pool = self.pool.value or None
if vmId == -1: # vmId == -1 if cloning for template
return self.parent().cloneMachine(
self.machine.value,
name,
description,
linkedClone=False,
toStorage=self.datastore.value,
toPool=pool
)
return self.parent().cloneMachine(vmId, name, description, linkedClone=True, toStorage=self.datastore.value, memory=self.memory.num()*1024*1024)
return self.parent().cloneMachine(
vmId,
name,
description,
linkedClone=True,
toStorage=self.datastore.value,
toPool=pool,
memory=self.memory.num()*1024*1024
)
def getMachineInfo(self, vmId: int) -> 'client.types.VMInfo':
return self.parent().getMachineInfo(vmId)