forked from shaba/openuds
Fixed Xen
This commit is contained in:
parent
8d680ad32d
commit
7e034a9de1
@ -85,9 +85,6 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
|
||||
# Serializable needed methods
|
||||
def marshal(self) -> bytes:
|
||||
"""
|
||||
Does nothing right here, we will use envoronment storage in this sample
|
||||
"""
|
||||
return b'\1'.join([
|
||||
b'v1',
|
||||
self._name.encode('utf8'),
|
||||
@ -99,9 +96,6 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
])
|
||||
|
||||
def unmarshal(self, data: bytes) -> None:
|
||||
"""
|
||||
Does nothing here also, all data are keeped at environment storage
|
||||
"""
|
||||
vals = data.split(b'\1')
|
||||
if vals[0] == b'v1':
|
||||
self._name = vals[1].decode('utf8')
|
||||
@ -112,26 +106,6 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
self._queue = pickle.loads(vals[6])
|
||||
|
||||
def getName(self) -> str:
|
||||
"""
|
||||
We override this to return a name to display. Default inplementation
|
||||
(in base class), returns getUniqueIde() value
|
||||
This name will help user to identify elements, and is only used
|
||||
at administration interface.
|
||||
|
||||
We will use here the environment name provided generator to generate
|
||||
a name for this element.
|
||||
|
||||
The namaGenerator need two params, the base name and a length for a
|
||||
numeric incremental part for generating unique names. This are unique for
|
||||
all UDS names generations, that is, UDS will not generate this name again
|
||||
until this name is freed, or object is removed, what makes its environment
|
||||
to also get removed, that makes all uniques ids (names and macs right now)
|
||||
to also get released.
|
||||
|
||||
Every time get method of a generator gets called, the generator creates
|
||||
a new unique name, so we keep the first generated name cached and don't
|
||||
generate more names. (Generator are simple utility classes)
|
||||
"""
|
||||
if self._name == '':
|
||||
try:
|
||||
self._name = self.nameGenerator().get(self.service().getBaseName(), self.service().getLenName())
|
||||
@ -140,57 +114,16 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
return self._name
|
||||
|
||||
def setIp(self, ip: str) -> None:
|
||||
"""
|
||||
In our case, there is no OS manager associated with this, so this method
|
||||
will never get called, but we put here as sample.
|
||||
|
||||
Whenever an os manager actor notifies the broker the state of the service
|
||||
(mainly machines), the implementation of that os manager can (an probably will)
|
||||
need to notify the IP of the deployed service. Remember that UDS treats with
|
||||
IP services, so will probable needed in every service that you will create.
|
||||
:note: This IP is the IP of the "consumed service", so the transport can
|
||||
access it.
|
||||
"""
|
||||
logger.debug('Setting IP to %s', ip)
|
||||
self._ip = ip
|
||||
|
||||
def getUniqueId(self) -> str:
|
||||
"""
|
||||
Return and unique identifier for this service.
|
||||
In our case, we will generate a mac name, that can be also as sample
|
||||
of 'mac' generator use, and probably will get used something like this
|
||||
at some services.
|
||||
|
||||
The get method of a mac generator takes one param, that is the mac range
|
||||
to use to get an unused mac.
|
||||
"""
|
||||
return self._mac.upper()
|
||||
|
||||
def getIp(self) -> str:
|
||||
"""
|
||||
We need to implement this method, so we can return the IP for transports
|
||||
use. If no IP is known for this service, this must return None
|
||||
|
||||
If our sample do not returns an IP, IP transport will never work with
|
||||
this service. Remember in real cases to return a valid IP address if
|
||||
the service is accesible and you alredy know that (for example, because
|
||||
the IP has been assigend via setIp by an os manager) or because
|
||||
you get it for some other method.
|
||||
|
||||
Storage returns None if key is not stored.
|
||||
|
||||
:note: Keeping the IP address is responsibility of the User Deployment.
|
||||
Every time the core needs to provide the service to the user, or
|
||||
show the IP to the administrator, this method will get called
|
||||
|
||||
"""
|
||||
return self._ip
|
||||
|
||||
def setReady(self) -> str:
|
||||
"""
|
||||
The method is invoked whenever a machine is provided to an user, right
|
||||
before presenting it (via transport rendering) to the user.
|
||||
"""
|
||||
if self.cache.get('ready') == '1':
|
||||
return State.FINISHED
|
||||
|
||||
|
@ -27,4 +27,4 @@
|
||||
# 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.
|
||||
|
||||
from .provider import Provider
|
||||
from .provider import XenProvider
|
||||
|
@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -30,18 +29,22 @@
|
||||
"""
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import pickle
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from uds.core.services import UserDeployment
|
||||
from uds.core.util.state import State
|
||||
from uds.core.util import log
|
||||
|
||||
from uds.services.Xen.xen_client import XenPowerState
|
||||
from .xen_client import XenPowerState
|
||||
|
||||
import pickle
|
||||
import logging
|
||||
import six
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds import models
|
||||
from .service import XenLinkedService
|
||||
from .publication import XenPublication
|
||||
from uds.core.util.storage import Storage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -51,34 +54,32 @@ NO_MORE_NAMES = 'NO-NAME-ERROR'
|
||||
|
||||
|
||||
class XenLinkedDeployment(UserDeployment):
|
||||
"""
|
||||
This class generates the user consumable elements of the service tree.
|
||||
|
||||
After creating at administration interface an Deployed Service, UDS will
|
||||
create consumable services for users using UserDeployment class as
|
||||
provider of this elements.
|
||||
|
||||
The logic for managing ovirt deployments (user machines in this case) is here.
|
||||
|
||||
"""
|
||||
|
||||
# : Recheck every six seconds by default (for task methods)
|
||||
suggestedTime = 7
|
||||
|
||||
def initialize(self):
|
||||
self._name = ''
|
||||
self._ip = ''
|
||||
self._mac = ''
|
||||
self._vmid = ''
|
||||
self._reason = ''
|
||||
self._task = ''
|
||||
_name: str = ''
|
||||
_ip: str = ''
|
||||
_mac: str = ''
|
||||
_man: str = ''
|
||||
_vmid: str = ''
|
||||
_reason: str = ''
|
||||
_task = ''
|
||||
_queue: typing.List[int]
|
||||
|
||||
def initialize(self) -> None:
|
||||
self._queue = []
|
||||
|
||||
def service(self) -> 'XenLinkedService':
|
||||
return typing.cast('XenLinkedService', super().service())
|
||||
|
||||
def publication(self) -> 'XenPublication':
|
||||
pub = super().publication()
|
||||
if pub is None:
|
||||
raise Exception('No publication for this element!')
|
||||
return typing.cast('XenPublication', pub)
|
||||
|
||||
# Serializable needed methods
|
||||
def marshal(self):
|
||||
"""
|
||||
Does nothing right here, we will use envoronment storage in this sample
|
||||
"""
|
||||
def marshal(self) -> bytes:
|
||||
return b'\1'.join([
|
||||
b'v1',
|
||||
self._name.encode('utf8'),
|
||||
@ -90,11 +91,8 @@ class XenLinkedDeployment(UserDeployment):
|
||||
self._task.encode('utf8')
|
||||
])
|
||||
|
||||
def unmarshal(self, str_):
|
||||
"""
|
||||
Does nothing here also, all data are keeped at environment storage
|
||||
"""
|
||||
vals = str_.split(b'\1')
|
||||
def unmarshal(self, data: bytes) -> None:
|
||||
vals = data.split(b'\1')
|
||||
logger.debug('Values: %s', vals)
|
||||
if vals[0] == b'v1':
|
||||
self._name = vals[1].decode('utf8')
|
||||
@ -105,106 +103,45 @@ class XenLinkedDeployment(UserDeployment):
|
||||
self._queue = pickle.loads(vals[6])
|
||||
self._task = vals[7].decode('utf8')
|
||||
|
||||
def getName(self):
|
||||
"""
|
||||
We override this to return a name to display. Default inplementation
|
||||
(in base class), returns getUniqueIde() value
|
||||
This name will help user to identify elements, and is only used
|
||||
at administration interface.
|
||||
|
||||
We will use here the environment name provided generator to generate
|
||||
a name for this element.
|
||||
|
||||
The namaGenerator need two params, the base name and a length for a
|
||||
numeric incremental part for generating unique names. This are unique for
|
||||
all UDS names generations, that is, UDS will not generate this name again
|
||||
until this name is freed, or object is removed, what makes its environment
|
||||
to also get removed, that makes all uniques ids (names and macs right now)
|
||||
to also get released.
|
||||
|
||||
Every time get method of a generator gets called, the generator creates
|
||||
a new unique name, so we keep the first generated name cached and don't
|
||||
generate more names. (Generator are simple utility classes)
|
||||
"""
|
||||
if self._name == '':
|
||||
def getName(self) -> str:
|
||||
if not self._name:
|
||||
try:
|
||||
self._name = self.nameGenerator().get(self.service().getBaseName(), self.service().getLenName())
|
||||
except KeyError:
|
||||
return NO_MORE_NAMES
|
||||
return self._name
|
||||
|
||||
def setIp(self, ip):
|
||||
"""
|
||||
In our case, there is no OS manager associated with this, so this method
|
||||
will never get called, but we put here as sample.
|
||||
|
||||
Whenever an os manager actor notifies the broker the state of the service
|
||||
(mainly machines), the implementation of that os manager can (an probably will)
|
||||
need to notify the IP of the deployed service. Remember that UDS treats with
|
||||
IP services, so will probable needed in every service that you will create.
|
||||
:note: This IP is the IP of the "consumed service", so the transport can
|
||||
access it.
|
||||
"""
|
||||
logger.debug('Setting IP to %s' % ip)
|
||||
def setIp(self, ip: str) -> None:
|
||||
logger.debug('Setting IP to %s', ip)
|
||||
self._ip = ip
|
||||
|
||||
def getUniqueId(self):
|
||||
"""
|
||||
Return and unique identifier for this service.
|
||||
In our case, we will generate a mac name, that can be also as sample
|
||||
of 'mac' generator use, and probably will get used something like this
|
||||
at some services.
|
||||
|
||||
The get method of a mac generator takes one param, that is the mac range
|
||||
to use to get an unused mac.
|
||||
"""
|
||||
if self._mac == '':
|
||||
def getUniqueId(self) -> str:
|
||||
if not self._mac:
|
||||
self._mac = self.macGenerator().get(self.service().getMacRange())
|
||||
return self._mac
|
||||
|
||||
def getIp(self):
|
||||
"""
|
||||
We need to implement this method, so we can return the IP for transports
|
||||
use. If no IP is known for this service, this must return None
|
||||
|
||||
If our sample do not returns an IP, IP transport will never work with
|
||||
this service. Remember in real cases to return a valid IP address if
|
||||
the service is accesible and you alredy know that (for example, because
|
||||
the IP has been assigend via setIp by an os manager) or because
|
||||
you get it for some other method.
|
||||
|
||||
Storage returns None if key is not stored.
|
||||
|
||||
:note: Keeping the IP address is responsibility of the User Deployment.
|
||||
Every time the core needs to provide the service to the user, or
|
||||
show the IP to the administrator, this method will get called
|
||||
|
||||
"""
|
||||
def getIp(self) -> str:
|
||||
return self._ip
|
||||
|
||||
def setReady(self):
|
||||
"""
|
||||
The method is invoked whenever a machine is provided to an user, right
|
||||
before presenting it (via transport rendering) to the user.
|
||||
"""
|
||||
def setReady(self) -> str:
|
||||
try:
|
||||
state = self.service().getVMPowerState(self._vmid)
|
||||
|
||||
if state != XenPowerState.running:
|
||||
self._queue = [opStart, opFinish]
|
||||
return self.__executeQueue()
|
||||
except:
|
||||
except Exception:
|
||||
return self.__error('Machine is not available anymore')
|
||||
|
||||
return State.FINISHED
|
||||
|
||||
def reset(self):
|
||||
if self._vmid != '':
|
||||
def reset(self) -> None:
|
||||
if self._vmid:
|
||||
self.service().resetVM(self._vmid) # Reset in sync
|
||||
|
||||
def notifyReadyFromOsManager(self, data):
|
||||
def notifyReadyFromOsManager(self, data: typing.Any) -> str:
|
||||
# Here we will check for suspending the VM (when full ready)
|
||||
logger.debug('Checking if cache 2 for {0}'.format(self._name))
|
||||
logger.debug('Checking if cache 2 for %s', self._name)
|
||||
if self.__getCurrentOp() == opWait:
|
||||
logger.debug('Machine is ready. Moving to level 2')
|
||||
self.__popCurrentOp() # Remove current state
|
||||
@ -212,7 +149,7 @@ class XenLinkedDeployment(UserDeployment):
|
||||
# Do not need to go to level 2 (opWait is in fact "waiting for moving machine to cache level 2)
|
||||
return State.FINISHED
|
||||
|
||||
def deployForUser(self, user):
|
||||
def deployForUser(self, user: 'models.User') -> str:
|
||||
"""
|
||||
Deploys an service instance for an user.
|
||||
"""
|
||||
@ -220,63 +157,56 @@ class XenLinkedDeployment(UserDeployment):
|
||||
self.__initQueueForDeploy(False)
|
||||
return self.__executeQueue()
|
||||
|
||||
def deployForCache(self, cacheLevel):
|
||||
def deployForCache(self, cacheLevel: int) -> str:
|
||||
"""
|
||||
Deploys an service instance for cache
|
||||
"""
|
||||
self.__initQueueForDeploy(cacheLevel == self.L2_CACHE)
|
||||
return self.__executeQueue()
|
||||
|
||||
def __initQueueForDeploy(self, forLevel2=False):
|
||||
|
||||
def __initQueueForDeploy(self, forLevel2: bool = False) -> None:
|
||||
if forLevel2 is False:
|
||||
self._queue = [opCreate, opConfigure, opProvision, opStart, opFinish]
|
||||
else:
|
||||
self._queue = [opCreate, opConfigure, opProvision, opStart, opWait, opWaitSuspend, opSuspend, opFinish]
|
||||
|
||||
def __getCurrentOp(self):
|
||||
def __getCurrentOp(self) -> int:
|
||||
if len(self._queue) == 0:
|
||||
return opFinish
|
||||
|
||||
return self._queue[0]
|
||||
|
||||
def __popCurrentOp(self):
|
||||
def __popCurrentOp(self) -> int:
|
||||
if len(self._queue) == 0:
|
||||
return opFinish
|
||||
|
||||
res = self._queue.pop(0)
|
||||
return res
|
||||
|
||||
def __pushFrontOp(self, op):
|
||||
def __pushFrontOp(self, op: int) -> None:
|
||||
self._queue.insert(0, op)
|
||||
|
||||
def __pushBackOp(self, op):
|
||||
def __pushBackOp(self, op: int) -> None:
|
||||
self._queue.append(op)
|
||||
|
||||
def __error(self, reason):
|
||||
"""
|
||||
Internal method to set object as error state
|
||||
|
||||
Returns:
|
||||
State.ERROR, so we can do "return self.__error(reason)"
|
||||
"""
|
||||
logger.debug('Setting error state, reason: {0}'.format(reason))
|
||||
def __error(self, reason: typing.Any) -> str:
|
||||
logger.debug('Setting error state, reason: %s', reason)
|
||||
self.doLog(log.ERROR, reason)
|
||||
|
||||
if self._vmid != '': # Powers off
|
||||
if self._vmid != '': # Powers off and delete VM
|
||||
try:
|
||||
state = self.service().getVMPowerState(self._vmid)
|
||||
if state in (XenPowerState.running, XenPowerState.paused, XenPowerState.suspended):
|
||||
self.service().stopVM(self._vmid, False) # In sync mode
|
||||
self.service().removeVM(self._vmid)
|
||||
except:
|
||||
logger.debug('Can\t set machine state to stopped')
|
||||
self.service().removeVM(self._vmid)
|
||||
except Exception:
|
||||
logger.debug('Can\'t set machine %s state to stopped', self._vmid)
|
||||
|
||||
self._queue = [opError]
|
||||
self._reason = str(reason)
|
||||
return State.ERROR
|
||||
|
||||
def __executeQueue(self):
|
||||
def __executeQueue(self) -> str:
|
||||
self.__debug('executeQueue')
|
||||
op = self.__getCurrentOp()
|
||||
|
||||
@ -286,7 +216,7 @@ class XenLinkedDeployment(UserDeployment):
|
||||
if op == opFinish:
|
||||
return State.FINISHED
|
||||
|
||||
fncs = {
|
||||
fncs: typing.Dict[int, typing.Optional[typing.Callable[[], str]]] = {
|
||||
opCreate: self.__create,
|
||||
opRetry: self.__retry,
|
||||
opStart: self.__startMachine,
|
||||
@ -300,7 +230,7 @@ class XenLinkedDeployment(UserDeployment):
|
||||
}
|
||||
|
||||
try:
|
||||
execFnc = fncs.get(op, None)
|
||||
execFnc: typing.Optional[typing.Callable[[], str]] = fncs.get(op, None)
|
||||
|
||||
if execFnc is None:
|
||||
return self.__error('Unknown operation found at execution queue ({0})'.format(op))
|
||||
@ -312,7 +242,7 @@ class XenLinkedDeployment(UserDeployment):
|
||||
return self.__error(e)
|
||||
|
||||
# Queue execution methods
|
||||
def __retry(self):
|
||||
def __retry(self) -> str:
|
||||
"""
|
||||
Used to retry an operation
|
||||
In fact, this will not be never invoked, unless we push it twice, because
|
||||
@ -322,13 +252,13 @@ class XenLinkedDeployment(UserDeployment):
|
||||
"""
|
||||
return State.FINISHED
|
||||
|
||||
def __wait(self):
|
||||
def __wait(self) -> str:
|
||||
"""
|
||||
Executes opWait, it simply waits something "external" to end
|
||||
"""
|
||||
return State.RUNNING
|
||||
|
||||
def __create(self):
|
||||
def __create(self) -> str:
|
||||
"""
|
||||
Deploys a machine from template for user/cache
|
||||
"""
|
||||
@ -344,7 +274,9 @@ class XenLinkedDeployment(UserDeployment):
|
||||
if self._task is None:
|
||||
raise Exception('Can\'t create machine')
|
||||
|
||||
def __remove(self):
|
||||
return State.RUNNING
|
||||
|
||||
def __remove(self) -> str:
|
||||
"""
|
||||
Removes a machine from system
|
||||
"""
|
||||
@ -356,7 +288,9 @@ class XenLinkedDeployment(UserDeployment):
|
||||
else:
|
||||
self.service().removeVM(self._vmid)
|
||||
|
||||
def __startMachine(self):
|
||||
return State.RUNNING
|
||||
|
||||
def __startMachine(self) -> str:
|
||||
"""
|
||||
Powers on the machine
|
||||
"""
|
||||
@ -367,7 +301,9 @@ class XenLinkedDeployment(UserDeployment):
|
||||
else:
|
||||
self._task = ''
|
||||
|
||||
def __stopMachine(self):
|
||||
return State.RUNNING
|
||||
|
||||
def __stopMachine(self) -> str:
|
||||
"""
|
||||
Powers off the machine
|
||||
"""
|
||||
@ -378,13 +314,16 @@ class XenLinkedDeployment(UserDeployment):
|
||||
else:
|
||||
self._task = ''
|
||||
|
||||
def __waitSuspend(self):
|
||||
return State.RUNNING
|
||||
|
||||
def __waitSuspend(self) -> str:
|
||||
"""
|
||||
Before suspending, wait for machine to have the SUSPEND feature
|
||||
"""
|
||||
self.task = ''
|
||||
self._task = ''
|
||||
return State.RUNNING
|
||||
|
||||
def __suspendMachine(self):
|
||||
def __suspendMachine(self) -> str:
|
||||
"""
|
||||
Suspends the machine
|
||||
"""
|
||||
@ -395,18 +334,24 @@ class XenLinkedDeployment(UserDeployment):
|
||||
else:
|
||||
self._task = ''
|
||||
|
||||
return State.RUNNING
|
||||
|
||||
def __configure(self):
|
||||
"""
|
||||
Provisions machine & changes the mac of the indicated nic
|
||||
"""
|
||||
self.service().configureVM(self._vmid, self.getUniqueId())
|
||||
|
||||
return State.RUNNING
|
||||
|
||||
def __provision(self):
|
||||
"""
|
||||
Makes machine usable on Xen
|
||||
"""
|
||||
self.service().provisionVM(self._vmid, False) # Let's try this in "sync" mode, this must be fast enough
|
||||
|
||||
return State.RUNNING
|
||||
|
||||
# Check methods
|
||||
def __checkCreate(self):
|
||||
"""
|
||||
@ -466,7 +411,7 @@ class XenLinkedDeployment(UserDeployment):
|
||||
def __checkProvision(self):
|
||||
return State.FINISHED
|
||||
|
||||
def checkState(self):
|
||||
def checkState(self) -> str:
|
||||
"""
|
||||
Check what operation is going on, and acts acordly to it
|
||||
"""
|
||||
@ -479,7 +424,7 @@ class XenLinkedDeployment(UserDeployment):
|
||||
if op == opFinish:
|
||||
return State.FINISHED
|
||||
|
||||
fncs = {
|
||||
fncs: typing.Dict[int, typing.Optional[typing.Callable[[], str]]] = {
|
||||
opCreate: self.__checkCreate,
|
||||
opRetry: self.__retry,
|
||||
opWait: self.__wait,
|
||||
@ -493,10 +438,10 @@ class XenLinkedDeployment(UserDeployment):
|
||||
}
|
||||
|
||||
try:
|
||||
chkFnc = fncs.get(op, None)
|
||||
chkFnc: typing.Optional[typing.Callable[[], str]] = fncs.get(op, None)
|
||||
|
||||
if chkFnc is None:
|
||||
return self.__error('Unknown operation found at check queue ({0})'.format(op))
|
||||
return self.__error('Unknown operation found at check queue ({})'.format(op))
|
||||
|
||||
state = chkFnc()
|
||||
if state == State.FINISHED:
|
||||
@ -507,15 +452,7 @@ class XenLinkedDeployment(UserDeployment):
|
||||
except Exception as e:
|
||||
return self.__error(e)
|
||||
|
||||
def finish(self):
|
||||
"""
|
||||
Invoked when the core notices that the deployment of a service has finished.
|
||||
(No matter wether it is for cache or for an user)
|
||||
"""
|
||||
self.__debug('finish')
|
||||
pass
|
||||
|
||||
def moveToCache(self, newLevel):
|
||||
def moveToCache(self, newLevel: int) -> str:
|
||||
"""
|
||||
Moves machines between cache levels
|
||||
"""
|
||||
@ -529,7 +466,7 @@ class XenLinkedDeployment(UserDeployment):
|
||||
|
||||
return self.__executeQueue()
|
||||
|
||||
def reasonOfError(self):
|
||||
def reasonOfError(self) -> str:
|
||||
"""
|
||||
Returns the reason of the error.
|
||||
|
||||
@ -539,7 +476,7 @@ class XenLinkedDeployment(UserDeployment):
|
||||
"""
|
||||
return self._reason
|
||||
|
||||
def destroy(self):
|
||||
def destroy(self) -> str:
|
||||
"""
|
||||
Invoked for destroying a deployed service
|
||||
"""
|
||||
@ -559,7 +496,7 @@ class XenLinkedDeployment(UserDeployment):
|
||||
# Do not execute anything.here, just continue normally
|
||||
return State.RUNNING
|
||||
|
||||
def cancel(self):
|
||||
def cancel(self) -> str:
|
||||
"""
|
||||
This is a task method. As that, the excepted return values are
|
||||
State values RUNNING, FINISHED or ERROR.
|
||||
@ -572,7 +509,7 @@ class XenLinkedDeployment(UserDeployment):
|
||||
return self.destroy()
|
||||
|
||||
@staticmethod
|
||||
def __op2str(op):
|
||||
def __op2str(op) -> str:
|
||||
return {
|
||||
opCreate: 'create',
|
||||
opStart: 'start',
|
||||
@ -588,9 +525,5 @@ class XenLinkedDeployment(UserDeployment):
|
||||
opProvision: 'provisioning'
|
||||
}.get(op, '????')
|
||||
|
||||
def __debug(self, txt):
|
||||
logger.debug('_name {0}: {1}'.format(txt, self._name))
|
||||
logger.debug('_ip {0}: {1}'.format(txt, self._ip))
|
||||
logger.debug('_mac {0}: {1}'.format(txt, self._mac))
|
||||
logger.debug('_vmid {0}: {1}'.format(txt, self._vmid))
|
||||
logger.debug('Queue at {0}: {1}'.format(txt, [XenLinkedDeployment.__op2str(op) for op in self._queue]))
|
||||
def __debug(self, txt: str) -> None:
|
||||
logger.debug('State at %s: name: %s, ip: %s, mac: %s, vmid:%s, queue: %s', txt, self._name, self._ip, self._mac, self._vmid, [XenLinkedDeployment.__op2str(op) for op in self._queue])
|
||||
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -25,16 +25,13 @@
|
||||
# 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 Apr 8, 2014
|
||||
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from django.utils.translation import ugettext_noop as _
|
||||
from uds.core.util.state import State
|
||||
from uds.core.services import ServiceProvider
|
||||
from uds.core.ui import gui
|
||||
# from uds.core.util import validators
|
||||
@ -44,17 +41,16 @@ from .xen_client import XenServer
|
||||
|
||||
from .service import XenLinkedService
|
||||
|
||||
import six
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
__updated__ = '2018-09-21'
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds.core import Module
|
||||
from uds.core.environment import Environment
|
||||
|
||||
CACHE_TIME_FOR_SERVER = 1800
|
||||
|
||||
|
||||
class Provider(ServiceProvider):
|
||||
class XenProvider(ServiceProvider): # pylint: disable=too-many-public-methods
|
||||
"""
|
||||
This class represents the sample services provider
|
||||
|
||||
@ -107,21 +103,23 @@ class Provider(ServiceProvider):
|
||||
verifySSL = gui.CheckBoxField(label=_('Verify Certificate'), order=91,
|
||||
tooltip=_('If selected, certificate will be checked against system valid certificate providers'), required=True, tab=gui.ADVANCED_TAB)
|
||||
|
||||
_api: typing.Optional[XenServer]
|
||||
|
||||
# XenServer engine, right now, only permits a connection to one server and only one per instance
|
||||
# If we want to connect to more than one server, we need keep locked access to api, change api server, etc..
|
||||
# We have implemented an "exclusive access" client that will only connect to one server at a time (using locks)
|
||||
# and this way all will be fine
|
||||
def __getApi(self, force=False):
|
||||
def __getApi(self, force: bool = False) -> XenServer:
|
||||
"""
|
||||
Returns the connection API object for XenServer (using XenServersdk)
|
||||
"""
|
||||
logger.debug('API verifySSL: {} {}'.format(self.verifySSL.value, self.verifySSL.isTrue()))
|
||||
if self._api is None or force:
|
||||
self._api = XenServer(self.host.value, '443', self.username.value, self.password.value, True, self.verifySSL.isTrue())
|
||||
if not self._api or force:
|
||||
self._api = XenServer(self.host.value, 443, self.username.value, self.password.value, True, self.verifySSL.isTrue())
|
||||
|
||||
return self._api
|
||||
|
||||
# There is more fields type, but not here the best place to cover it
|
||||
def initialize(self, values=None):
|
||||
def initialize(self, values: 'Module.ValuesType') -> None:
|
||||
"""
|
||||
We will use the "autosave" feature for form fields
|
||||
"""
|
||||
@ -139,26 +137,27 @@ class Provider(ServiceProvider):
|
||||
"""
|
||||
self.__getApi().test()
|
||||
|
||||
def checkTaskFinished(self, task):
|
||||
def checkTaskFinished(self, task: typing.Optional[str]) -> typing.Tuple[bool, str]:
|
||||
"""
|
||||
Checks a task state.
|
||||
Returns None if task is Finished
|
||||
Returns a number indicating % of completion if running
|
||||
Raises an exception with status else ('cancelled', 'unknown', 'failure')
|
||||
"""
|
||||
if task is None or task == '':
|
||||
if not task:
|
||||
return True, ''
|
||||
|
||||
ts = self.__getApi().getTaskInfo(task)
|
||||
logger.debug('Task status: {0}'.format(ts))
|
||||
logger.debug('Task status: %s', ts)
|
||||
if ts['status'] == 'running':
|
||||
return False, ts['progress']
|
||||
if ts['status'] == 'success':
|
||||
return True, ts['result']
|
||||
|
||||
# Any other state, raises an exception
|
||||
raise Exception(six.text_type(ts['result'])) # Should be error message
|
||||
raise Exception(str(ts['result'])) # Should be error message
|
||||
|
||||
def getMachines(self, force=False):
|
||||
def getMachines(self, force: bool = False) -> typing.Iterable[typing.MutableMapping[str, typing.Any]]:
|
||||
"""
|
||||
Obtains the list of machines inside XenServer.
|
||||
Machines starting with UDS are filtered out
|
||||
@ -179,7 +178,7 @@ class Provider(ServiceProvider):
|
||||
continue
|
||||
yield m
|
||||
|
||||
def getStorages(self, force=False):
|
||||
def getStorages(self, force: bool = False) -> typing.Iterable[typing.MutableMapping[str, typing.Any]]:
|
||||
"""
|
||||
Obtains the list of storages inside XenServer.
|
||||
|
||||
@ -196,7 +195,7 @@ class Provider(ServiceProvider):
|
||||
"""
|
||||
return self.__getApi().getSRs()
|
||||
|
||||
def getStorageInfo(self, storageId, force=False):
|
||||
def getStorageInfo(self, storageId: str, force=False) -> typing.MutableMapping[str, typing.Any]:
|
||||
"""
|
||||
Obtains the storage info
|
||||
|
||||
@ -218,15 +217,15 @@ class Provider(ServiceProvider):
|
||||
"""
|
||||
return self.__getApi().getSRInfo(storageId)
|
||||
|
||||
def getNetworks(self, force=False):
|
||||
def getNetworks(self, force: bool = False) -> typing.Iterable[typing.MutableMapping[str, typing.Any]]:
|
||||
return self.__getApi().getNetworks()
|
||||
|
||||
def cloneForTemplate(self, name, comments, machineId, sr):
|
||||
def cloneForTemplate(self, name: str, comments: str, machineId: str, sr: str):
|
||||
task = self.__getApi().cloneVM(machineId, name, sr)
|
||||
logger.debug('Task for cloneForTemplate: {0}'.format(task))
|
||||
logger.debug('Task for cloneForTemplate: %s', task)
|
||||
return task
|
||||
|
||||
def convertToTemplate(self, machineId, shadowMultiplier=4):
|
||||
def convertToTemplate(self, machineId: str, shadowMultiplier: int = 4) -> None:
|
||||
"""
|
||||
Publish the machine (makes a template from it so we can create COWs) and returns the template id of
|
||||
the creating machine
|
||||
@ -243,33 +242,15 @@ class Provider(ServiceProvider):
|
||||
"""
|
||||
self.__getApi().convertToTemplate(machineId, shadowMultiplier)
|
||||
|
||||
def getMachineState(self, machineId):
|
||||
"""
|
||||
Returns the state of the machine
|
||||
This method do not uses cache at all (it always tries to get machine state from XenServer server)
|
||||
|
||||
Args:
|
||||
machineId: Id 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.__getApi().getMachineState(machineId)
|
||||
|
||||
def removeTemplate(self, templateId):
|
||||
def removeTemplate(self, templateId: str) -> None:
|
||||
"""
|
||||
Removes a template from XenServer server
|
||||
|
||||
Returns nothing, and raises an Exception if it fails
|
||||
"""
|
||||
return self.__getApi().removeTemplate(templateId)
|
||||
self.__getApi().removeTemplate(templateId)
|
||||
|
||||
def startDeployFromTemplate(self, name, comments, templateId):
|
||||
def startDeployFromTemplate(self, name: str, comments: str, templateId: str) -> str:
|
||||
"""
|
||||
Deploys a virtual machine on selected cluster from selected template
|
||||
|
||||
@ -287,13 +268,13 @@ class Provider(ServiceProvider):
|
||||
"""
|
||||
return self.__getApi().cloneTemplate(templateId, name)
|
||||
|
||||
def getVMPowerState(self, machineId):
|
||||
def getVMPowerState(self, machineId: str) -> str:
|
||||
"""
|
||||
Returns current machine power state
|
||||
"""
|
||||
return self.__getApi().getVMPowerState(machineId)
|
||||
|
||||
def startVM(self, machineId, asnc=True):
|
||||
def startVM(self, machineId: str, asnc: bool = True) -> typing.Optional[str]:
|
||||
"""
|
||||
Tries to start a machine. No check is done, it is simply requested to XenServer.
|
||||
|
||||
@ -306,7 +287,7 @@ class Provider(ServiceProvider):
|
||||
"""
|
||||
return self.__getApi().startVM(machineId, asnc)
|
||||
|
||||
def stopVM(self, machineId, asnc=True):
|
||||
def stopVM(self, machineId: str, asnc: bool = True) -> typing.Optional[str]:
|
||||
"""
|
||||
Tries to start a machine. No check is done, it is simply requested to XenServer
|
||||
|
||||
@ -317,7 +298,7 @@ class Provider(ServiceProvider):
|
||||
"""
|
||||
return self.__getApi().stopVM(machineId, asnc)
|
||||
|
||||
def resetVM(self, machineId, asnc=True):
|
||||
def resetVM(self, machineId: str, asnc: bool = True) -> typing.Optional[str]:
|
||||
"""
|
||||
Tries to start a machine. No check is done, it is simply requested to XenServer
|
||||
|
||||
@ -328,7 +309,7 @@ class Provider(ServiceProvider):
|
||||
"""
|
||||
return self.__getApi().resetVM(machineId, asnc)
|
||||
|
||||
def canSuspendVM(self, machineId):
|
||||
def canSuspendVM(self, machineId: str) -> bool:
|
||||
"""
|
||||
The machine can be suspended only when "suspend" is in their operations list (mush have xentools installed)
|
||||
|
||||
@ -340,7 +321,7 @@ class Provider(ServiceProvider):
|
||||
"""
|
||||
return self.__getApi().canSuspendVM(machineId)
|
||||
|
||||
def suspendVM(self, machineId, asnc=True):
|
||||
def suspendVM(self, machineId: str, asnc: bool = True) -> typing.Optional[str]:
|
||||
"""
|
||||
Tries to start a machine. No check is done, it is simply requested to XenServer
|
||||
|
||||
@ -351,7 +332,7 @@ class Provider(ServiceProvider):
|
||||
"""
|
||||
return self.__getApi().suspendVM(machineId, asnc)
|
||||
|
||||
def resumeVM(self, machineId, asnc=True):
|
||||
def resumeVM(self, machineId: str, asnc: bool = True) -> typing.Optional[str]:
|
||||
"""
|
||||
Tries to start a machine. No check is done, it is simply requested to XenServer
|
||||
|
||||
@ -362,7 +343,7 @@ class Provider(ServiceProvider):
|
||||
"""
|
||||
return self.__getApi().resumeVM(machineId, asnc)
|
||||
|
||||
def removeVM(self, machineId):
|
||||
def removeVM(self, machineId: str) -> None:
|
||||
"""
|
||||
Tries to delete a machine. No check is done, it is simply requested to XenServer
|
||||
|
||||
@ -371,19 +352,19 @@ class Provider(ServiceProvider):
|
||||
|
||||
Returns:
|
||||
"""
|
||||
return self.__getApi().removeVM(machineId)
|
||||
self.__getApi().removeVM(machineId)
|
||||
|
||||
def configureVM(self, machineId, netId, mac, memory):
|
||||
def configureVM(self, machineId: str, netId: str, mac: str, memory: int) -> None:
|
||||
self.__getApi().configureVM(machineId, mac={'network': netId, 'mac': mac}, memory=memory)
|
||||
|
||||
def provisionVM(self, machineId, asnc):
|
||||
def provisionVM(self, machineId: str, asnc: bool = True) -> str:
|
||||
return self.__getApi().provisionVM(machineId, asnc=asnc)
|
||||
|
||||
def getMacRange(self):
|
||||
def getMacRange(self) -> str:
|
||||
return self.macsRange.value
|
||||
|
||||
@staticmethod
|
||||
def test(env, data):
|
||||
def test(env: 'Environment', data: 'Module.ValuesType') -> typing.List[typing.Any]:
|
||||
"""
|
||||
Test XenServer Connectivity
|
||||
|
||||
@ -411,9 +392,9 @@ class Provider(ServiceProvider):
|
||||
# logger.exception("Exception caugth!!!")
|
||||
# return [False, str(e)]
|
||||
# return [True, _('Nothing tested, but all went fine..')]
|
||||
xe = Provider(env, data)
|
||||
xe = XenProvider(env, data)
|
||||
try:
|
||||
xe.testConnection()
|
||||
return [True, _('Connection test successful')]
|
||||
except Exception as e:
|
||||
return [False, _("Connection failed: {0}").format(six.text_type(e))]
|
||||
return [False, _("Connection failed: {}").format(str(e))]
|
||||
|
@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -30,53 +29,41 @@
|
||||
"""
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from uds.core.services import Publication
|
||||
from uds.core.util.state import State
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from .service import XenLinkedService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class XenPublication(Publication):
|
||||
"""
|
||||
This class provides the publication of a oVirtLinkedService
|
||||
"""
|
||||
_name = ''
|
||||
_reason = ''
|
||||
_destroyAfter = 'f'
|
||||
_templateId = ''
|
||||
_state = ''
|
||||
_task = ''
|
||||
|
||||
suggestedTime = 20 # : Suggested recheck time if publication is unfinished in seconds
|
||||
|
||||
def initialize(self):
|
||||
"""
|
||||
This method will be invoked by default __init__ of base class, so it gives
|
||||
us the oportunity to initialize whataver we need here.
|
||||
_name: str = ''
|
||||
_reason: str = ''
|
||||
_destroyAfter: str = 'f'
|
||||
_templateId: str = ''
|
||||
_state: str = ''
|
||||
_task: str = ''
|
||||
|
||||
In our case, we setup a few attributes..
|
||||
"""
|
||||
def service(self) -> 'XenLinkedService':
|
||||
return typing.cast('XenLinkedService', super().service())
|
||||
|
||||
# We do not check anything at marshal method, so we ensure that
|
||||
# default values are correctly handled by marshal.
|
||||
self._name = ''
|
||||
self._reason = ''
|
||||
self._destroyAfter = 'f'
|
||||
self._templateId = ''
|
||||
self._state = ''
|
||||
self._task = ''
|
||||
|
||||
def marshal(self):
|
||||
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, self._task]).encode('utf8')
|
||||
|
||||
def unmarshal(self, data):
|
||||
def unmarshal(self, data: bytes) -> None:
|
||||
"""
|
||||
deserializes the data and loads it inside instance.
|
||||
"""
|
||||
@ -85,7 +72,7 @@ class XenPublication(Publication):
|
||||
if vals[0] == 'v1':
|
||||
self._name, self._reason, self._destroyAfter, self._templateId, self._state, self._task = vals[1:]
|
||||
|
||||
def publish(self):
|
||||
def publish(self) -> str:
|
||||
"""
|
||||
Realizes the publication of the service
|
||||
"""
|
||||
@ -104,7 +91,7 @@ class XenPublication(Publication):
|
||||
|
||||
return State.RUNNING
|
||||
|
||||
def checkState(self):
|
||||
def checkState(self) -> str:
|
||||
"""
|
||||
Checks state of publication creation
|
||||
"""
|
||||
@ -115,10 +102,10 @@ class XenPublication(Publication):
|
||||
return State.ERROR
|
||||
|
||||
try:
|
||||
state = self.service().checkTaskFinished(self._task)
|
||||
if state[0]: # Finished
|
||||
state, result = self.service().checkTaskFinished(self._task)
|
||||
if state: # Finished
|
||||
self._state = 'finished'
|
||||
self._templateId = state[1]
|
||||
self._templateId = result
|
||||
if self._destroyAfter == 't':
|
||||
return self.destroy()
|
||||
|
||||
@ -131,33 +118,10 @@ class XenPublication(Publication):
|
||||
|
||||
return State.RUNNING
|
||||
|
||||
def finish(self):
|
||||
"""
|
||||
In our case, finish does nothing
|
||||
"""
|
||||
pass
|
||||
|
||||
def reasonOfError(self):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
def reasonOfError(self) -> str:
|
||||
return self._reason
|
||||
|
||||
def destroy(self):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
def destroy(self) -> str:
|
||||
# We do not do anything else to destroy this instance of publication
|
||||
if self._state == 'ok':
|
||||
self._destroyAfter = 't'
|
||||
@ -172,17 +136,14 @@ class XenPublication(Publication):
|
||||
|
||||
return State.FINISHED
|
||||
|
||||
def cancel(self):
|
||||
"""
|
||||
Do same thing as destroy
|
||||
"""
|
||||
def cancel(self) -> str:
|
||||
return self.destroy()
|
||||
|
||||
# Here ends the publication needed methods.
|
||||
# Methods provided below are specific for this publication
|
||||
# Methods provided below are specific for this publication type
|
||||
# and will be used by user deployments that uses this kind of publication
|
||||
|
||||
def getTemplateId(self):
|
||||
def getTemplateId(self) -> str:
|
||||
"""
|
||||
Returns the template id associated with the publication
|
||||
"""
|
||||
|
@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -30,8 +29,10 @@
|
||||
"""
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from django.utils.translation import ugettext_noop as _, ugettext
|
||||
from django.utils.translation import ugettext_noop as _
|
||||
from uds.core.services import Service, types as serviceTypes
|
||||
from uds.core.util import tools
|
||||
from uds.core.ui import gui
|
||||
@ -39,12 +40,15 @@ from uds.core.ui import gui
|
||||
from .publication import XenPublication
|
||||
from .deployment import XenLinkedDeployment
|
||||
|
||||
import logging
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from .provider import XenProvider
|
||||
from uds.core import Module
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class XenLinkedService(Service):
|
||||
class XenLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
"""
|
||||
Xen Linked clones service. This is based on creating a template from selected vm, and then use it to
|
||||
|
||||
@ -190,56 +194,45 @@ class XenLinkedService(Service):
|
||||
raise Service.ValidationException(
|
||||
_('The minimum allowed memory is 256 Mb'))
|
||||
|
||||
def initGui(self):
|
||||
"""
|
||||
Loads required values inside
|
||||
"""
|
||||
def parent(self) -> 'XenProvider':
|
||||
return typing.cast('XenProvider', super().parent())
|
||||
|
||||
def initGui(self) -> None:
|
||||
# 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
|
||||
|
||||
machines = self.parent().getMachines()
|
||||
storages = self.parent().getStorages()
|
||||
networks = self.parent().getNetworks()
|
||||
machines_list = []
|
||||
for m in machines:
|
||||
machines_list.append(gui.choiceItem(m['id'], m['name']))
|
||||
machines_list = [gui.choiceItem(m['id'], m['name']) for m in self.parent().getMachines()]
|
||||
|
||||
storages_list = []
|
||||
for storage in storages:
|
||||
space, free = storage['size'] / \
|
||||
1024, (storage['size'] - storage['used']) / 1024
|
||||
storages_list.append(gui.choiceItem(
|
||||
storage['id'], "%s (%4.2f Gb/%4.2f Gb)" % (storage['name'], space, free)))
|
||||
network_list = []
|
||||
for net in networks:
|
||||
network_list.append(gui.choiceItem(net['id'], net['name']))
|
||||
for storage in self.parent().getStorages():
|
||||
space, free = storage['size'] / 1024, (storage['size'] - storage['used']) / 1024
|
||||
storages_list.append(gui.choiceItem(storage['id'], "%s (%4.2f Gb/%4.2f Gb)" % (storage['name'], space, free)))
|
||||
|
||||
network_list = [gui.choiceItem(net['id'], net['name']) for net in self.parent().getNetworks()]
|
||||
|
||||
self.machine.setValues(machines_list)
|
||||
self.datastore.setValues(storages_list)
|
||||
self.network.setValues(network_list)
|
||||
|
||||
def checkTaskFinished(self, task):
|
||||
def checkTaskFinished(self, task: str) -> typing.Tuple[bool, str]:
|
||||
return self.parent().checkTaskFinished(task)
|
||||
|
||||
def datastoreHasSpace(self):
|
||||
def datastoreHasSpace(self) -> None:
|
||||
# Get storages for that datacenter
|
||||
logger.debug('Checking datastore space for {0}'.format(
|
||||
self.datastore.value))
|
||||
info = self.parent().getStorageInfo(self.datastore.value)
|
||||
logger.debug('Datastore Info: {0}'.format(info))
|
||||
logger.debug('Checking datastore space for %s: %s', self.datastore.value, info)
|
||||
availableGB = (info['size'] - info['used']) / 1024
|
||||
if availableGB < self.minSpaceGB.num():
|
||||
raise Exception('Not enough free space available: (Needs at least {0} GB and there is only {1} GB '.format(
|
||||
self.minSpaceGB.num(), availableGB))
|
||||
raise Exception('Not enough free space available: (Needs at least {} GB and there is only {} GB '.format(self.minSpaceGB.num(), availableGB))
|
||||
|
||||
def sanitizeVmName(self, name):
|
||||
def sanitizeVmName(self, name: str) -> str:
|
||||
"""
|
||||
Xen Seems to allow all kind of names
|
||||
"""
|
||||
return name
|
||||
|
||||
def startDeployTemplate(self, name, comments):
|
||||
def startDeployTemplate(self, name: str, comments: str) -> str:
|
||||
"""
|
||||
Invokes makeTemplate from parent provider, completing params
|
||||
|
||||
@ -253,20 +246,20 @@ class XenLinkedService(Service):
|
||||
Raises an exception if operation fails.
|
||||
"""
|
||||
|
||||
logger.debug('Starting deploy of template from machine {0} on datastore {1}'.format(
|
||||
self.machine.value, self.datastore.value))
|
||||
logger.debug('Starting deploy of template from machine %s on datastore %s', self.machine.value, self.datastore.value)
|
||||
|
||||
# Checks datastore size
|
||||
# Checks datastore available space, raises exeception in no min available
|
||||
self.datastoreHasSpace()
|
||||
|
||||
return self.parent().cloneForTemplate(name, comments, self.machine.value, self.datastore.value)
|
||||
|
||||
def convertToTemplate(self, machineId):
|
||||
def convertToTemplate(self, machineId: str) -> None:
|
||||
"""
|
||||
converts machine to template
|
||||
"""
|
||||
self.parent().convertToTemplate(machineId, self.shadow.value)
|
||||
|
||||
def startDeployFromTemplate(self, name, comments, templateId):
|
||||
def startDeployFromTemplate(self, name: str, comments: str, templateId: str) -> str:
|
||||
"""
|
||||
Deploys a virtual machine on selected cluster from selected template
|
||||
|
||||
@ -281,19 +274,18 @@ class XenLinkedService(Service):
|
||||
Returns:
|
||||
Id of the machine being created form template
|
||||
"""
|
||||
logger.debug(
|
||||
'Deploying from template {0} machine {1}'.format(templateId, name))
|
||||
logger.debug('Deploying from template %s machine %s', templateId, name)
|
||||
self.datastoreHasSpace()
|
||||
|
||||
return self.parent().startDeployFromTemplate(name, comments, templateId)
|
||||
|
||||
def removeTemplate(self, templateId):
|
||||
def removeTemplate(self, templateId: str) -> None:
|
||||
"""
|
||||
invokes removeTemplate from parent provider
|
||||
"""
|
||||
return self.parent().removeTemplate(templateId)
|
||||
self.parent().removeTemplate(templateId)
|
||||
|
||||
def getVMPowerState(self, machineId):
|
||||
def getVMPowerState(self, machineId: str) -> str:
|
||||
"""
|
||||
Invokes getMachineState from parent provider
|
||||
|
||||
@ -305,7 +297,7 @@ class XenLinkedService(Service):
|
||||
"""
|
||||
return self.parent().getVMPowerState(machineId)
|
||||
|
||||
def startVM(self, machineId, asnc=True):
|
||||
def startVM(self, machineId: str, asnc: bool = True) -> typing.Optional[str]:
|
||||
"""
|
||||
Tries to start a machine. No check is done, it is simply requested to Xen.
|
||||
|
||||
@ -318,7 +310,7 @@ class XenLinkedService(Service):
|
||||
"""
|
||||
return self.parent().startVM(machineId, asnc)
|
||||
|
||||
def stopVM(self, machineId, asnc=True):
|
||||
def stopVM(self, machineId: str, asnc: bool = True) -> typing.Optional[str]:
|
||||
"""
|
||||
Tries to stop a machine. No check is done, it is simply requested to Xen
|
||||
|
||||
@ -329,7 +321,7 @@ class XenLinkedService(Service):
|
||||
"""
|
||||
return self.parent().stopVM(machineId, asnc)
|
||||
|
||||
def resetVM(self, machineId, asnc=True):
|
||||
def resetVM(self, machineId: str, asnc: bool = True) -> typing.Optional[str]:
|
||||
"""
|
||||
Tries to stop a machine. No check is done, it is simply requested to Xen
|
||||
|
||||
@ -340,7 +332,7 @@ class XenLinkedService(Service):
|
||||
"""
|
||||
return self.parent().resetVM(machineId, asnc)
|
||||
|
||||
def canSuspendVM(self, machineId):
|
||||
def canSuspendVM(self, machineId: str) -> bool:
|
||||
"""
|
||||
The machine can be suspended only when "suspend" is in their operations list (mush have xentools installed)
|
||||
|
||||
@ -352,7 +344,7 @@ class XenLinkedService(Service):
|
||||
"""
|
||||
return self.parent().canSuspendVM(machineId)
|
||||
|
||||
def suspendVM(self, machineId, asnc=True):
|
||||
def suspendVM(self, machineId: str, asnc: bool = True) -> typing.Optional[str]:
|
||||
"""
|
||||
Tries to suspend a machine. No check is done, it is simply requested to Xen
|
||||
|
||||
@ -363,7 +355,7 @@ class XenLinkedService(Service):
|
||||
"""
|
||||
return self.parent().suspendVM(machineId, asnc)
|
||||
|
||||
def resumeVM(self, machineId, asnc=True):
|
||||
def resumeVM(self, machineId: str, asnc: bool = True) -> typing.Optional[str]:
|
||||
"""
|
||||
Tries to resume a machine. No check is done, it is simply requested to Xen
|
||||
|
||||
@ -374,7 +366,7 @@ class XenLinkedService(Service):
|
||||
"""
|
||||
return self.parent().suspendVM(machineId, asnc)
|
||||
|
||||
def removeVM(self, machineId):
|
||||
def removeVM(self, machineId: str) -> None:
|
||||
"""
|
||||
Tries to delete a machine. No check is done, it is simply requested to Xen
|
||||
|
||||
@ -383,34 +375,28 @@ class XenLinkedService(Service):
|
||||
|
||||
Returns:
|
||||
"""
|
||||
return self.parent().removeVM(machineId)
|
||||
self.parent().removeVM(machineId)
|
||||
|
||||
def configureVM(self, machineId, mac):
|
||||
return self.parent().configureVM(machineId, self.network.value, mac, self.memory.value)
|
||||
def configureVM(self, machineId: str, mac: str) -> None:
|
||||
self.parent().configureVM(machineId, self.network.value, mac, self.memory.value)
|
||||
|
||||
def provisionVM(self, machineId, asnc=True):
|
||||
def provisionVM(self, machineId: str, asnc: bool = True) -> str:
|
||||
return self.parent().provisionVM(machineId, asnc)
|
||||
|
||||
def getMacRange(self):
|
||||
def getMacRange(self) -> str:
|
||||
"""
|
||||
Returns de selected mac range
|
||||
"""
|
||||
return self.parent().getMacRange()
|
||||
|
||||
def getBaseName(self):
|
||||
def getBaseName(self) -> str:
|
||||
"""
|
||||
Returns the base name
|
||||
"""
|
||||
return self.baseName.value
|
||||
|
||||
def getLenName(self):
|
||||
def getLenName(self) -> int:
|
||||
"""
|
||||
Returns the length of numbers part
|
||||
"""
|
||||
return int(self.lenName.value)
|
||||
|
||||
def getDisplay(self):
|
||||
"""
|
||||
Returns the selected display type (for created machines, for administration
|
||||
"""
|
||||
return self.display.value
|
||||
|
@ -54,19 +54,25 @@
|
||||
# OF THIS SOFTWARE.
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# Fixes and adaption by agomez@virtualcable.net
|
||||
|
||||
import ssl
|
||||
import gettext
|
||||
import six.moves.xmlrpc_client as xmlrpclib
|
||||
import six.moves.http_client as httplib
|
||||
import xmlrpc.client as xmlrpclib
|
||||
import http.client as httplib
|
||||
import socket
|
||||
import sys
|
||||
import typing
|
||||
|
||||
translation = gettext.translation('xen-xm', fallback = True)
|
||||
translation = gettext.translation('xen-xm', fallback=True)
|
||||
|
||||
API_VERSION_1_1 = '1.1'
|
||||
API_VERSION_1_2 = '1.2'
|
||||
|
||||
class Failure(Exception):
|
||||
def __init__(self, details):
|
||||
details: typing.List[typing.Any]
|
||||
def __init__(self, details: typing.List[typing.Any]):
|
||||
super().__init__()
|
||||
self.details = details
|
||||
|
||||
def __str__(self):
|
||||
@ -78,9 +84,8 @@ class Failure(Exception):
|
||||
return msg
|
||||
|
||||
def _details_map(self):
|
||||
return dict([(str(i), self.details[i])
|
||||
for i in range(len(self.details))])
|
||||
|
||||
# dict([(str(i), self.details[i]) for i in range(len(self.details))])
|
||||
return {str(i): d for i, d in enumerate(self.details)}
|
||||
|
||||
# Just a "constant" that we use to decide whether to retry the RPC
|
||||
_RECONNECT_AND_RETRY = object()
|
||||
@ -96,19 +101,23 @@ class UDSHTTP(httplib.HTTPConnection):
|
||||
_connection_class = UDSHTTPConnection
|
||||
|
||||
class UDSTransport(xmlrpclib.Transport):
|
||||
def __init__(self, use_datetime=0):
|
||||
_use_datetime: bool
|
||||
_extra_headers: typing.List[typing.Tuple[str, str]]
|
||||
_connection: typing.Tuple[typing.Any, typing.Any]
|
||||
|
||||
def __init__(self, use_datetime: bool = False):
|
||||
super().__init__()
|
||||
self._use_datetime = use_datetime
|
||||
self._extra_headers=[]
|
||||
self._extra_headers = []
|
||||
self._connection = (None, None)
|
||||
def add_extra_header(self, key, value):
|
||||
self._extra_headers += [ (key,value) ]
|
||||
def make_connection(self, host):
|
||||
# Python 2.4 compatibility
|
||||
if sys.version_info[0] <= 2 and sys.version_info[1] < 7:
|
||||
return UDSHTTP(host)
|
||||
else:
|
||||
return UDSHTTPConnection(host)
|
||||
def send_request(self, connection, handler, request_body):
|
||||
|
||||
def add_extra_header(self, key: str, value: str) -> None:
|
||||
self._extra_headers += [(key, value)]
|
||||
|
||||
def make_connection(self, host: str) -> httplib.HTTPConnection:
|
||||
return UDSHTTPConnection(host)
|
||||
|
||||
def send_request(self, connection, handler, request_body, debug): # pylint: disable=arguments-differ
|
||||
connection.putrequest("POST", handler)
|
||||
for key, value in self._extra_headers:
|
||||
connection.putheader(key, value)
|
||||
@ -130,9 +139,7 @@ class Session(xmlrpclib.ServerProxy):
|
||||
|
||||
# Fix for CA-172901 (+ Python 2.4 compatibility)
|
||||
# Fix for context=ctx ( < Python 2.7.9 compatibility)
|
||||
if not (sys.version_info[0] <= 2 and sys.version_info[1] <= 7 and sys.version_info[2] <= 9 ) \
|
||||
and ignore_ssl:
|
||||
import ssl
|
||||
if not (sys.version_info[0] <= 2 and sys.version_info[1] <= 7 and sys.version_info[2] <= 9) and ignore_ssl:
|
||||
ctx = ssl._create_unverified_context()
|
||||
xmlrpclib.ServerProxy.__init__(self, uri, transport, encoding,
|
||||
verbose, allow_none, context=ctx)
|
||||
@ -204,7 +211,7 @@ class Session(xmlrpclib.ServerProxy):
|
||||
host = self.xenapi.pool.get_master(pool)
|
||||
major = self.xenapi.host.get_API_version_major(host)
|
||||
minor = self.xenapi.host.get_API_version_minor(host)
|
||||
return "%s.%s"%(major,minor)
|
||||
return "%s.%s"%(major, minor)
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name == 'handle':
|
||||
@ -222,23 +229,18 @@ def xapi_local():
|
||||
return Session("http://_var_lib_xcp_xapi/", transport=UDSTransport())
|
||||
|
||||
def _parse_result(result):
|
||||
if type(result) != dict or 'Status' not in result:
|
||||
if not isinstance(result, dict) or 'Status' not in result:
|
||||
raise xmlrpclib.Fault(500, 'Missing Status in response from server' + result)
|
||||
if result['Status'] == 'Success':
|
||||
if 'Value' in result:
|
||||
return result['Value']
|
||||
else:
|
||||
raise xmlrpclib.Fault(500,
|
||||
'Missing Value in response from server')
|
||||
else:
|
||||
if 'ErrorDescription' in result:
|
||||
if result['ErrorDescription'][0] == 'SESSION_INVALID':
|
||||
return _RECONNECT_AND_RETRY
|
||||
else:
|
||||
raise Failure(result['ErrorDescription'])
|
||||
else:
|
||||
raise xmlrpclib.Fault(
|
||||
500, 'Missing ErrorDescription in response from server')
|
||||
raise xmlrpclib.Fault(500, 'Missing Value in response from server')
|
||||
|
||||
if 'ErrorDescription' in result:
|
||||
if result['ErrorDescription'][0] == 'SESSION_INVALID':
|
||||
return _RECONNECT_AND_RETRY
|
||||
raise Failure(result['ErrorDescription'])
|
||||
raise xmlrpclib.Fault(500, 'Missing ErrorDescription in response from server')
|
||||
|
||||
|
||||
# Based upon _Method from xmlrpclib.
|
||||
@ -251,8 +253,7 @@ class _Dispatcher:
|
||||
def __repr__(self):
|
||||
if self.__name:
|
||||
return '<XenAPI._Dispatcher for %s>' % self.__name
|
||||
else:
|
||||
return '<XenAPI._Dispatcher>'
|
||||
return '<XenAPI._Dispatcher>'
|
||||
|
||||
def __getattr__(self, name):
|
||||
if self.__name is None:
|
||||
|
@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2014 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -26,14 +25,13 @@
|
||||
# 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.
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import xmlrpc.client
|
||||
from . import XenAPI
|
||||
|
||||
import ssl
|
||||
|
||||
import enum
|
||||
import xmlrpc.client
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from . import XenAPI
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -51,23 +49,23 @@ class XenFailure(XenAPI.Failure, XenFault):
|
||||
exHostIsSlave = 'HOST_IS_SLAVE'
|
||||
exSRError = 'SR_BACKEND_FAILURE_44'
|
||||
|
||||
def __init__(self, details=None):
|
||||
def __init__(self, details: typing.Optional[typing.List] = None):
|
||||
details = [] if details is None else details
|
||||
super(XenFailure, self).__init__(details)
|
||||
|
||||
def isHandleInvalid(self):
|
||||
def isHandleInvalid(self) -> bool:
|
||||
return self.details[0] == XenFailure.exHandleInvalid
|
||||
|
||||
def needsXenTools(self):
|
||||
def needsXenTools(self) -> bool:
|
||||
return self.details[0] == XenFailure.exVmMissingPVDrivers
|
||||
|
||||
def badPowerState(self):
|
||||
def badPowerState(self) -> bool:
|
||||
return self.details[0] == XenFailure.exBadVmPowerState
|
||||
|
||||
def isSlave(self):
|
||||
def isSlave(self) -> bool:
|
||||
return self.details[0] == XenFailure.exHostIsSlave
|
||||
|
||||
def asHumanReadable(self):
|
||||
def asHumanReadable(self) -> str:
|
||||
try:
|
||||
errList = {
|
||||
XenFailure.exBadVmPowerState: 'Machine state is invalid for requested operation (needs {2} and state is {3})',
|
||||
@ -82,33 +80,45 @@ class XenFailure(XenAPI.Failure, XenFault):
|
||||
except Exception:
|
||||
return 'Unknown exception: {0}'.format(self.details)
|
||||
|
||||
def __unicode__(self):
|
||||
def __str__(self) -> str:
|
||||
return self.asHumanReadable()
|
||||
|
||||
|
||||
class XenException(XenFault):
|
||||
|
||||
def __init__(self, message):
|
||||
def __init__(self, message: typing.Any):
|
||||
XenFault.__init__(self, message)
|
||||
logger.debug('Exception create: {0}'.format(message))
|
||||
logger.debug('Exception create: %s', message)
|
||||
|
||||
|
||||
class XenPowerState(object):
|
||||
halted = 'Halted'
|
||||
running = 'Running'
|
||||
suspended = 'Suspended'
|
||||
paused = 'Paused'
|
||||
class XenPowerState: # pylint: disable=too-few-public-methods
|
||||
halted: str = 'Halted'
|
||||
running: str = 'Running'
|
||||
suspended: str = 'Suspended'
|
||||
paused: str = 'Paused'
|
||||
|
||||
|
||||
class XenServer(object):
|
||||
class XenServer: # pylint: disable=too-many-public-methods
|
||||
_originalHost: str
|
||||
_host: str
|
||||
_port: str
|
||||
_useSSL: bool
|
||||
_verifySSL: bool
|
||||
_protocol: str
|
||||
_url: str
|
||||
_loggedIn: bool
|
||||
_username: str
|
||||
_password: str
|
||||
_session: typing.Any
|
||||
_poolName: str
|
||||
_apiVersion: str
|
||||
|
||||
def __init__(self, host, port, username, password, useSSL=False, verifySSL=False):
|
||||
def __init__(self, host: str, port: int, username: str, password: str, useSSL: bool = False, verifySSL: bool = False):
|
||||
self._originalHost = self._host = host
|
||||
self._port = str(port)
|
||||
self._useSSL = useSSL and True or False
|
||||
self._verifySSL = verifySSL and True or False
|
||||
self._protocol = 'http' + (self._useSSL and 's' or '') + '://'
|
||||
self._url = None
|
||||
self._useSSL = bool(useSSL)
|
||||
self._verifySSL = bool(verifySSL)
|
||||
self._protocol = 'http' + ('s' if self._useSSL else '') + '://'
|
||||
self._url = ''
|
||||
self._loggedIn = False
|
||||
self._username = username
|
||||
self._password = password
|
||||
@ -116,16 +126,16 @@ class XenServer(object):
|
||||
self._poolName = self._apiVersion = ''
|
||||
|
||||
@staticmethod
|
||||
def toMb(number):
|
||||
return int(number) / (1024 * 1024)
|
||||
def toMb(number: typing.Union[str, int]) -> int:
|
||||
return int(number) // (1024 * 1024)
|
||||
|
||||
def checkLogin(self):
|
||||
if self._loggedIn is False:
|
||||
def checkLogin(self) -> bool:
|
||||
if not self._loggedIn:
|
||||
self.login()
|
||||
return self._loggedIn
|
||||
|
||||
def getXenapiProperty(self, prop):
|
||||
if self.checkLogin() is False:
|
||||
def getXenapiProperty(self, prop: str) -> typing.Any:
|
||||
if not self.checkLogin():
|
||||
raise Exception("Can't log in")
|
||||
return getattr(self._session.xenapi, prop)
|
||||
|
||||
@ -145,16 +155,16 @@ class XenServer(object):
|
||||
poolName = property(lambda self: self.checkLogin() and self._poolName)
|
||||
hasPool = property(lambda self: self.checkLogin() and self._poolName != '')
|
||||
|
||||
def getPoolName(self):
|
||||
def getPoolName(self) -> str:
|
||||
pool = self.pool.get_all()[0]
|
||||
return self.pool.get_name_label(pool)
|
||||
|
||||
# Login/Logout
|
||||
|
||||
def login(self, switchToMaster=False):
|
||||
def login(self, switchToMaster: bool = False) -> None:
|
||||
try:
|
||||
# We recalculate here url, because we can "switch host" on any moment
|
||||
self._url = self._protocol + self._host + ':' + self._port
|
||||
# On modern python's, HTTPS is verified by default,
|
||||
|
||||
if self._useSSL:
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
|
||||
if self._verifySSL is False:
|
||||
@ -169,7 +179,7 @@ class XenServer(object):
|
||||
self._poolName = str(self.getPoolName())
|
||||
except XenAPI.Failure as e: # XenAPI.Failure: ['HOST_IS_SLAVE', '172.27.0.29'] indicates that this host is an slave of 172.27.0.29, connect to it...
|
||||
if switchToMaster and e.details[0] == 'HOST_IS_SLAVE':
|
||||
logger.info('{0} is an Slave, connecting to master at {1} cause switchToMaster is True'.format(self._host, e.details[1]))
|
||||
logger.info('%s is an Slave, connecting to master at %s cause switchToMaster is True', self._host, e.details[1])
|
||||
self._host = e.details[1]
|
||||
self.login()
|
||||
else:
|
||||
@ -178,28 +188,28 @@ class XenServer(object):
|
||||
logger.exception('Unrecognized xenapi exception')
|
||||
raise
|
||||
|
||||
def test(self):
|
||||
def test(self) -> None:
|
||||
self.login(False)
|
||||
|
||||
def logout(self):
|
||||
def logout(self) -> None:
|
||||
self._session.logout()
|
||||
self._loggedIn = False
|
||||
self._session = None
|
||||
self._poolName = self._apiVersion = ''
|
||||
|
||||
def getHost(self):
|
||||
def getHost(self) -> str:
|
||||
return self._host
|
||||
|
||||
def setHost(self, host):
|
||||
def setHost(self, host: str) -> None:
|
||||
self._host = host
|
||||
|
||||
def getTaskInfo(self, task):
|
||||
def getTaskInfo(self, task: str) -> typing.MutableMapping[str, typing.Any]:
|
||||
progress = 0
|
||||
result = None
|
||||
destroyTask = False
|
||||
try:
|
||||
status = self.task.get_status(task)
|
||||
logger.debug('Task {0} in state {1}'.format(task, status))
|
||||
logger.debug('Task %s in state %s', task, status)
|
||||
if status == 'pending':
|
||||
status = 'running'
|
||||
progress = int(self.task.get_progress(task) * 100)
|
||||
@ -210,7 +220,7 @@ class XenServer(object):
|
||||
result = XenFailure(self.task.get_error_info(task))
|
||||
destroyTask = True
|
||||
except XenAPI.Failure as e:
|
||||
logger.debug('XenServer Failure: {0}'.format(e.details[0]))
|
||||
logger.debug('XenServer Failure: %s', e.details[0])
|
||||
if e.details[0] == 'HANDLE_INVALID':
|
||||
result = None
|
||||
status = 'unknown'
|
||||
@ -232,11 +242,11 @@ class XenServer(object):
|
||||
try:
|
||||
self.task.destroy(task)
|
||||
except Exception as e:
|
||||
logger.info('Task {0} returned error {1}'.format(task, str(e)))
|
||||
logger.warning('Destroy task %s returned error %s', task, str(e))
|
||||
|
||||
return {'result': result, 'progress': progress, 'status':str(status)}
|
||||
|
||||
def getSRs(self):
|
||||
def getSRs(self) -> typing.Iterable[typing.MutableMapping[str, typing.Any]]:
|
||||
for srId in self.SR.get_all():
|
||||
# Only valid SR shared, non iso
|
||||
name_label = self.SR.get_name_label(srId)
|
||||
@ -261,7 +271,7 @@ class XenServer(object):
|
||||
'used': XenServer.toMb(self.SR.get_physical_utilisation(srId))
|
||||
}
|
||||
|
||||
def getSRInfo(self, srId):
|
||||
def getSRInfo(self, srId: str) -> typing.MutableMapping[str, typing.Any]:
|
||||
return {
|
||||
'id': srId,
|
||||
'name': self.SR.get_name_label(srId),
|
||||
@ -269,7 +279,7 @@ class XenServer(object):
|
||||
'used': XenServer.toMb(self.SR.get_physical_utilisation(srId))
|
||||
}
|
||||
|
||||
def getNetworks(self):
|
||||
def getNetworks(self) -> typing.Iterable[typing.MutableMapping[str, typing.Any]]:
|
||||
for netId in self.network.get_all():
|
||||
if self.network.get_other_config(netId).get('is_host_internal_management_network', False) is False:
|
||||
yield {
|
||||
@ -277,13 +287,13 @@ class XenServer(object):
|
||||
'name': self.network.get_name_label(netId),
|
||||
}
|
||||
|
||||
def getNetworkInfo(self, netId):
|
||||
def getNetworkInfo(self, netId: str) -> typing.MutableMapping[str, typing.Any]:
|
||||
return {
|
||||
'id': netId,
|
||||
'name': self.network.get_name_label(netId)
|
||||
}
|
||||
|
||||
def getVMs(self):
|
||||
def getVMs(self) -> typing.Iterable[typing.MutableMapping[str, typing.Any]]:
|
||||
try:
|
||||
vms = self.VM.get_all()
|
||||
for vm in vms:
|
||||
@ -300,21 +310,21 @@ class XenServer(object):
|
||||
except Exception as e:
|
||||
raise XenException(str(e))
|
||||
|
||||
def getVMPowerState(self, vmId):
|
||||
def getVMPowerState(self, vmId: str) -> str:
|
||||
try:
|
||||
power_state = self.VM.get_power_state(vmId)
|
||||
logger.debug('Power state of {0}: {1}'.format(vmId, power_state))
|
||||
logger.debug('Power state of %s: %s', vmId, power_state)
|
||||
return power_state
|
||||
except XenAPI.Failure as e:
|
||||
raise XenFailure(e.details)
|
||||
|
||||
def getVMInfo(self, vmId):
|
||||
def getVMInfo(self, vmId: str) -> typing.Any:
|
||||
try:
|
||||
return self.VM.get_record(vmId)
|
||||
except XenAPI.Failure as e:
|
||||
return XenFailure(e.details)
|
||||
raise XenFailure(e.details)
|
||||
|
||||
def startVM(self, vmId, asnc=True):
|
||||
def startVM(self, vmId: str, asnc: bool = True) -> typing.Optional[str]:
|
||||
vmState = self.getVMPowerState(vmId)
|
||||
if vmState == XenPowerState.running:
|
||||
return None # Already powered on
|
||||
@ -326,7 +336,7 @@ class XenServer(object):
|
||||
return self.Async.VM.start(vmId, False, False)
|
||||
return self.VM.start(vmId, False, False)
|
||||
|
||||
def stopVM(self, vmId, asnc=True):
|
||||
def stopVM(self, vmId: str, asnc: bool = True) -> typing.Optional[str]:
|
||||
vmState = self.getVMPowerState(vmId)
|
||||
if vmState in (XenPowerState.suspended, XenPowerState.halted):
|
||||
return None # Already powered off
|
||||
@ -334,20 +344,21 @@ class XenServer(object):
|
||||
return self.Async.VM.hard_shutdown(vmId)
|
||||
return self.VM.hard_shutdown(vmId)
|
||||
|
||||
def resetVM(self, vmId, asnc=True):
|
||||
def resetVM(self, vmId, asnc=True) -> typing.Optional[str]:
|
||||
vmState = self.getVMPowerState(vmId)
|
||||
if vmState in (XenPowerState.suspended, XenPowerState.halted):
|
||||
return None # Already powered off
|
||||
return None # Already powered off, cannot reboot
|
||||
|
||||
if asnc:
|
||||
return self.Async.VM.hard_reboot(vmId)
|
||||
return self.VM.hard_reboot(vmId)
|
||||
|
||||
def canSuspendVM(self, vmId):
|
||||
def canSuspendVM(self, vmId: str) -> bool:
|
||||
operations = self.VM.get_allowed_operations(vmId)
|
||||
logger.debug('Operations: {}'.format(operations))
|
||||
logger.debug('Operations: %s', operations)
|
||||
return 'suspend' in operations
|
||||
|
||||
def suspendVM(self, vmId, asnc=True):
|
||||
def suspendVM(self, vmId: str, asnc: bool = True) -> typing.Optional[str]:
|
||||
vmState = self.getVMPowerState(vmId)
|
||||
if vmState == XenPowerState.suspended:
|
||||
return None
|
||||
@ -355,7 +366,7 @@ class XenServer(object):
|
||||
return self.Async.VM.suspend(vmId)
|
||||
return self.VM.suspend(vmId)
|
||||
|
||||
def resumeVM(self, vmId, asnc=True):
|
||||
def resumeVM(self, vmId: str, asnc: bool = True) -> typing.Optional[str]:
|
||||
vmState = self.getVMPowerState(vmId)
|
||||
if vmState != XenPowerState.suspended:
|
||||
return None
|
||||
@ -363,7 +374,7 @@ class XenServer(object):
|
||||
return self.Async.VM.resume(vmId, False, False)
|
||||
return self.VM.resume(vmId, False, False)
|
||||
|
||||
def cloneVM(self, vmId, targetName, targetSR=None):
|
||||
def cloneVM(self, vmId: str, targetName: str, targetSR: typing.Optional[str] = None) -> str:
|
||||
"""
|
||||
If targetSR is NONE:
|
||||
Clones the specified VM, making a new VM.
|
||||
@ -376,9 +387,9 @@ class XenServer(object):
|
||||
'full disks' - i.e. not part of a CoW chain.
|
||||
This function can only be called when the VM is in the Halted State.
|
||||
"""
|
||||
logger.debug('Cloning VM {0} to {1} on sr {2}'.format(vmId, targetName, targetSR))
|
||||
logger.debug('Cloning VM %s to %s on sr %s', vmId, targetName, targetSR)
|
||||
operations = self.VM.get_allowed_operations(vmId)
|
||||
logger.debug('Allowed operations: {0}'.format(operations))
|
||||
logger.debug('Allowed operations: %s', operations)
|
||||
|
||||
try:
|
||||
if targetSR:
|
||||
@ -393,7 +404,7 @@ class XenServer(object):
|
||||
except XenAPI.Failure as e:
|
||||
raise XenFailure(e.details)
|
||||
|
||||
def removeVM(self, vmId):
|
||||
def removeVM(self, vmId: str) -> None:
|
||||
logger.debug('Removing machine')
|
||||
vdisToDelete = []
|
||||
for vdb in self.VM.get_VBDs(vmId):
|
||||
@ -403,19 +414,19 @@ class XenServer(object):
|
||||
if vdi == 'OpaqueRef:NULL':
|
||||
logger.debug('VDB without VDI')
|
||||
continue
|
||||
logger.debug('VDI: {0}'.format(vdi))
|
||||
logger.debug('VDI: %s', vdi)
|
||||
except Exception:
|
||||
logger.exception('Exception getting VDI from VDB')
|
||||
if self.VDI.get_read_only(vdi) is True:
|
||||
logger.debug('{0} is read only, skipping'.format(vdi))
|
||||
logger.debug('%s is read only, skipping', vdi)
|
||||
continue
|
||||
logger.debug('VDI to delete: {0}'.format(vdi))
|
||||
logger.debug('VDI to delete: %s', vdi)
|
||||
vdisToDelete.append(vdi)
|
||||
self.VM.destroy(vmId)
|
||||
for vdi in vdisToDelete:
|
||||
self.VDI.destroy(vdi)
|
||||
|
||||
def configureVM(self, vmId, **kwargs):
|
||||
def configureVM(self, vmId: str, **kwargs):
|
||||
"""
|
||||
Optional args:
|
||||
mac = { 'network': netId, 'mac': mac }
|
||||
@ -423,8 +434,8 @@ class XenServer(object):
|
||||
|
||||
Mac address should be in the range 02:xx:xx:xx:xx (recommended, but not a "have to")
|
||||
"""
|
||||
mac = kwargs.get('mac', None)
|
||||
memory = kwargs.get('memory', None)
|
||||
mac: typing.Optional[typing.Dict[str, str]] = kwargs.get('mac', None)
|
||||
memory: typing.Optional[typing.Union[str, int]] = kwargs.get('memory', None)
|
||||
|
||||
# If requested mac address change
|
||||
try:
|
||||
@ -433,7 +444,7 @@ class XenServer(object):
|
||||
vif = self.VIF.get_record(vifId)
|
||||
|
||||
if vif['network'] == mac['network']:
|
||||
logger.debug('Found VIF: {0}'.format(vif['network']))
|
||||
logger.debug('Found VIF: %s', vif['network'])
|
||||
self.VIF.destroy(vifId)
|
||||
|
||||
# for k in ['status_code', 'status_detail', 'uuid']:
|
||||
@ -445,15 +456,15 @@ class XenServer(object):
|
||||
vif['MAC_autogenerated'] = False
|
||||
self.VIF.create(vif)
|
||||
# If requested memory change
|
||||
if memory is not None:
|
||||
logger.debug('Setting up memory to {0} MB'.format(memory))
|
||||
if memory:
|
||||
logger.debug('Setting up memory to %s MB', memory)
|
||||
# Convert memory to MB
|
||||
memory = str(int(memory) * 1024 * 1024)
|
||||
self.VM.set_memory_limits(vmId, memory, memory, memory, memory)
|
||||
except XenAPI.Failure as e:
|
||||
raise XenFailure(e.details)
|
||||
|
||||
def provisionVM(self, vmId, **kwargs):
|
||||
def provisionVM(self, vmId: str, **kwargs):
|
||||
tags = self.VM.get_tags(vmId)
|
||||
try:
|
||||
del tags[tags.index(TAG_TEMPLATE)]
|
||||
@ -466,10 +477,10 @@ class XenServer(object):
|
||||
return self.Async.VM.provision(vmId)
|
||||
return self.VM.provision(vmId)
|
||||
|
||||
def convertToTemplate(self, vmId, shadowMultiplier=4):
|
||||
def convertToTemplate(self, vmId: str, shadowMultiplier: int = 4) -> None:
|
||||
try:
|
||||
operations = self.VM.get_allowed_operations(vmId)
|
||||
logger.debug('Allowed operations: {0}'.format(operations))
|
||||
logger.debug('Allowed operations: %s', operations)
|
||||
if 'make_into_template' not in operations:
|
||||
raise XenException('Convert in template is not supported for this machine')
|
||||
self.VM.set_is_a_template(vmId, True)
|
||||
@ -487,15 +498,14 @@ class XenServer(object):
|
||||
try:
|
||||
self.VM.set_HVM_shadow_multiplier(vmId, float(shadowMultiplier))
|
||||
except Exception:
|
||||
# Can't set shadowMultiplier, nothing happens
|
||||
pass # TODO: Log this?
|
||||
pass # Can't set shadowMultiplier, nothing happens
|
||||
except XenAPI.Failure as e:
|
||||
raise XenFailure(e.details)
|
||||
|
||||
def removeTemplate(self, templateId):
|
||||
def removeTemplate(self, templateId: str) -> None:
|
||||
self.removeVM(templateId)
|
||||
|
||||
def cloneTemplate(self, templateId, targetName):
|
||||
def cloneTemplate(self, templateId: str, targetName: str) -> str:
|
||||
"""
|
||||
After cloning template, we must deploy the VM so it's a full usable VM
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user