forked from shaba/openuds
proxmox publications works (but not functional yet)
This commit is contained in:
parent
b3c9f07d36
commit
23bbda845c
@ -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',
|
||||
|
@ -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)
|
||||
|
@ -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]
|
||||
|
@ -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 = ''
|
||||
|
@ -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',
|
||||
|
@ -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)
|
||||
|
@ -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...
|
||||
|
@ -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)
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user