forked from shaba/openuds
upgrading OpenNebula connector
This commit is contained in:
parent
99fe68608c
commit
ce91840622
@ -31,6 +31,7 @@
|
|||||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
import logging
|
import logging
|
||||||
|
import typing
|
||||||
|
|
||||||
from django.utils.translation import ugettext_noop as _
|
from django.utils.translation import ugettext_noop as _
|
||||||
from uds.core.services import ServiceProvider
|
from uds.core.services import ServiceProvider
|
||||||
@ -43,7 +44,7 @@ from . import on
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Provider(ServiceProvider):
|
class Provider(ServiceProvider): # pylint: disable=too-many-public-methods
|
||||||
'''
|
'''
|
||||||
This class represents the sample services provider
|
This class represents the sample services provider
|
||||||
|
|
||||||
@ -96,7 +97,7 @@ class Provider(ServiceProvider):
|
|||||||
timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=90, tooltip=_('Timeout in seconds of connection to OpenNebula'), required=True, tab=gui.ADVANCED_TAB)
|
timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=90, tooltip=_('Timeout in seconds of connection to OpenNebula'), required=True, tab=gui.ADVANCED_TAB)
|
||||||
|
|
||||||
# Own variables
|
# Own variables
|
||||||
_api = None
|
_api: typing.Optional[on.client.OpenNebulaClient] = None
|
||||||
|
|
||||||
def initialize(self, values=None):
|
def initialize(self, values=None):
|
||||||
'''
|
'''
|
||||||
@ -106,18 +107,18 @@ class Provider(ServiceProvider):
|
|||||||
# Just reset _api connection variable
|
# Just reset _api connection variable
|
||||||
self._api = None
|
self._api = None
|
||||||
|
|
||||||
if values is not None:
|
if values:
|
||||||
self.timeout.value = validators.validateTimeout(self.timeout.value)
|
self.timeout.value = validators.validateTimeout(self.timeout.value)
|
||||||
logger.debug('Endpoint: %s', self.endpoint)
|
logger.debug('Endpoint: %s', self.endpoint)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def endpoint(self):
|
def endpoint(self) -> str:
|
||||||
return 'http{}://{}:{}/RPC2'.format('s' if self.ssl.isTrue() else '', self.host.value, self.port.value)
|
return 'http{}://{}:{}/RPC2'.format('s' if self.ssl.isTrue() else '', self.host.value, self.port.value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api(self):
|
def api(self) -> on.client.OpenNebulaClient:
|
||||||
if self._api is None:
|
if self._api is None:
|
||||||
self._api = on.OpenNebulaClient(self.username.value, self.password.value, self.endpoint)
|
self._api = on.client.OpenNebulaClient(self.username.value, self.password.value, self.endpoint)
|
||||||
|
|
||||||
logger.debug('Api: %s', self._api)
|
logger.debug('Api: %s', self._api)
|
||||||
return self._api
|
return self._api
|
||||||
|
@ -1,304 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
#
|
|
||||||
# Copyright (c) 2012-2019 Virtual Cable S.L.
|
|
||||||
# All rights reserved.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
# are permitted provided that the following conditions are met:
|
|
||||||
#
|
|
||||||
# * Redistributions of source code must retain the above copyright notice,
|
|
||||||
# this list of conditions and the following disclaimer.
|
|
||||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
|
||||||
# and/or other materials provided with the distribution.
|
|
||||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
|
||||||
# may be used to endorse or promote products derived from this software
|
|
||||||
# without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
"""
|
|
||||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
|
||||||
"""
|
|
||||||
|
|
||||||
# pylint: disable=maybe-no-member
|
|
||||||
import types
|
|
||||||
import xmlrpc.client
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from uds.core.util import xml2dict
|
|
||||||
|
|
||||||
from . import storage
|
from . import storage
|
||||||
from . import template
|
from . import template
|
||||||
from . import vm
|
from . import vm
|
||||||
|
from . import client
|
||||||
|
|
||||||
# Import submodules
|
# Import common
|
||||||
from .common import VmState, ImageState, sanitizeName
|
from .common import VmState, ImageState, sanitizeName
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
# Decorator
|
|
||||||
def ensureConnected(fnc):
|
|
||||||
def inner(*args, **kwargs):
|
|
||||||
args[0].connect()
|
|
||||||
return fnc(*args, **kwargs)
|
|
||||||
return inner
|
|
||||||
|
|
||||||
|
|
||||||
# Result checker
|
|
||||||
def checkResult(lst, parseResult=True):
|
|
||||||
if not lst[0]:
|
|
||||||
raise Exception('OpenNebula error {}: "{}"'.format(lst[2], lst[1]))
|
|
||||||
if parseResult:
|
|
||||||
return xml2dict.parse(lst[1])
|
|
||||||
|
|
||||||
return lst[1]
|
|
||||||
|
|
||||||
|
|
||||||
def asList(element):
|
|
||||||
if isinstance(element, (tuple, list)):
|
|
||||||
return element
|
|
||||||
return (element,)
|
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyShadowingNames
|
|
||||||
class OpenNebulaClient:
|
|
||||||
def __init__(self, username, password, endpoint):
|
|
||||||
self.username = username
|
|
||||||
self.password = password
|
|
||||||
self.endpoint = endpoint
|
|
||||||
self.connection = None
|
|
||||||
self.cachedVersion = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def sessionString(self):
|
|
||||||
return '{}:{}'.format(self.username, self.password)
|
|
||||||
|
|
||||||
@property # type: ignore
|
|
||||||
@ensureConnected
|
|
||||||
def version(self):
|
|
||||||
if self.cachedVersion is None:
|
|
||||||
# Retrieve Version & keep it
|
|
||||||
result = self.connection.one.system.version(self.sessionString)
|
|
||||||
self.cachedVersion = checkResult(result, parseResult=False).split('.')
|
|
||||||
return self.cachedVersion
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
if self.connection is not None:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.connection = xmlrpc.client.ServerProxy(self.endpoint) # @UndefinedVariable
|
|
||||||
|
|
||||||
@ensureConnected
|
|
||||||
def enumStorage(self, storageType=0):
|
|
||||||
storageType = str(storageType) # Ensure it is an string
|
|
||||||
# Invoke datastore pools info, no parameters except connection string
|
|
||||||
result = self.connection.one.datastorepool.info(self.sessionString)
|
|
||||||
result = checkResult(result)
|
|
||||||
for ds in asList(result['DATASTORE_POOL']['DATASTORE']):
|
|
||||||
if ds['TYPE'] == storageType:
|
|
||||||
yield(ds['ID'], ds['NAME'], ds['TOTAL_MB'], ds['FREE_MB'])
|
|
||||||
|
|
||||||
@ensureConnected
|
|
||||||
def enumTemplates(self):
|
|
||||||
"""
|
|
||||||
Invoke templates pools info, with this parameters:
|
|
||||||
1.- Session string
|
|
||||||
2.- Filter flag - < = -3: Connected user’s resources - -2: All resources - -1: Connected user’s and his group’s resources - > = 0: UID User’s Resources
|
|
||||||
3.- When the next parameter is >= -1 this is the Range start ID. Can be -1. For smaller values this is the offset used for pagination.
|
|
||||||
4.- For values >= -1 this is the Range end ID. Can be -1 to get until the last ID. For values < -1 this is the page size used for pagination.
|
|
||||||
"""
|
|
||||||
result = checkResult(self.connection.one.templatepool.info(self.sessionString, -1, -1, -1))
|
|
||||||
for ds in asList(result['VMTEMPLATE_POOL']['VMTEMPLATE']):
|
|
||||||
try:
|
|
||||||
yield(ds['ID'], ds['NAME'], ds['TEMPLATE']['MEMORY'])
|
|
||||||
except Exception: # Maybe no memory? (then template is not usable)
|
|
||||||
pass
|
|
||||||
|
|
||||||
@ensureConnected
|
|
||||||
def enumImages(self):
|
|
||||||
"""
|
|
||||||
Invoke images pools info, with this parameters:
|
|
||||||
1.- Session string
|
|
||||||
2.- Filter flag - < = -3: Connected user’s resources - -2: All resources - -1: Connected user’s and his group’s resources - > = 0: UID User’s Resources
|
|
||||||
3.- When the next parameter is >= -1 this is the Range start ID. Can be -1. For smaller values this is the offset used for pagination.
|
|
||||||
4.- For values >= -1 this is the Range end ID. Can be -1 to get until the last ID. For values < -1 this is the page size used for pagination.
|
|
||||||
"""
|
|
||||||
result = self.connection.one.imagepool.info(self.sessionString, -1, -1, -1)
|
|
||||||
result = checkResult(result)
|
|
||||||
for ds in asList(result['IMAGE_POOL']['IMAGE']):
|
|
||||||
yield(ds['ID'], ds['NAME'])
|
|
||||||
|
|
||||||
@ensureConnected
|
|
||||||
def templateInfo(self, templateId, extraInfo=False):
|
|
||||||
"""
|
|
||||||
Returns a list
|
|
||||||
first element is a dictionary (built from XML)
|
|
||||||
second is original XML
|
|
||||||
"""
|
|
||||||
result = self.connection.one.template.info(self.sessionString, int(templateId), extraInfo)
|
|
||||||
res = checkResult(result)
|
|
||||||
return res, result[1]
|
|
||||||
|
|
||||||
@ensureConnected
|
|
||||||
def instantiateTemplate(self, templateId, vmName, createHold=False, templateToMerge='', privatePersistent=False):
|
|
||||||
"""
|
|
||||||
Instantiates a template (compatible with open nebula 4 & 5)
|
|
||||||
1.- Session string
|
|
||||||
2.- ID Of the template to instantiate
|
|
||||||
3.- Name of the vm. If empty, open nebula will assign one
|
|
||||||
4.- False to create machine on pending (default), True to create it on hold
|
|
||||||
5.- A string containing an extra template to be merged with the one being instantiated. It can be empty. Syntax can be the usual attribute=value or XML.
|
|
||||||
6.- true to create a private persistent copy of the template plus any image defined in DISK, and instantiate that copy.
|
|
||||||
Note: This parameter is ignored on version 4, it is new for version 5.
|
|
||||||
"""
|
|
||||||
if self.version[0] == '4': # Version 4 has one less parameter than version 5
|
|
||||||
result = self.connection.one.template.instantiate(self.sessionString, int(templateId), vmName, createHold, templateToMerge)
|
|
||||||
else:
|
|
||||||
result = self.connection.one.template.instantiate(self.sessionString, int(templateId), vmName, createHold, templateToMerge, privatePersistent)
|
|
||||||
|
|
||||||
return checkResult(result, parseResult=False)
|
|
||||||
|
|
||||||
@ensureConnected
|
|
||||||
def updateTemplate(self, templateId, templateData, updateType=0):
|
|
||||||
"""
|
|
||||||
Updates the template with the templateXml
|
|
||||||
1.- Session string
|
|
||||||
2.- Object ID (integer)
|
|
||||||
3.- The new template contents. Syntax can be the usual attribute=value or XML.
|
|
||||||
4.- Update type. 0 replace the whole template, 1 merge with the existing one
|
|
||||||
"""
|
|
||||||
result = self.connection.one.template.update(self.sessionString, int(templateId), templateData, int(updateType))
|
|
||||||
return checkResult(result, parseResult=False)
|
|
||||||
|
|
||||||
@ensureConnected
|
|
||||||
def cloneTemplate(self, templateId, name):
|
|
||||||
"""
|
|
||||||
Clones the template
|
|
||||||
"""
|
|
||||||
if self.version[0] == '4':
|
|
||||||
result = self.connection.one.template.clone(self.sessionString, int(templateId), name)
|
|
||||||
else:
|
|
||||||
result = self.connection.one.template.clone(self.sessionString, int(templateId), name, False) # This works as previous version clone
|
|
||||||
|
|
||||||
return checkResult(result, parseResult=False)
|
|
||||||
|
|
||||||
@ensureConnected
|
|
||||||
def deleteTemplate(self, templateId):
|
|
||||||
"""
|
|
||||||
Deletes the template (not images)
|
|
||||||
"""
|
|
||||||
result = self.connection.one.template.delete(self.sessionString, int(templateId))
|
|
||||||
return checkResult(result, parseResult=False)
|
|
||||||
|
|
||||||
@ensureConnected
|
|
||||||
def cloneImage(self, srcId, name, datastoreId=-1):
|
|
||||||
"""
|
|
||||||
Clones the image.
|
|
||||||
"""
|
|
||||||
result = self.connection.one.image.clone(self.sessionString, int(srcId), name, int(datastoreId))
|
|
||||||
return checkResult(result, parseResult=False)
|
|
||||||
|
|
||||||
@ensureConnected
|
|
||||||
def makePersistentImage(self, imageId, persistent=False):
|
|
||||||
"""
|
|
||||||
Clones the image.
|
|
||||||
"""
|
|
||||||
result = self.connection.one.image.persistent(self.sessionString, int(imageId), persistent)
|
|
||||||
return checkResult(result, parseResult=False)
|
|
||||||
|
|
||||||
@ensureConnected
|
|
||||||
def deleteImage(self, imageId):
|
|
||||||
"""
|
|
||||||
Deletes an image
|
|
||||||
"""
|
|
||||||
result = self.connection.one.image.delete(self.sessionString, int(imageId))
|
|
||||||
return checkResult(result, parseResult=False)
|
|
||||||
|
|
||||||
@ensureConnected
|
|
||||||
def imageInfo(self, imageInfo):
|
|
||||||
"""
|
|
||||||
Returns a list
|
|
||||||
first element is a dictionary (built from XML)
|
|
||||||
second is original XML
|
|
||||||
"""
|
|
||||||
result = self.connection.one.image.info(self.sessionString, int(imageInfo))
|
|
||||||
res = checkResult(result)
|
|
||||||
return res, result[1]
|
|
||||||
|
|
||||||
@ensureConnected
|
|
||||||
def enumVMs(self):
|
|
||||||
"""
|
|
||||||
Invoke vm pools info, with this parameters:
|
|
||||||
1.- Session string
|
|
||||||
2.- Filter flag - < = -3: Connected user’s resources - -2: All resources - -1: Connected user’s and his group’s resources - > = 0: UID User’s Resources
|
|
||||||
3.- When the next parameter is >= -1 this is the Range start ID. Can be -1. For smaller values this is the offset used for pagination.
|
|
||||||
4.- For values >= -1 this is the Range end ID. Can be -1 to get until the last ID. For values < -1 this is the page size used for pagination.
|
|
||||||
5.- VM state to filter by. (-2 = any state including DONE, -1 = any state EXCEPT DONE)
|
|
||||||
"""
|
|
||||||
result = self.connection.one.vmpool.info(self.sessionString, -1, -1, -1, -1)
|
|
||||||
result = checkResult(result)
|
|
||||||
for ds in asList(result['VM_POOL']['VM']):
|
|
||||||
yield(ds['ID'], ds['NAME'])
|
|
||||||
|
|
||||||
@ensureConnected
|
|
||||||
def VMInfo(self, vmId):
|
|
||||||
"""
|
|
||||||
Returns a list
|
|
||||||
first element is a dictionary (built from XML)
|
|
||||||
second is original XML
|
|
||||||
"""
|
|
||||||
result = self.connection.one.vm.info(self.sessionString, int(vmId))
|
|
||||||
res = checkResult(result)
|
|
||||||
return res, result[1]
|
|
||||||
|
|
||||||
@ensureConnected
|
|
||||||
def deleteVM(self, vmId):
|
|
||||||
"""
|
|
||||||
Deletes an vm
|
|
||||||
"""
|
|
||||||
if self.version[0] == '4':
|
|
||||||
return self.VMAction(vmId, 'delete')
|
|
||||||
else:
|
|
||||||
# Version 5
|
|
||||||
return self.VMAction(vmId, 'terminate-hard')
|
|
||||||
|
|
||||||
@ensureConnected
|
|
||||||
def getVMState(self, vmId):
|
|
||||||
"""
|
|
||||||
Returns the VM State
|
|
||||||
"""
|
|
||||||
result = self.connection.one.vm.info(self.sessionString, int(vmId))
|
|
||||||
return int(checkResult(result)['VM']['STATE'])
|
|
||||||
|
|
||||||
@ensureConnected
|
|
||||||
def getVMSubstate(self, vmId):
|
|
||||||
"""
|
|
||||||
Returns the VM State
|
|
||||||
"""
|
|
||||||
result = self.connection.one.vm.info(self.sessionString, int(vmId))
|
|
||||||
r = checkResult(result)
|
|
||||||
try:
|
|
||||||
if int(r['VM']['STATE']) == VmState.ACTIVE:
|
|
||||||
return int(r['VM']['LCM_STATE'])
|
|
||||||
# Substate is not available if VM state is not active
|
|
||||||
return -1
|
|
||||||
except Exception:
|
|
||||||
logger.exception('getVMSubstate')
|
|
||||||
return -1
|
|
||||||
|
|
||||||
@ensureConnected
|
|
||||||
def VMAction(self, vmId, action):
|
|
||||||
result = self.connection.one.vm.action(self.sessionString, action, int(vmId))
|
|
||||||
return checkResult(result, parseResult=False)
|
|
||||||
|
325
server/src/uds/services/OpenNebula/on/client.py
Normal file
325
server/src/uds/services/OpenNebula/on/client.py
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012-2019 Virtual Cable S.L.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
# are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
|
"""
|
||||||
|
|
||||||
|
# pylint: disable=maybe-no-member
|
||||||
|
import xmlrpc.client
|
||||||
|
import logging
|
||||||
|
import typing
|
||||||
|
|
||||||
|
from uds.core.util import xml2dict
|
||||||
|
|
||||||
|
from . import types
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
RT = typing.TypeVar('RT')
|
||||||
|
|
||||||
|
# Decorator
|
||||||
|
def ensureConnected(fnc: typing.Callable[..., RT]) -> typing.Callable[..., RT]:
|
||||||
|
def inner(*args, **kwargs) -> RT:
|
||||||
|
args[0].connect()
|
||||||
|
return fnc(*args, **kwargs)
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
|
# Result checker
|
||||||
|
def checkResultRaw(lst: typing.List) -> str:
|
||||||
|
# Openebula response is always this way:
|
||||||
|
# [Boolean, String, ErrorCode]
|
||||||
|
# First is True if ok, False if not
|
||||||
|
# Second is Result String if was ok
|
||||||
|
# Third is error code if first is False
|
||||||
|
if not lst[0]:
|
||||||
|
raise Exception('OpenNebula error {}: "{}"'.format(lst[2], lst[1]))
|
||||||
|
|
||||||
|
return lst[1]
|
||||||
|
|
||||||
|
|
||||||
|
def checkResult(lst: typing.List) -> typing.Tuple[typing.Dict, str]:
|
||||||
|
return xml2dict.parse(checkResultRaw(lst)), lst[1]
|
||||||
|
|
||||||
|
|
||||||
|
def asIterable(element: RT) -> typing.Iterable[RT]:
|
||||||
|
if isinstance(element, (tuple, list)):
|
||||||
|
return element
|
||||||
|
return (element,)
|
||||||
|
|
||||||
|
|
||||||
|
class OpenNebulaClient: # pylint: disable=too-many-public-methods
|
||||||
|
username: str
|
||||||
|
password: str
|
||||||
|
endpoint: str
|
||||||
|
connection: xmlrpc.client.ServerProxy
|
||||||
|
cachedVersion: typing.Optional[typing.List[str]]
|
||||||
|
|
||||||
|
def __init__(self, username: str, password: str, endpoint: str) -> None:
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self.endpoint = endpoint
|
||||||
|
self.connection = None
|
||||||
|
self.cachedVersion = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sessionString(self):
|
||||||
|
return '{}:{}'.format(self.username, self.password)
|
||||||
|
|
||||||
|
@property # type: ignore
|
||||||
|
@ensureConnected
|
||||||
|
def version(self) -> typing.List[str]:
|
||||||
|
if self.cachedVersion is None:
|
||||||
|
# Retrieve Version & keep it
|
||||||
|
result = self.connection.one.system.version(self.sessionString)
|
||||||
|
self.cachedVersion = checkResultRaw(result).split('.')
|
||||||
|
return self.cachedVersion
|
||||||
|
|
||||||
|
def connect(self) -> None:
|
||||||
|
if self.connection is not None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.connection = xmlrpc.client.ServerProxy(self.endpoint)
|
||||||
|
|
||||||
|
@ensureConnected
|
||||||
|
def enumStorage(self, storageType: int = 0) -> typing.Iterable[types.StorageType]:
|
||||||
|
sstorageType = str(storageType) # Ensure it is an string
|
||||||
|
# Invoke datastore pools info, no parameters except connection string
|
||||||
|
result, _ = checkResult(self.connection.one.datastorepool.info(self.sessionString))
|
||||||
|
for ds in asIterable(result['DATASTORE_POOL']['DATASTORE']):
|
||||||
|
if ds['TYPE'] == sstorageType:
|
||||||
|
yield types.StorageType(ds['ID'], ds['NAME'], int(ds['TOTAL_MB']), int(ds['FREE_MB']), None)
|
||||||
|
|
||||||
|
@ensureConnected
|
||||||
|
def enumTemplates(self) -> typing.Iterable[types.TemplateType]:
|
||||||
|
"""
|
||||||
|
Invoke templates pools info, with this parameters:
|
||||||
|
1.- Session string
|
||||||
|
2.- Filter flag - < = -3: Connected user’s resources - -2: All resources - -1: Connected user’s and his group’s resources - > = 0: UID User’s Resources
|
||||||
|
3.- When the next parameter is >= -1 this is the Range start ID. Can be -1. For smaller values this is the offset used for pagination.
|
||||||
|
4.- For values >= -1 this is the Range end ID. Can be -1 to get until the last ID. For values < -1 this is the page size used for pagination.
|
||||||
|
"""
|
||||||
|
result, _ = checkResult(self.connection.one.templatepool.info(self.sessionString, -1, -1, -1))
|
||||||
|
for ds in asIterable(result['VMTEMPLATE_POOL']['VMTEMPLATE']):
|
||||||
|
try:
|
||||||
|
yield types.TemplateType(ds['ID'], ds['NAME'], int(ds['TEMPLATE']['MEMORY']), None)
|
||||||
|
except Exception: # Maybe no memory? (then template is not usable)
|
||||||
|
pass
|
||||||
|
|
||||||
|
@ensureConnected
|
||||||
|
def enumImages(self) -> typing.Iterable[types.ImageType]:
|
||||||
|
"""
|
||||||
|
Invoke images pools info, with this parameters:
|
||||||
|
1.- Session string
|
||||||
|
2.- Filter flag - < = -3: Connected user’s resources - -2: All resources - -1: Connected user’s and his group’s resources - > = 0: UID User’s Resources
|
||||||
|
3.- When the next parameter is >= -1 this is the Range start ID. Can be -1. For smaller values this is the offset used for pagination.
|
||||||
|
4.- For values >= -1 this is the Range end ID. Can be -1 to get until the last ID. For values < -1 this is the page size used for pagination.
|
||||||
|
"""
|
||||||
|
result, _ = checkResult(self.connection.one.imagepool.info(self.sessionString, -1, -1, -1))
|
||||||
|
for ds in asIterable(result['IMAGE_POOL']['IMAGE']):
|
||||||
|
yield types.ImageType(
|
||||||
|
ds['ID'],
|
||||||
|
ds['NAME'],
|
||||||
|
int(ds.get('SIZE', -1)),
|
||||||
|
ds.get('PERSISTENT', '0') != '0',
|
||||||
|
int(ds.get('RUNNING_VMS', '0')),
|
||||||
|
types.ImageState.fromState(ds['STATE']),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
@ensureConnected
|
||||||
|
def templateInfo(self, templateId: str, extraInfo: bool = False) -> types.TemplateType:
|
||||||
|
"""
|
||||||
|
Returns a list
|
||||||
|
first element is a dictionary (built from XML)
|
||||||
|
second is original XML
|
||||||
|
"""
|
||||||
|
result = self.connection.one.template.info(self.sessionString, int(templateId), extraInfo)
|
||||||
|
ds, xml = checkResult(result)
|
||||||
|
return types.TemplateType(ds['VMTEMPLATE']['ID'], ds['VMTEMPLATE']['NAME'], int(ds['VMTEMPLATE']['TEMPLATE']['MEMORY']), xml)
|
||||||
|
|
||||||
|
@ensureConnected
|
||||||
|
def instantiateTemplate(self, templateId: str, vmName: str, createHold: bool = False, templateToMerge: str = '', privatePersistent: bool = False) -> str:
|
||||||
|
"""
|
||||||
|
Instantiates a template (compatible with open nebula 4 & 5)
|
||||||
|
1.- Session string
|
||||||
|
2.- ID Of the template to instantiate
|
||||||
|
3.- Name of the vm. If empty, open nebula will assign one
|
||||||
|
4.- False to create machine on pending (default), True to create it on hold
|
||||||
|
5.- A string containing an extra template to be merged with the one being instantiated. It can be empty. Syntax can be the usual attribute=value or XML.
|
||||||
|
6.- true to create a private persistent copy of the template plus any image defined in DISK, and instantiate that copy.
|
||||||
|
Note: This parameter is ignored on version 4, it is new for version 5.
|
||||||
|
"""
|
||||||
|
if self.version[0] == '4': # Version 4 has one less parameter than version 5
|
||||||
|
result = self.connection.one.template.instantiate(self.sessionString, int(templateId), vmName, createHold, templateToMerge)
|
||||||
|
else:
|
||||||
|
result = self.connection.one.template.instantiate(self.sessionString, int(templateId), vmName, createHold, templateToMerge, privatePersistent)
|
||||||
|
|
||||||
|
return checkResultRaw(result)
|
||||||
|
|
||||||
|
@ensureConnected
|
||||||
|
def updateTemplate(self, templateId: str, templateData: str, updateType: int = 0) -> str:
|
||||||
|
"""
|
||||||
|
Updates the template with the templateXml
|
||||||
|
1.- Session string
|
||||||
|
2.- Object ID (integer)
|
||||||
|
3.- The new template contents. Syntax can be the usual attribute=value or XML.
|
||||||
|
4.- Update type. 0 replace the whole template, 1 merge with the existing one
|
||||||
|
"""
|
||||||
|
result = self.connection.one.template.update(self.sessionString, int(templateId), templateData, int(updateType))
|
||||||
|
return checkResultRaw(result)
|
||||||
|
|
||||||
|
@ensureConnected
|
||||||
|
def cloneTemplate(self, templateId: str, name: str) -> str:
|
||||||
|
"""
|
||||||
|
Clones the template
|
||||||
|
"""
|
||||||
|
if self.version[0] == '4':
|
||||||
|
result = self.connection.one.template.clone(self.sessionString, int(templateId), name)
|
||||||
|
else:
|
||||||
|
result = self.connection.one.template.clone(self.sessionString, int(templateId), name, False) # This works as previous version clone
|
||||||
|
|
||||||
|
return checkResultRaw(result)
|
||||||
|
|
||||||
|
@ensureConnected
|
||||||
|
def deleteTemplate(self, templateId: str) -> str:
|
||||||
|
"""
|
||||||
|
Deletes the template (not images)
|
||||||
|
"""
|
||||||
|
result = self.connection.one.template.delete(self.sessionString, int(templateId))
|
||||||
|
return checkResultRaw(result)
|
||||||
|
|
||||||
|
@ensureConnected
|
||||||
|
def cloneImage(self, srcId: str, name: str, datastoreId: typing.Union[str, int] = -1) -> str:
|
||||||
|
"""
|
||||||
|
Clones the image.
|
||||||
|
"""
|
||||||
|
result = self.connection.one.image.clone(self.sessionString, int(srcId), name, int(datastoreId))
|
||||||
|
return checkResultRaw(result)
|
||||||
|
|
||||||
|
@ensureConnected
|
||||||
|
def makePersistentImage(self, imageId: str, persistent: bool = False) -> str:
|
||||||
|
"""
|
||||||
|
Clones the image.
|
||||||
|
"""
|
||||||
|
result = self.connection.one.image.persistent(self.sessionString, int(imageId), persistent)
|
||||||
|
return checkResultRaw(result)
|
||||||
|
|
||||||
|
@ensureConnected
|
||||||
|
def deleteImage(self, imageId: str) -> str:
|
||||||
|
"""
|
||||||
|
Deletes an image
|
||||||
|
"""
|
||||||
|
result = self.connection.one.image.delete(self.sessionString, int(imageId))
|
||||||
|
return checkResultRaw(result)
|
||||||
|
|
||||||
|
@ensureConnected
|
||||||
|
def imageInfo(self, imageInfo) -> types.ImageType:
|
||||||
|
"""
|
||||||
|
Returns a list
|
||||||
|
first element is a dictionary (built from XML)
|
||||||
|
second is original XML
|
||||||
|
"""
|
||||||
|
result, xml = checkResult(self.connection.one.image.info(self.sessionString, int(imageInfo)))
|
||||||
|
ds = result['IMAGE']
|
||||||
|
return types.ImageType(
|
||||||
|
ds['ID'],
|
||||||
|
ds['NAME'],
|
||||||
|
int(ds.get('SIZE', -1)),
|
||||||
|
ds.get('PERSISTENT', '0') != '0',
|
||||||
|
int(ds.get('RUNNING_VMS', '0')),
|
||||||
|
types.ImageState.fromState(ds['STATE']),
|
||||||
|
xml
|
||||||
|
)
|
||||||
|
|
||||||
|
@ensureConnected
|
||||||
|
def enumVMs(self) -> typing.Iterable[types.VirtualMachineType]:
|
||||||
|
"""
|
||||||
|
Invoke vm pools info, with this parameters:
|
||||||
|
1.- Session string
|
||||||
|
2.- Filter flag - < = -3: Connected user’s resources - -2: All resources - -1: Connected user’s and his group’s resources - > = 0: UID User’s Resources
|
||||||
|
3.- When the next parameter is >= -1 this is the Range start ID. Can be -1. For smaller values this is the offset used for pagination.
|
||||||
|
4.- For values >= -1 this is the Range end ID. Can be -1 to get until the last ID. For values < -1 this is the page size used for pagination.
|
||||||
|
5.- VM state to filter by. (-2 = any state including DONE, -1 = any state EXCEPT DONE)
|
||||||
|
"""
|
||||||
|
result, _ = checkResult(self.connection.one.vmpool.info(self.sessionString, -1, -1, -1, -1))
|
||||||
|
if result['VM_POOL']:
|
||||||
|
for ds in asIterable(result['VM_POOL'].get('VM', [])):
|
||||||
|
yield types.VirtualMachineType(ds['ID'], ds['NAME'], int(ds.get('MEMORY', '0')), types.VmState.fromState(ds['STATE']), None)
|
||||||
|
|
||||||
|
@ensureConnected
|
||||||
|
def VMInfo(self, vmId: str) -> types.VirtualMachineType:
|
||||||
|
"""
|
||||||
|
Returns a list
|
||||||
|
first element is a dictionary (built from XML)
|
||||||
|
second is original XML
|
||||||
|
"""
|
||||||
|
result, xml = checkResult(self.connection.one.vm.info(self.sessionString, int(vmId)))
|
||||||
|
ds = result['VM']
|
||||||
|
return types.VirtualMachineType(ds['ID'], ds['NAME'], int(ds.get('MEMORY', '0')), types.VmState.fromState(ds['STATE']), xml)
|
||||||
|
|
||||||
|
@ensureConnected
|
||||||
|
def deleteVM(self, vmId: str) -> str:
|
||||||
|
"""
|
||||||
|
Deletes an vm
|
||||||
|
"""
|
||||||
|
if self.version[0] == '4':
|
||||||
|
return self.VMAction(vmId, 'delete')
|
||||||
|
|
||||||
|
# Version 5
|
||||||
|
return self.VMAction(vmId, 'terminate-hard')
|
||||||
|
|
||||||
|
@ensureConnected
|
||||||
|
def getVMState(self, vmId: str) -> types.VmState:
|
||||||
|
"""
|
||||||
|
Returns the VM State
|
||||||
|
"""
|
||||||
|
return self.VMInfo(vmId).state
|
||||||
|
|
||||||
|
@ensureConnected
|
||||||
|
def getVMSubstate(self, vmId: str) -> int:
|
||||||
|
"""
|
||||||
|
Returns the VM State
|
||||||
|
"""
|
||||||
|
result = self.connection.one.vm.info(self.sessionString, int(vmId))
|
||||||
|
r, _ = checkResult(result)
|
||||||
|
try:
|
||||||
|
if int(r['VM']['STATE']) == types.VmState.ACTIVE.value:
|
||||||
|
return int(r['VM']['LCM_STATE'])
|
||||||
|
# Substate is not available if VM state is not active
|
||||||
|
return -1
|
||||||
|
except Exception:
|
||||||
|
logger.exception('getVMSubstate')
|
||||||
|
return -1
|
||||||
|
|
||||||
|
@ensureConnected
|
||||||
|
def VMAction(self, vmId: str, action: str) -> str:
|
||||||
|
result = self.connection.one.vm.action(self.sessionString, action, int(vmId))
|
||||||
|
return checkResultRaw(result)
|
@ -32,7 +32,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# import sys
|
# import sys
|
||||||
import types
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@ -40,16 +39,6 @@ import logging
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# module = sys.modules[__name__]
|
# module = sys.modules[__name__]
|
||||||
VmState = types.ModuleType('VmState')
|
|
||||||
ImageState = types.ModuleType('ImageState')
|
|
||||||
|
|
||||||
for i in enumerate(['INIT', 'PENDING', 'HOLD', 'ACTIVE', 'STOPPED', 'SUSPENDED', 'DONE', 'FAILED', 'POWEROFF', 'UNDEPLOYED', 'UNKNOWN']):
|
|
||||||
setattr(VmState, i[1], i[0])
|
|
||||||
|
|
||||||
for i in enumerate(['INIT', 'READY', 'USED', 'DISABLED', 'LOCKED', 'ERROR', 'CLONE', 'DELETE', 'USED_PERS', 'LOCKED_USED', 'LOCKED_USED_PERS']):
|
|
||||||
setattr(ImageState, i[1], i[0])
|
|
||||||
|
|
||||||
|
|
||||||
def sanitizeName(name):
|
def sanitizeName(name):
|
||||||
"""
|
"""
|
||||||
machine names with [a-zA-Z0-9_-]
|
machine names with [a-zA-Z0-9_-]
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012-2019 Virtual Cable S.L.
|
# Copyright (c) 2012-2019 Virtual Cable S.L.
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
@ -30,14 +29,18 @@
|
|||||||
"""
|
"""
|
||||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import typing
|
||||||
|
|
||||||
|
# Not imported at runtime, just for type checking
|
||||||
|
if typing.TYPE_CHECKING:
|
||||||
|
from . import client
|
||||||
|
from . import types
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def enumerateDatastores(api, datastoreType=0):
|
def enumerateDatastores(api: 'client.OpenNebulaClient', datastoreType: int = 0) -> typing.Iterable['types.StorageType']:
|
||||||
"""
|
"""
|
||||||
0 seems to be images datastore
|
0 seems to be images datastore
|
||||||
"""
|
"""
|
||||||
return api.enumStorage(datastoreType)
|
yield from api.enumStorage(datastoreType)
|
||||||
|
@ -32,22 +32,26 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import typing
|
||||||
|
|
||||||
from defusedxml import minidom
|
from defusedxml import minidom
|
||||||
# Python bindings for OpenNebula
|
|
||||||
|
from . import types
|
||||||
from .common import sanitizeName
|
from .common import sanitizeName
|
||||||
|
|
||||||
|
# Not imported at runtime, just for type checking
|
||||||
|
if typing.TYPE_CHECKING:
|
||||||
|
from . import client
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def getTemplates(api, force=False):
|
def getTemplates(api: 'client.OpenNebulaClient', force: bool = False) -> typing.Iterable[types.TemplateType]:
|
||||||
|
|
||||||
for t in api.enumTemplates():
|
for t in api.enumTemplates():
|
||||||
if t[1][:4] != 'UDSP': # 0 = id, 1 = name
|
if t.name[:4] != 'UDSP':
|
||||||
yield t
|
yield t
|
||||||
|
|
||||||
|
|
||||||
def create(api, fromTemplateId, name, toDataStore):
|
def create(api: 'client.OpenNebulaClient', fromTemplateId: str, name: str, toDataStore: str) -> str:
|
||||||
"""
|
"""
|
||||||
Publish the machine (makes a template from it so we can create COWs) and returns the template id of
|
Publish the machine (makes a template from it so we can create COWs) and returns the template id of
|
||||||
the creating machine
|
the creating machine
|
||||||
@ -69,15 +73,13 @@ def create(api, fromTemplateId, name, toDataStore):
|
|||||||
templateId = api.cloneTemplate(fromTemplateId, name)
|
templateId = api.cloneTemplate(fromTemplateId, name)
|
||||||
|
|
||||||
# Now copy cloned images if possible
|
# Now copy cloned images if possible
|
||||||
imgs = dict(((i[1], i[0]) for i in api.enumImages()))
|
imgs = {i.name: i.id for i in api.enumImages()}
|
||||||
|
|
||||||
info = api.templateInfo(templateId)[1]
|
info = api.templateInfo(templateId).xml
|
||||||
template = minidom.parseString(info).getElementsByTagName('TEMPLATE')[0]
|
template = minidom.parseString(info).getElementsByTagName('TEMPLATE')[0]
|
||||||
logger.debug('XML: %s', template.toxml())
|
logger.debug('XML: %s', template.toxml())
|
||||||
|
|
||||||
counter = 0
|
for counter, dsk in enumerate(template.getElementsByTagName('DISK')):
|
||||||
for dsk in template.getElementsByTagName('DISK'):
|
|
||||||
counter += 1
|
|
||||||
imgIds = dsk.getElementsByTagName('IMAGE_ID')
|
imgIds = dsk.getElementsByTagName('IMAGE_ID')
|
||||||
if not imgIds:
|
if not imgIds:
|
||||||
fromId = False
|
fromId = False
|
||||||
@ -114,7 +116,7 @@ def create(api, fromTemplateId, name, toDataStore):
|
|||||||
# api.call('template.update', templateId, template.toxml())
|
# api.call('template.update', templateId, template.toxml())
|
||||||
api.updateTemplate(templateId, template.toxml())
|
api.updateTemplate(templateId, template.toxml())
|
||||||
|
|
||||||
return str(templateId)
|
return templateId
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception('Creating template on OpenNebula')
|
logger.exception('Creating template on OpenNebula')
|
||||||
try:
|
try:
|
||||||
@ -124,7 +126,7 @@ def create(api, fromTemplateId, name, toDataStore):
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
def remove(api, templateId):
|
def remove(api: 'client.OpenNebulaClient', templateId: str) -> None:
|
||||||
"""
|
"""
|
||||||
Removes a template from ovirt server
|
Removes a template from ovirt server
|
||||||
|
|
||||||
@ -134,9 +136,9 @@ def remove(api, templateId):
|
|||||||
# First, remove Images (wont be possible if there is any images already in use, but will try)
|
# First, remove Images (wont be possible if there is any images already in use, but will try)
|
||||||
# Now copy cloned images if possible
|
# Now copy cloned images if possible
|
||||||
try:
|
try:
|
||||||
imgs = dict(((i[1], i[0]) for i in api.enumImages()))
|
imgs = {i.name: i.id for i in api.enumImages()}
|
||||||
|
|
||||||
info = api.templateInfo(templateId)[1]
|
info = api.templateInfo(templateId).xml
|
||||||
template = minidom.parseString(info).getElementsByTagName('TEMPLATE')[0]
|
template = minidom.parseString(info).getElementsByTagName('TEMPLATE')[0]
|
||||||
logger.debug('XML: %s', template.toxml())
|
logger.debug('XML: %s', template.toxml())
|
||||||
|
|
||||||
@ -163,7 +165,7 @@ def remove(api, templateId):
|
|||||||
except Exception:
|
except Exception:
|
||||||
logger.error('Removing template on OpenNebula')
|
logger.error('Removing template on OpenNebula')
|
||||||
|
|
||||||
def deployFrom(api, templateId, name):
|
def deployFrom(api: 'client.OpenNebulaClient', templateId: str, name: str) -> str:
|
||||||
"""
|
"""
|
||||||
Deploys a virtual machine on selected cluster from selected template
|
Deploys a virtual machine on selected cluster from selected template
|
||||||
|
|
||||||
@ -176,16 +178,16 @@ def deployFrom(api, templateId, name):
|
|||||||
Id of the machine being created form template
|
Id of the machine being created form template
|
||||||
"""
|
"""
|
||||||
vmId = api.instantiateTemplate(templateId, name, False, '', False) # api.call('template.instantiate', int(templateId), name, False, '')
|
vmId = api.instantiateTemplate(templateId, name, False, '', False) # api.call('template.instantiate', int(templateId), name, False, '')
|
||||||
return str(vmId)
|
return vmId
|
||||||
|
|
||||||
def checkPublished(api, templateId):
|
def checkPublished(api: 'client.OpenNebulaClient', templateId):
|
||||||
"""
|
"""
|
||||||
checks if the template is fully published (images are ready...)
|
checks if the template is fully published (images are ready...)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
imgs = dict(((i[1], i[0]) for i in api.enumImages()))
|
imgs = {i.name: i.id for i in api.enumImages()}
|
||||||
|
|
||||||
info = api.templateInfo(templateId)[1]
|
info = api.templateInfo(templateId).xml
|
||||||
template = minidom.parseString(info).getElementsByTagName('TEMPLATE')[0]
|
template = minidom.parseString(info).getElementsByTagName('TEMPLATE')[0]
|
||||||
logger.debug('XML: %s', template.toxml())
|
logger.debug('XML: %s', template.toxml())
|
||||||
|
|
||||||
@ -203,12 +205,13 @@ def checkPublished(api, templateId):
|
|||||||
|
|
||||||
logger.debug('Found %s for checking', imgId)
|
logger.debug('Found %s for checking', imgId)
|
||||||
|
|
||||||
state = api.imageInfo(imgId)[0]['IMAGE']['STATE']
|
state = api.imageInfo(imgId).state
|
||||||
if state in ('0', '4'):
|
if state in (types.ImageState.INIT, types.ImageState.LOCKED):
|
||||||
return False
|
return False
|
||||||
if state != '1': # If error is not READY
|
if state != types.ImageState.READY: # If error is not READY
|
||||||
raise Exception('Error publishing. Image is in an invalid state. (Check it and delete it if not needed anymore)')
|
raise Exception('Error publishing. Image is in an invalid state. (Check it and delete it if not needed anymore)')
|
||||||
# Ensure image is non persistent. This may be invoked more than once, but idoes not matters
|
|
||||||
|
# Ensure image is non persistent. This may be invoked more than once, but it does not matters
|
||||||
api.makePersistentImage(imgId, False)
|
api.makePersistentImage(imgId, False)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
|
110
server/src/uds/services/OpenNebula/on/types.py
Normal file
110
server/src/uds/services/OpenNebula/on/types.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012-2019 Virtual Cable S.L.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
# are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
|
"""
|
||||||
|
import enum
|
||||||
|
import typing
|
||||||
|
|
||||||
|
class VmState(enum.Enum): # pylint: disable=too-few-public-methods
|
||||||
|
INIT = 0
|
||||||
|
PENDING = 1
|
||||||
|
HOLD = 2
|
||||||
|
ACTIVE = 3
|
||||||
|
STOPPED = 4
|
||||||
|
SUSPENDED = 5
|
||||||
|
DONE = 6
|
||||||
|
FAILED = 7
|
||||||
|
POWEROFF = 8
|
||||||
|
UNDEPLOYED = 9
|
||||||
|
|
||||||
|
UNKNOWN = 99
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fromState(state: str) -> 'VmState':
|
||||||
|
try:
|
||||||
|
return VmState(int(state))
|
||||||
|
except Exception:
|
||||||
|
return VmState.UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
class ImageState(enum.Enum): # pylint: disable=too-few-public-methods
|
||||||
|
INIT = 0
|
||||||
|
READY = 1
|
||||||
|
USED = 2
|
||||||
|
DISABLED = 3
|
||||||
|
LOCKED = 4
|
||||||
|
ERROR = 5
|
||||||
|
CLONE = 6
|
||||||
|
DELETE = 7
|
||||||
|
USED_PERS = 8
|
||||||
|
LOCKED_USED = 9
|
||||||
|
LOCKED_USED_PERS = 10
|
||||||
|
|
||||||
|
UNKNOWN = 99
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fromState(state: str) -> 'ImageState':
|
||||||
|
try:
|
||||||
|
return ImageState(int(state))
|
||||||
|
except Exception:
|
||||||
|
return ImageState.UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
class StorageType(typing.NamedTuple):
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
total: int # In Megabytes
|
||||||
|
free: int # In Megabytes
|
||||||
|
xml: typing.Optional[str]
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateType(typing.NamedTuple):
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
memory: int
|
||||||
|
xml: typing.Optional[str]
|
||||||
|
|
||||||
|
|
||||||
|
class ImageType(typing.NamedTuple):
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
size: int # In Megabytes
|
||||||
|
persistent: bool
|
||||||
|
running_vms: int
|
||||||
|
state: ImageState
|
||||||
|
xml: typing.Optional[str]
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualMachineType(typing.NamedTuple):
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
memory: int
|
||||||
|
state: VmState
|
||||||
|
xml: typing.Optional[str]
|
@ -30,19 +30,21 @@
|
|||||||
'''
|
'''
|
||||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
# import oca
|
import typing
|
||||||
|
|
||||||
from defusedxml import minidom
|
from defusedxml import minidom
|
||||||
# Python bindings for OpenNebula
|
|
||||||
from .common import VmState
|
|
||||||
|
|
||||||
|
from . import types
|
||||||
|
|
||||||
|
# Not imported at runtime, just for type checking
|
||||||
|
if typing.TYPE_CHECKING:
|
||||||
|
from . import client
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def getMachineState(api, machineId):
|
def getMachineState(api: 'client.OpenNebulaClient', machineId: str) -> types.VmState:
|
||||||
'''
|
'''
|
||||||
Returns the state of the machine
|
Returns the state of the machine
|
||||||
This method do not uses cache at all (it always tries to get machine state from OpenNebula server)
|
This method do not uses cache at all (it always tries to get machine state from OpenNebula server)
|
||||||
@ -54,17 +56,14 @@ def getMachineState(api, machineId):
|
|||||||
one of the on.VmState Values
|
one of the on.VmState Values
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
# vm = oca.VirtualMachine.new_with_id(api, int(machineId))
|
|
||||||
# vm.info()
|
|
||||||
# return vm.state
|
|
||||||
return api.getVMState(machineId)
|
return api.getVMState(machineId)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Error obtaining machine state for %s on OpenNebula: %s', machineId, e)
|
logger.error('Error obtaining machine state for %s on OpenNebula: %s', machineId, e)
|
||||||
|
|
||||||
return VmState.UNKNOWN
|
return types.VmState.UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
def getMachineSubstate(api, machineId):
|
def getMachineSubstate(api: 'client.OpenNebulaClient', machineId: str) -> int:
|
||||||
'''
|
'''
|
||||||
Returns the lcm_state
|
Returns the lcm_state
|
||||||
'''
|
'''
|
||||||
@ -73,10 +72,10 @@ def getMachineSubstate(api, machineId):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Error obtaining machine substate for %s on OpenNebula: %s', machineId, e)
|
logger.error('Error obtaining machine substate for %s on OpenNebula: %s', machineId, e)
|
||||||
|
|
||||||
return VmState.UNKNOWN
|
return types.VmState.UNKNOWN.value
|
||||||
|
|
||||||
|
|
||||||
def startMachine(api, machineId):
|
def startMachine(api: 'client.OpenNebulaClient', machineId: str) -> None:
|
||||||
'''
|
'''
|
||||||
Tries to start a machine. No check is done, it is simply requested to OpenNebula.
|
Tries to start a machine. No check is done, it is simply requested to OpenNebula.
|
||||||
|
|
||||||
@ -94,7 +93,7 @@ def startMachine(api, machineId):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def stopMachine(api, machineId):
|
def stopMachine(api: 'client.OpenNebulaClient', machineId: str) -> None:
|
||||||
'''
|
'''
|
||||||
Tries to start a machine. No check is done, it is simply requested to OpenNebula
|
Tries to start a machine. No check is done, it is simply requested to OpenNebula
|
||||||
|
|
||||||
@ -109,7 +108,7 @@ def stopMachine(api, machineId):
|
|||||||
logger.error('Error powering off %s on OpenNebula: %s', machineId, e)
|
logger.error('Error powering off %s on OpenNebula: %s', machineId, e)
|
||||||
|
|
||||||
|
|
||||||
def suspendMachine(api, machineId):
|
def suspendMachine(api: 'client.OpenNebulaClient', machineId: str) -> None:
|
||||||
'''
|
'''
|
||||||
Tries to suspend a machine. No check is done, it is simply requested to OpenNebula
|
Tries to suspend a machine. No check is done, it is simply requested to OpenNebula
|
||||||
|
|
||||||
@ -124,7 +123,7 @@ def suspendMachine(api, machineId):
|
|||||||
logger.error('Error suspending %s on OpenNebula: %s', machineId, e)
|
logger.error('Error suspending %s on OpenNebula: %s', machineId, e)
|
||||||
|
|
||||||
|
|
||||||
def resetMachine(api, machineId):
|
def resetMachine(api: 'client.OpenNebulaClient', machineId: str) -> None:
|
||||||
'''
|
'''
|
||||||
Tries to suspend a machine. No check is done, it is simply requested to OpenNebula
|
Tries to suspend a machine. No check is done, it is simply requested to OpenNebula
|
||||||
|
|
||||||
@ -139,7 +138,7 @@ def resetMachine(api, machineId):
|
|||||||
logger.error('Error reseting %s on OpenNebula: %s', machineId, e)
|
logger.error('Error reseting %s on OpenNebula: %s', machineId, e)
|
||||||
|
|
||||||
|
|
||||||
def removeMachine(api, machineId):
|
def removeMachine(api: 'client.OpenNebulaClient', machineId: str) -> None:
|
||||||
'''
|
'''
|
||||||
Tries to delete a machine. No check is done, it is simply requested to OpenNebula
|
Tries to delete a machine. No check is done, it is simply requested to OpenNebula
|
||||||
|
|
||||||
@ -158,7 +157,7 @@ def removeMachine(api, machineId):
|
|||||||
raise Exception(err)
|
raise Exception(err)
|
||||||
|
|
||||||
|
|
||||||
def enumerateMachines(api):
|
def enumerateMachines(api: 'client.OpenNebulaClient') -> typing.Iterable[types.VirtualMachineType]:
|
||||||
'''
|
'''
|
||||||
Obtains the list of machines inside OpenNebula.
|
Obtains the list of machines inside OpenNebula.
|
||||||
Machines starting with UDS are filtered out
|
Machines starting with UDS are filtered out
|
||||||
@ -173,7 +172,7 @@ def enumerateMachines(api):
|
|||||||
'id'
|
'id'
|
||||||
'cluster_id'
|
'cluster_id'
|
||||||
'''
|
'''
|
||||||
return api.enumVMs()
|
yield from api.enumVMs()
|
||||||
|
|
||||||
|
|
||||||
def getNetInfo(api, machineId, networkId=None):
|
def getNetInfo(api, machineId, networkId=None):
|
||||||
|
Loading…
Reference in New Issue
Block a user