Fixed Xen

This commit is contained in:
Adolfo Gómez García 2019-11-18 14:50:15 +01:00
parent 8d680ad32d
commit 7e034a9de1
8 changed files with 352 additions and 547 deletions

View File

@ -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

View File

@ -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

View File

@ -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')
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])

View File

@ -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))]

View File

@ -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
"""

View File

@ -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

View File

@ -54,11 +54,15 @@
# 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)
@ -66,7 +70,9 @@ 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._connection = (None, None)
def add_extra_header(self, key, value):
def add_extra_header(self, key: str, value: str) -> None:
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:
def make_connection(self, host: str) -> httplib.HTTPConnection:
return UDSHTTPConnection(host)
def send_request(self, connection, handler, request_body):
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)
@ -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:
raise xmlrpclib.Fault(500, 'Missing Value in response from server')
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 ErrorDescription in response from server')
# Based upon _Method from xmlrpclib.
@ -251,7 +253,6 @@ class _Dispatcher:
def __repr__(self):
if self.__name:
return '<XenAPI._Dispatcher for %s>' % self.__name
else:
return '<XenAPI._Dispatcher>'
def __getattr__(self, name):

View File

@ -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
"""