proxmox publications works (but not functional yet)

This commit is contained in:
Adolfo Gómez García 2020-02-24 01:49:16 +01:00
parent b3c9f07d36
commit 23bbda845c
9 changed files with 228 additions and 369 deletions

View File

@ -33,7 +33,7 @@ def getResources(parameters: typing.Any) -> typing.List[typing.Dict[str, typing.
if storage['type'] == 'data':
space, free = (storage['available'] + storage['used']) / 1024 / 1024 / 1024, storage['available'] / 1024 / 1024 / 1024
res.append({'id': storage['id'], 'text': "%s (%4.2f Gb/%4.2f Gb) %s" % (storage['name'], space, free, storage['active'] and '(ok)' or '(disabled)')})
res.append({'id': storage['id'], 'text': "%s (%4.2f GB/%4.2f GB) %s" % (storage['name'], space, free, storage['active'] and '(ok)' or '(disabled)')})
data = [
{
'name': 'datastore',

View File

@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# Copyright (c) 2012-2020 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -30,8 +28,8 @@
from uds.core import managers
from .provider import ProxmoxProvider
from .jobs import OVirtHouseKeeping, OVirtDeferredRemoval
from .jobs import ProxmoxDeferredRemoval
# Scheduled task to do clean processes
for cls in (OVirtDeferredRemoval, OVirtHouseKeeping):
for cls in (ProxmoxDeferredRemoval, ):
managers.taskManager().registerJob(cls)

View File

@ -342,7 +342,7 @@ class ProxmoxClient:
@ensureConected
@allowCache('storages', CACHE_DURATION, cachingArgs=[1, 2], cachingKWArgs=['node', 'content'], cachingKeyFnc=cachingKeyHelper)
def listStorage(self, node: typing.Union[None, str, typing.Iterable[str]] = None, content: typing.Optional[str] = None, **kwargs) -> typing.List[types.StorageInfo]:
def listStorages(self, node: typing.Union[None, str, typing.Iterable[str]] = None, content: typing.Optional[str] = None, **kwargs) -> typing.List[types.StorageInfo]:
"""We use a list for storage instead of an iterator, so we can cache it...
"""
nodeList: typing.Iterable[str]

View File

@ -39,13 +39,13 @@ from uds.core import managers
from uds.core.util.state import State
from uds.core.util import log
from .jobs import OVirtDeferredRemoval
from .jobs import ProxmoxDeferredRemoval
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from uds import models
from .service import OVirtLinkedService
from .publication import OVirtPublication
from .service import ProxmoxLinkedService
from .publication import ProxmoxPublication
logger = logging.getLogger(__name__)
@ -55,7 +55,7 @@ NO_MORE_NAMES = 'NO-NAME-ERROR'
UP_STATES = ('up', 'reboot_in_progress', 'powering_up', 'restoring_state')
class OVirtLinkedDeployment(services.UserDeployment):
class ProxmoxDeployment(services.UserDeployment):
"""
This class generates the user consumable elements of the service tree.
@ -78,14 +78,14 @@ class OVirtLinkedDeployment(services.UserDeployment):
_queue: typing.List[int]
# Utility overrides for type checking...
def service(self) -> 'OVirtLinkedService':
return typing.cast('OVirtLinkedService', super().service())
def service(self) -> 'ProxmoxLinkedService':
return typing.cast('ProxmoxLinkedService', super().service())
def publication(self) -> 'OVirtPublication':
def publication(self) -> 'ProxmoxPublication':
pub = super().publication()
if pub is None:
raise Exception('No publication for this element!')
return typing.cast('OVirtPublication', pub)
return typing.cast('ProxmoxPublication', pub)
def initialize(self):
self._name = ''

View File

@ -1,39 +1,64 @@
#
# Copyright (c) 2012-2020 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Created on Nov 15, 2012-2019
@author: dkmaster
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import logging
import typing
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from .provider import OVirtProvider
from django.utils.translation import ugettext as _
logger = logging.getLogger(__name__)
def getResources(parameters: typing.Any) -> typing.List[typing.Dict[str, typing.Any]]:
"""
This helper is designed as a callback for machine selector, so we can provide valid clusters and datastores domains based on it
"""
from .provider import OVirtProvider
def getStorage(parameters: typing.Any) -> typing.List[typing.Dict[str, typing.Any]]:
from .provider import ProxmoxProvider
from uds.core.environment import Environment
logger.debug('Parameters received by getResources Helper: %s', parameters)
env = Environment(parameters['ev'])
provider: 'OVirtProvider' = OVirtProvider(env)
provider: ProxmoxProvider = ProxmoxProvider(env)
provider.unserialize(parameters['ov'])
# Obtains datacenter from cluster
ci = provider.getClusterInfo(parameters['cluster'])
try:
vmInfo = provider.getMachineInfo(int(parameters['machine']))
except Exception:
return []
storages = provider.listStorages(vmInfo.node)
res = []
# Get storages for that datacenter
for storage in provider.getDatacenterInfo(ci['datacenter_id'])['storage']:
if storage['type'] == 'data':
space, free = (storage['available'] + storage['used']) / 1024 / 1024 / 1024, storage['available'] / 1024 / 1024 / 1024
for storage in sorted(provider.listStorages(vmInfo.node), key=lambda x: int(not x.shared)):
space, free = storage.avail / 1024 / 1024 / 1024, (storage.avail - storage.used) / 1024 / 1024 / 1024
extra = _(' shared') if storage.shared else _(' (bound to {})').format(vmInfo.node)
res.append({'id': storage.storage, 'text': "%s (%4.2f GB/%4.2f GB)%s" % (storage.storage, space, free, extra)})
res.append({'id': storage['id'], 'text': "%s (%4.2f Gb/%4.2f Gb) %s" % (storage['name'], space, free, storage['active'] and '(ok)' or '(disabled)')})
data = [
{
'name': 'datastore',

View File

@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# Copyright (c) 2012-2020 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -29,6 +27,7 @@
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import time
import logging
import typing
@ -36,76 +35,76 @@ from uds.core import jobs
from uds.models import Provider
from . import provider
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from .provider import OVirtProvider
from . import client
logger = logging.getLogger(__name__)
class OVirtHouseKeeping(jobs.Job):
frecuency = 60 * 60 * 24 * 15 + 1 # Once every 15 days
friendly_name = 'Ovirt house keeping'
def run(self):
return
class OVirtDeferredRemoval(jobs.Job):
class ProxmoxDeferredRemoval(jobs.Job):
frecuency = 60 * 5 # Once every NN minutes
friendly_name = 'Ovirt removal'
counter = 0
@staticmethod
def remove(providerInstance: 'OVirtProvider', vmId: str) -> None:
def remove(providerInstance: 'provider.ProxmoxProvider', vmId: int) -> None:
logger.debug('Adding %s from %s to defeffed removal process', vmId, providerInstance)
OVirtDeferredRemoval.counter += 1
ProxmoxDeferredRemoval.counter += 1
try:
# Tries to stop machine sync when found, if any error is done, defer removal for a scheduled task
try:
# First check state & stop machine if needed
state = providerInstance.getMachineState(vmId)
if state in ('up', 'powering_up', 'suspended'):
providerInstance.stopMachine(vmId)
elif state != 'unknown': # Machine exists, remove it later
providerInstance.storage.saveData('tr' + vmId, vmId, attr1='tRm')
# First check state & stop machine if needed
vmInfo = providerInstance.getMachineInfo(vmId)
if vmInfo.status == 'running':
# If running vm, simply stops it and wait for next
ProxmoxDeferredRemoval.waitForTaskFinish(providerInstance, providerInstance.stopMachine(vmId))
except Exception as e:
providerInstance.storage.saveData('tr' + vmId, vmId, attr1='tRm')
logger.info('Machine %s could not be removed right now, queued for later: %s', vmId, e)
ProxmoxDeferredRemoval.waitForTaskFinish(providerInstance, providerInstance.removeMachine(vmId))
except client.PromxmoxNotFound:
return # Machine does not exists
except Exception as e:
logger.warning('Exception got queuing for Removal: %s', e)
providerInstance.storage.saveData('tr' + str(vmId), str(vmId), attr1='tRm')
logger.info('Machine %s could not be removed right now, queued for later: %s', vmId, e)
@staticmethod
def waitForTaskFinish(providerInstance: 'provider.ProxmoxProvider', upid: 'client.types.UPID', maxWait: int = 30) -> bool:
counter = 0
while providerInstance.getTaskInfo(upid.node, upid.upid).isRunning() and counter < maxWait:
time.sleep(0.3)
counter += 1
return counter < maxWait
def run(self) -> None:
from .provider import OVirtProvider
logger.debug('Looking for deferred vm removals')
provider: Provider
dbProvider: Provider
# Look for Providers of type VCServiceProvider
for provider in Provider.objects.filter(maintenance_mode=False, data_type=OVirtProvider.typeType):
logger.debug('Provider %s if os type ovirt', provider)
for dbProvider in Provider.objects.filter(maintenance_mode=False, data_type=provider.ProxmoxProvider.typeType):
logger.debug('Provider %s if os type proxmox', dbProvider)
storage = provider.getEnvironment().storage
instance: OVirtProvider = typing.cast(OVirtProvider, provider.getInstance())
storage = dbProvider.getEnvironment().storage
instance: provider.ProxmoxProvider = typing.cast(provider.ProxmoxProvider, dbProvider.getInstance())
for i in storage.filter('tRm'):
vmId = i[1].decode()
vmId = int(i[1].decode())
try:
vmInfo = instance.getMachineInfo(vmId)
logger.debug('Found %s for removal %s', vmId, i)
# If machine is powered on, tries to stop it
# tries to remove in sync mode
state = instance.getMachineState(vmId)
if state in ('up', 'powering_up', 'suspended'):
instance.stopMachine(vmId)
if vmInfo.status == 'running':
ProxmoxDeferredRemoval.waitForTaskFinish(instance, instance.stopMachine(vmId))
return
if state != 'unknown': # Machine exists, try to remove it now
instance.removeMachine(vmId)
if vmInfo.status == 'stopped': # Machine exists, try to remove it now
ProxmoxDeferredRemoval.waitForTaskFinish(instance, instance.removeMachine(vmId))
# It this is reached, remove check
storage.remove('tr' + vmId)
storage.remove('tr' + str(vmId))
except client.PromxmoxNotFound:
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)

View File

@ -106,39 +106,32 @@ class ProxmoxProvider(services.ServiceProvider): # pylint: disable=too-many-pub
return self.__getApi().test()
def getMachines(self) -> typing.List[client.types.VMInfo]:
def listMachines(self) -> typing.List[client.types.VMInfo]:
return self.__getApi().listVms()
def getStorageInfo(self, storageId: str, force: bool = False) -> client.types.StorageInfo:
"""
Obtains the storage info
def getMachineInfo(self, vmId: int) -> client.types.VMInfo:
return self.__getApi().getVmInfo(vmId)
Args:
storageId: Id of the storage to get information about it
force: If true, force to update the cache, if false, tries to first
get data from cache and, if valid, return this.
def getStorageInfo(self, storageId: str, node: str) -> client.types.StorageInfo:
return self.__getApi().getStorage(storageId, node)
Returns
def listStorages(self, node: typing.Optional[str]) -> typing.List[client.types.StorageInfo]:
return self.__getApi().listStorages(node=node)
A dictionary with following values
'id' -> Storage id
'name' -> Storage name
'type' -> Storage type ('data', 'iso')
'available' -> Space available, in bytes
'used' -> Space used, in bytes
# 'active' -> True or False --> This is not provided by api?? (api.storagedomains.get)
"""
return self.__getApi().getStorage(storageId, force)
def makeTemplate(
self,
vmId: int
) -> None:
def makeTemplate(self, vmId: int) -> None:
return self.__getApi().convertToTemplate(vmId)
def getMachineState(self, vmId: int) -> client.types.VMInfo:
return self.__getApi().getVmInfo(vmId)
def cloneMachine(
self,
vmId: int,
name: str,
description: typing.Optional[str],
linkedClone: bool,
toNode: typing.Optional[str] = None,
toStorage: typing.Optional[str] = None,
memory: int = 0
) -> client.types.VmCreationResult:
return self.__getApi().cloneVm(vmId, name, description, linkedClone, toNode, toStorage, memory)
def startMachine(self,vmId: int) -> client.types.UPID:
return self.__getApi().startVm(vmId)
@ -152,6 +145,9 @@ class ProxmoxProvider(services.ServiceProvider): # pylint: disable=too-many-pub
def removeMachine(self, vmId: int) -> client.types.UPID:
return self.__getApi().deleteVm(vmId)
def getTaskInfo(self, node: str, upid: str) -> client.types.TaskStatus:
return self.__getApi().getTask(node, upid)
def getConsoleConnection(self, machineId: str) -> typing.Optional[typing.MutableMapping[str, typing.Any]]:
# TODO: maybe proxmox also supports "spice"? for future release...

View File

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# All rights reserved.
@ -35,52 +33,47 @@ import logging
import typing
from django.utils.translation import ugettext as _
from uds.core.services import Publication
from uds.core import services
from uds.core.util.state import State
from uds.core.util.auto_attributes import AutoAttributes
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from .service import OVirtLinkedService
from .service import ProxmoxLinkedService
from . import client
logger = logging.getLogger(__name__)
class ProxmoxPublication(services.Publication):
suggestedTime = 20
class OVirtPublication(Publication):
"""
This class provides the publication of a oVirtLinkedService
"""
suggestedTime = 20 # : Suggested recheck time if publication is unfinished in seconds
_name: str
_reason: str
_destroyAfter: str
_templateId: str
_vm: str
_task: str
_state: str
_operation: str
_destroyAfter: str
_reason: str
def service(self) -> 'OVirtLinkedService':
return typing.cast('OVirtLinkedService', super().service())
def initialize(self) -> None:
"""
This method will be invoked by default __init__ of base class, so it gives
us the oportunity to initialize whataver we need here.
In our case, we setup a few attributes..
"""
# We do not check anything at marshal method, so we ensure that
# default values are correctly handled by marshal.
def __init__(self, environment, **kwargs):
services.Publication.__init__(self, environment, **kwargs)
self._name = ''
self._vm = ''
self._task = ''
self._state = ''
self._operation = ''
self._destroyAfter = ''
self._reason = ''
self._destroyAfter = 'f'
self._templateId = ''
self._state = 'r'
# Utility overrides for type checking...
def service(self) -> 'ProxmoxLinkedService':
return typing.cast('ProxmoxLinkedService', super().service())
def marshal(self) -> bytes:
"""
returns data from an instance of Sample Publication serialized
"""
return '\t'.join(['v1', self._name, self._reason, self._destroyAfter, self._templateId, self._state]).encode('utf8')
return '\t'.join(['v1', self._name, self._vm, self._task, self._state, self._operation, self._destroyAfter, self._reason]).encode('utf8')
def unmarshal(self, data: bytes) -> None:
"""
@ -89,101 +82,79 @@ class OVirtPublication(Publication):
logger.debug('Data: %s', data)
vals = data.decode('utf8').split('\t')
if vals[0] == 'v1':
self._name, self._reason, self._destroyAfter, self._templateId, self._state = vals[1:]
self._name, self._vm, self._task, self._state, self._operation, self._destroyAfter, self._reason = vals[1:]
def publish(self) -> str:
"""
Realizes the publication of the service
If no space is available, publication will fail with an error
"""
self._name = self.service().sanitizeVmName('UDSP ' + self.dsName() + "-" + str(self.revision()))
comments = _('UDS pub for {0} at {1}').format(self.dsName(), str(datetime.now()).split('.')[0])
self._reason = '' # No error, no reason for it
self._destroyAfter = 'f'
self._state = 'locked'
try:
self._templateId = self.service().makeTemplate(self._name, comments)
# First we should create a full clone, so base machine do not get fullfilled with "garbage" delta disks...
self._name = 'UDS ' + _('Publication') + ' ' + self.dsName() + "-" + str(self.revision())
comments = _('UDS Publication for {0} created at {1}').format(self.dsName(), str(datetime.now()).split('.')[0])
task = self.service().cloneMachine(self._name, comments)
self._vm = str(task.vmid)
self._task = ','.join((task.upid.node, task.upid.upid))
self._state = State.RUNNING
self._operation = 'p' # Publishing
self._destroyAfter = ''
return State.RUNNING
except Exception as e:
self._state = 'error'
logger.exception('Caught exception %s', e)
self._reason = str(e)
return State.ERROR
return State.RUNNING
def checkState(self) -> str:
"""
Checks state of publication creation
"""
if self._state == 'ok':
return State.FINISHED
if self._state == 'error':
return State.ERROR
def checkState(self) -> str: # pylint: disable = too-many-branches,too-many-return-statements
if self._state != State.RUNNING:
return self._state
node, upid = self._task.split(',')
try:
self._state = self.service().getTemplateState(self._templateId)
if self._state == 'removed':
raise Exception('Template has been removed!')
task = self.service().getTaskInfo(node, upid)
if task.isRunning():
return State.RUNNING
except Exception as e:
self._state = 'error'
logger.exception('Proxmox publication')
self._state = State.ERROR
self._reason = str(e)
return State.ERROR
return self._state
# If publication os done (template is ready), and cancel was requested, do it just after template becomes ready
if self._state == 'ok':
if self._destroyAfter == 't':
if task.isErrored():
self._reason = task.exitstatus
self._state = State.ERROR
else: # Finished
if self._destroyAfter:
return self.destroy()
return State.FINISHED
self._state = State.FINISHED
if self._operation == 'p': # not Destroying
logger.debug('Marking as template')
# Mark vm as template
self.service().makeTemplate(int(self._vm))
return State.RUNNING
return self._state
def reasonOfError(self) -> str:
"""
If a publication produces an error, here we must notify the reason why
it happened. This will be called just after publish or checkState
if they return State.ERROR
Returns an string, in our case, set at checkState
"""
return self._reason
def finish(self) -> None:
self._task = ''
self._destroyAfter = ''
def destroy(self) -> str:
"""
This is called once a publication is no more needed.
This method do whatever needed to clean up things, such as
removing created "external" data (environment gets cleaned by core),
etc..
The retunred value is the same as when publishing, State.RUNNING,
State.FINISHED or State.ERROR.
"""
# We do not do anything else to destroy this instance of publication
if self._state == 'locked':
self._destroyAfter = 't'
if self._state == State.RUNNING and self._destroyAfter is False: # If called destroy twice, will BREAK STOP publication
self._destroyAfter = 'y'
return State.RUNNING
try:
self.service().removeTemplate(self._templateId)
except Exception as e:
self._state = 'error'
self._reason = str(e)
return State.ERROR
return State.FINISHED
self.state = State.RUNNING
self._operation = 'd'
self._destroyAfter = ''
self.service()
task = self.service().removeMachine(self.machine())
self._task = ','.join((task.node, task.upid))
return State.RUNNING
def cancel(self) -> str:
"""
Do same thing as destroy
"""
return self.destroy()
# Here ends the publication needed methods.
# Methods provided below are specific for this publication
# and will be used by user deployments that uses this kind of publication
def reasonOfError(self) -> str:
return self._reason
def getTemplateId(self) -> str:
"""
Returns the template id associated with the publication
"""
return self._templateId
def machine(self) -> int:
return int(self._vm)

View File

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# All rights reserved.
@ -42,8 +40,8 @@ from uds.core.util import validators
from uds.core.util import tools
from uds.core.ui import gui
from .publication import OVirtPublication
from .deployment import OVirtLinkedDeployment
from .publication import ProxmoxPublication
from .deployment import ProxmoxDeployment
from . import helpers
# Not imported at runtime, just for type checking
@ -101,9 +99,9 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
# : Types of publications (preparated data for deploys)
# : In our case, we do no need a publication, so this is None
publicationType = OVirtPublication
publicationType = ProxmoxPublication
# : Types of deploys (services in cache and/or assigned to users)
deployedType = OVirtLinkedDeployment
deployedType = ProxmoxDeployment
allowedProtocols = protocols.GENERIC + (protocols.SPICE,)
servicesTypeProvided = (serviceTypes.VDI,)
@ -111,16 +109,21 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
machine = gui.ChoiceField(
label=_("Base Machine"),
order=110,
fills={
'callbackName': 'pmFillResourcesFromMachine',
'function': helpers.getStorage,
'parameters': ['machine', 'ov', 'ev']
},
tooltip=_('Service base machine'),
tab=_('Machine'),
required=True
)
datastore = gui.ChoiceField(
label=_("Datastore Domain"),
label=_("Storage"),
rdonly=False,
order=111,
tooltip=_('Datastore for publications & machines.'),
tooltip=_('Storage for publications & machines.'),
tab=_('Machine'),
required=True
)
@ -160,33 +163,23 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
ev = gui.HiddenField(value=None) # We need to keep the env so we can instantiate the Provider
def initialize(self, values: 'Module.ValuesType') -> None:
"""
We check here form values to see if they are valid.
Note that we check them throught FROM variables, that already has been
initialized by __init__ method of base class, before invoking this.
"""
if values:
self.baseName.value = validators.validateHostname(self.baseName.value, 15, asPattern=True)
if int(self.memory.value) < 128:
raise Service.ValidationException(_('The minimum allowed memory is 128 Mb'))
def initGui(self) -> None:
"""
Loads required values inside
"""
# Here we have to use "default values", cause values aren't used at form initialization
# This is that value is always '', so if we want to change something, we have to do it
# at defValue
self.ov.defValue = self.parent().serialize()
self.ev.defValue = self.parent().env.key
vals = []
m: client.types.VMInfo
for m in self.parent().getMachines():
vals.append(gui.choiceItem(str(m.vmid), '{}\{}'.format(m.node, m.name)))
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"
@ -199,162 +192,39 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
"""
Ovirt only allows machine names with [a-zA-Z0-9_-]
"""
return re.sub("[^a-zA-Z0-9_-]", "_", name)
return re.sub("[^a-zA-Z0-9_-]", "-", name)
def makeTemplate(self, vmId: int) -> str:
"""
Invokes makeTemplate from parent provider, completing params
def makeTemplate(self, vmId: int) -> None:
self.parent().makeTemplate(vmId)
Args:
name: Name to assign to template (must be previously "sanitized"
comments: Comments (UTF-8) to add to template
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)
Returns:
template Id of the template created
return self.parent().cloneMachine(vmId, name, description, linkedClone=True, toStorage=self.datastore.value)
Raises an exception if operation fails.
"""
def getTaskInfo(self, node: str, upid: str) -> 'client.types.TaskStatus':
return self.parent().getTaskInfo(node, upid)
# Checks datastore size
# Get storages for that datacenter
return self.parent().makeTemplate(name, comments, self.machine.value, self.cluster.value, self.datastore.value, self.display.value)
def startMachine(self,vmId: int) -> 'client.types.UPID':
return self.parent().startMachine(vmId)
def getTemplateState(self, templateId: str) -> str:
"""
Invokes getTemplateState from parent provider
def stopMachine(self, vmId: int) -> 'client.types.UPID':
return self.parent().stopMachine(vmId)
Args:
templateId: templateId to remove
def suspendMachine(self, vmId: int) -> 'client.types.UPID':
return self.parent().suspendMachine(vmId)
Returns nothing
Raises an exception if operation fails.
"""
return self.parent().getTemplateState(templateId)
def deployFromTemplate(self, name: str, comments: str, templateId: str) -> str:
"""
Deploys a virtual machine on selected cluster from selected template
Args:
name: Name (sanitized) of the machine
comments: Comments for machine
templateId: Id of the template to deploy from
displayType: 'vnc' or 'spice'. Display to use ad oVirt admin interface
memoryMB: Memory requested for machine, in MB
guaranteedMB: Minimum memory guaranteed for this machine
Returns:
Id of the machine being created form template
"""
logger.debug('Deploying from template %s machine %s', templateId, name)
self.datastoreHasSpace()
return self.parent().deployFromTemplate(name, comments, templateId, self.cluster.value,
self.display.value, self.usb.value, int(self.memory.value), int(self.memoryGuaranteed.value))
def removeTemplate(self, templateId: str) -> None:
"""
invokes removeTemplate from parent provider
"""
self.parent().removeTemplate(templateId)
def getMachineState(self, machineId: str) -> str:
"""
Invokes getMachineState from parent provider
(returns if machine is "active" or "inactive"
Args:
machineId: If of the machine to get state
Returns:
one of this values:
unassigned, down, up, powering_up, powered_down,
paused, migrating_from, migrating_to, unknown, not_responding,
wait_for_launch, reboot_in_progress, saving_state, restoring_state,
suspended, image_illegal, image_locked or powering_down
Also can return'unknown' if Machine is not known
"""
return self.parent().getMachineState(machineId)
def startMachine(self, machineId: str) -> None:
"""
Tries to start a machine. No check is done, it is simply requested to oVirt.
This start also "resume" suspended/paused machines
Args:
machineId: Id of the machine
Returns:
"""
self.parent().startMachine(machineId)
def stopMachine(self, machineId: str) -> None:
"""
Tries to start a machine. No check is done, it is simply requested to oVirt
Args:
machineId: Id of the machine
Returns:
"""
self.parent().stopMachine(machineId)
def suspendMachine(self, machineId: str) -> None:
"""
Tries to start a machine. No check is done, it is simply requested to oVirt
Args:
machineId: Id of the machine
Returns:
"""
self.parent().suspendMachine(machineId)
def removeMachine(self, machineId: str) -> None:
"""
Tries to delete a machine. No check is done, it is simply requested to oVirt
Args:
machineId: Id of the machine
Returns:
"""
self.parent().removeMachine(machineId)
def updateMachineMac(self, machineId: str, macAddres: str) -> None:
"""
Changes the mac address of first nic of the machine to the one specified
"""
self.parent().updateMachineMac(machineId, macAddres)
def fixUsb(self, machineId: str):
if self.usb.value in ('native',):
self.parent().fixUsb(machineId)
def getMacRange(self) -> str:
"""
Returns de selected mac range
"""
return self.parent().getMacRange()
def removeMachine(self, vmId: int) -> 'client.types.UPID':
return self.parent().removeMachine(vmId)
def getBaseName(self) -> str:
"""
Returns the base name
"""
return self.baseName.value
def getLenName(self) -> int:
"""
Returns the length of numbers part
"""
return int(self.lenName.value)
def getDisplay(self) -> str:
"""
Returns the selected display type (for created machines, for administration
"""
return self.display.value
def getConsoleConnection(self, machineId: str) -> typing.Optional[typing.MutableMapping[str, typing.Any]]:
return self.parent().getConsoleConnection(machineId)