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:
parent
56492ccd2d
commit
52437afcf8
@ -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)
|
||||
|
||||
|
@ -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 # 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']]
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user