diff --git a/server/src/uds/services/OVirt/helpers.py b/server/src/uds/services/OVirt/helpers.py index 746d9cdc..9f672e38 100644 --- a/server/src/uds/services/OVirt/helpers.py +++ b/server/src/uds/services/OVirt/helpers.py @@ -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', diff --git a/server/src/uds/services/Proxmox/__init__.py b/server/src/uds/services/Proxmox/__init__.py index 5ca49033..b8953547 100644 --- a/server/src/uds/services/Proxmox/__init__.py +++ b/server/src/uds/services/Proxmox/__init__.py @@ -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) diff --git a/server/src/uds/services/Proxmox/client/__init__.py b/server/src/uds/services/Proxmox/client/__init__.py index ecf1edfa..1faacf73 100644 --- a/server/src/uds/services/Proxmox/client/__init__.py +++ b/server/src/uds/services/Proxmox/client/__init__.py @@ -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] diff --git a/server/src/uds/services/Proxmox/deployment.py b/server/src/uds/services/Proxmox/deployment.py index 3e0e80b2..7d5a090f 100644 --- a/server/src/uds/services/Proxmox/deployment.py +++ b/server/src/uds/services/Proxmox/deployment.py @@ -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 = '' diff --git a/server/src/uds/services/Proxmox/helpers.py b/server/src/uds/services/Proxmox/helpers.py index 746d9cdc..3bc9e978 100644 --- a/server/src/uds/services/Proxmox/helpers.py +++ b/server/src/uds/services/Proxmox/helpers.py @@ -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', diff --git a/server/src/uds/services/Proxmox/jobs.py b/server/src/uds/services/Proxmox/jobs.py index 55365911..4c1f2254 100644 --- a/server/src/uds/services/Proxmox/jobs.py +++ b/server/src/uds/services/Proxmox/jobs.py @@ -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) diff --git a/server/src/uds/services/Proxmox/provider.py b/server/src/uds/services/Proxmox/provider.py index 02fc2279..d4e1aba6 100644 --- a/server/src/uds/services/Proxmox/provider.py +++ b/server/src/uds/services/Proxmox/provider.py @@ -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... diff --git a/server/src/uds/services/Proxmox/publication.py b/server/src/uds/services/Proxmox/publication.py index 62a3d0b8..80212660 100644 --- a/server/src/uds/services/Proxmox/publication.py +++ b/server/src/uds/services/Proxmox/publication.py @@ -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) \ No newline at end of file diff --git a/server/src/uds/services/Proxmox/service.py b/server/src/uds/services/Proxmox/service.py index 5b839b81..063ab6c9 100644 --- a/server/src/uds/services/Proxmox/service.py +++ b/server/src/uds/services/Proxmox/service.py @@ -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)