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
|
||||
'''
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from django.utils.translation import ugettext_noop as _
|
||||
from uds.core.services import ServiceProvider
|
||||
@ -43,7 +44,7 @@ from . import on
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Provider(ServiceProvider):
|
||||
class Provider(ServiceProvider): # pylint: disable=too-many-public-methods
|
||||
'''
|
||||
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)
|
||||
|
||||
# Own variables
|
||||
_api = None
|
||||
_api: typing.Optional[on.client.OpenNebulaClient] = None
|
||||
|
||||
def initialize(self, values=None):
|
||||
'''
|
||||
@ -106,18 +107,18 @@ class Provider(ServiceProvider):
|
||||
# Just reset _api connection variable
|
||||
self._api = None
|
||||
|
||||
if values is not None:
|
||||
if values:
|
||||
self.timeout.value = validators.validateTimeout(self.timeout.value)
|
||||
logger.debug('Endpoint: %s', self.endpoint)
|
||||
|
||||
@property
|
||||
def endpoint(self):
|
||||
def endpoint(self) -> str:
|
||||
return 'http{}://{}:{}/RPC2'.format('s' if self.ssl.isTrue() else '', self.host.value, self.port.value)
|
||||
|
||||
@property
|
||||
def api(self):
|
||||
def api(self) -> on.client.OpenNebulaClient:
|
||||
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)
|
||||
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 template
|
||||
from . import vm
|
||||
from . import client
|
||||
|
||||
# Import submodules
|
||||
# Import common
|
||||
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 types
|
||||
import re
|
||||
|
||||
import logging
|
||||
@ -40,16 +39,6 @@ import logging
|
||||
logger = logging.getLogger(__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):
|
||||
"""
|
||||
machine names with [a-zA-Z0-9_-]
|
||||
|
@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
@ -30,14 +29,18 @@
|
||||
"""
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
|
||||
import logging
|
||||
import typing
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import client
|
||||
from . import types
|
||||
|
||||
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
|
||||
"""
|
||||
return api.enumStorage(datastoreType)
|
||||
yield from api.enumStorage(datastoreType)
|
||||
|
@ -32,22 +32,26 @@
|
||||
"""
|
||||
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from defusedxml import minidom
|
||||
# Python bindings for OpenNebula
|
||||
|
||||
from . import types
|
||||
from .common import sanitizeName
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import client
|
||||
|
||||
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():
|
||||
if t[1][:4] != 'UDSP': # 0 = id, 1 = name
|
||||
if t.name[:4] != 'UDSP':
|
||||
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
|
||||
the creating machine
|
||||
@ -69,15 +73,13 @@ def create(api, fromTemplateId, name, toDataStore):
|
||||
templateId = api.cloneTemplate(fromTemplateId, name)
|
||||
|
||||
# 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]
|
||||
logger.debug('XML: %s', template.toxml())
|
||||
|
||||
counter = 0
|
||||
for dsk in template.getElementsByTagName('DISK'):
|
||||
counter += 1
|
||||
for counter, dsk in enumerate(template.getElementsByTagName('DISK')):
|
||||
imgIds = dsk.getElementsByTagName('IMAGE_ID')
|
||||
if not imgIds:
|
||||
fromId = False
|
||||
@ -114,7 +116,7 @@ def create(api, fromTemplateId, name, toDataStore):
|
||||
# api.call('template.update', templateId, template.toxml())
|
||||
api.updateTemplate(templateId, template.toxml())
|
||||
|
||||
return str(templateId)
|
||||
return templateId
|
||||
except Exception as e:
|
||||
logger.exception('Creating template on OpenNebula')
|
||||
try:
|
||||
@ -124,7 +126,7 @@ def create(api, fromTemplateId, name, toDataStore):
|
||||
raise e
|
||||
|
||||
|
||||
def remove(api, templateId):
|
||||
def remove(api: 'client.OpenNebulaClient', templateId: str) -> None:
|
||||
"""
|
||||
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)
|
||||
# Now copy cloned images if possible
|
||||
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]
|
||||
logger.debug('XML: %s', template.toxml())
|
||||
|
||||
@ -163,7 +165,7 @@ def remove(api, templateId):
|
||||
except Exception:
|
||||
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
|
||||
|
||||
@ -176,16 +178,16 @@ def deployFrom(api, templateId, name):
|
||||
Id of the machine being created form template
|
||||
"""
|
||||
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...)
|
||||
"""
|
||||
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]
|
||||
logger.debug('XML: %s', template.toxml())
|
||||
|
||||
@ -203,12 +205,13 @@ def checkPublished(api, templateId):
|
||||
|
||||
logger.debug('Found %s for checking', imgId)
|
||||
|
||||
state = api.imageInfo(imgId)[0]['IMAGE']['STATE']
|
||||
if state in ('0', '4'):
|
||||
state = api.imageInfo(imgId).state
|
||||
if state in (types.ImageState.INIT, types.ImageState.LOCKED):
|
||||
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)')
|
||||
# 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)
|
||||
|
||||
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
|
||||
'''
|
||||
|
||||
import logging
|
||||
# import oca
|
||||
import typing
|
||||
|
||||
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__)
|
||||
|
||||
|
||||
def getMachineState(api, machineId):
|
||||
def getMachineState(api: 'client.OpenNebulaClient', machineId: str) -> types.VmState:
|
||||
'''
|
||||
Returns the state of the machine
|
||||
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
|
||||
'''
|
||||
try:
|
||||
# vm = oca.VirtualMachine.new_with_id(api, int(machineId))
|
||||
# vm.info()
|
||||
# return vm.state
|
||||
return api.getVMState(machineId)
|
||||
except Exception as 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
|
||||
'''
|
||||
@ -73,10 +72,10 @@ def getMachineSubstate(api, machineId):
|
||||
except Exception as 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.
|
||||
|
||||
@ -94,7 +93,7 @@ def startMachine(api, machineId):
|
||||
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
|
||||
|
||||
@ -109,7 +108,7 @@ def stopMachine(api, machineId):
|
||||
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
|
||||
|
||||
@ -124,7 +123,7 @@ def suspendMachine(api, machineId):
|
||||
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
|
||||
|
||||
@ -139,7 +138,7 @@ def resetMachine(api, machineId):
|
||||
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
|
||||
|
||||
@ -158,7 +157,7 @@ def removeMachine(api, machineId):
|
||||
raise Exception(err)
|
||||
|
||||
|
||||
def enumerateMachines(api):
|
||||
def enumerateMachines(api: 'client.OpenNebulaClient') -> typing.Iterable[types.VirtualMachineType]:
|
||||
'''
|
||||
Obtains the list of machines inside OpenNebula.
|
||||
Machines starting with UDS are filtered out
|
||||
@ -173,7 +172,7 @@ def enumerateMachines(api):
|
||||
'id'
|
||||
'cluster_id'
|
||||
'''
|
||||
return api.enumVMs()
|
||||
yield from api.enumVMs()
|
||||
|
||||
|
||||
def getNetInfo(api, machineId, networkId=None):
|
||||
|
Loading…
Reference in New Issue
Block a user