Advancend in oVirt connectivity. Has found some problems (such as ovirtsdk doesn't allow, right now, have more than one simultaneous connection to an ovirt server), and i'm solving it "serializing" access to ovirt servers... Hopefully in a near future, ovirtsdk will allow access to more than one server and this will be unnecesary.

This commit is contained in:
Adolfo Gómez 2012-11-14 11:27:00 +00:00
parent 1b03851ace
commit c8f8ee054c
4 changed files with 240 additions and 75 deletions

View File

@ -44,34 +44,18 @@ logger = logging.getLogger(__name__)
class OVirtLinkedService(Service):
'''
Basic service, the first part (variables) include the description of the service.
oVirt Linked clones service. This is based on creating a template from selected vm, and then use it to
Remember to fill all variables needed, but at least you must define:
* typeName
* typeType
* typeDescription
* iconFile (defaults to service.png)
* publicationType, type of publication in case it needs publication.
If this is not provided, core will assume that the service do not
needs publishing.
* deployedType, type of deployed user service. Do not forget this!!!
The rest of them can be ommited, but its recommended that you fill all
declarations shown in this sample (that in fact, are all)
This description informs the core what this service really provides,
and how this is done. Look at description of class variables for more
information.
'''
#: Name to show the administrator. This string will be translated BEFORE
#: sending it to administration interface, so don't forget to
#: mark it as translatable (using ugettext_noop)
typeName = translatable('Sample Service One')
typeName = translatable('oVirt Linked Clone')
#: Type used internally to identify this provider
typeType = 'SampleService1'
typeType = 'oVirtLinkedService'
#: Description shown at administration interface for this provider
typeDescription = translatable('Sample (and dummy) service ONE')
typeDescription = translatable('oVirt Services based on templates and COW')
#: Icon file used as icon for this provider. This string will be translated
#: BEFORE sending it to administration interface, so don't forget to
#: mark it as translatable (using ugettext_noop)
@ -85,27 +69,27 @@ class OVirtLinkedService(Service):
#: If we need to generate "cache" for this service, so users can access the
#: provided services faster. Is usesCache is True, you will need also
#: set publicationType, do take care about that!
usesCache = False
usesCache = True
#: Tooltip shown to user when this item is pointed at admin interface, none
#: because we don't use it
cacheTooltip = translatable('None')
cacheTooltip = translatable('Number of desired machines to keep running waiting for a user')
#: If we need to generate a "Level 2" cache for this service (i.e., L1
#: could be running machines and L2 suspended machines)
usesCache_L2 = False
#: Tooltip shown to user when this item is pointed at admin interface, None
#: also because we don't use it
cacheTooltip_L2 = translatable('None')
cacheTooltip_L2 = translatable('Number of desired machines to keep suspended waiting for use')
#: If the service needs a s.o. manager (managers are related to agents
#: provided by services itselfs, i.e. virtual machines with actors)
needsManager = False
needsManager = True
#: If true, the system can't do an automatic assignation of a deployed user
#: service from this service
mustAssignManually = False
#: Types of publications (preparated data for deploys)
#: In our case, we do no need a publication, so this is None
publicationType = None
publicationType = OVirtPublication
#: Types of deploys (services in cache and/or assigned to users)
deployedType = OVirtLinkedDeployment
@ -113,25 +97,9 @@ class OVirtLinkedService(Service):
# If we don't indicate an order, the output order of fields will be
# "random"
colour = gui.ChoiceField(order = 1,
label = translatable('Colour'),
tooltip = translatable('Colour of the field'),
# In this case, the choice can have none value selected by default
required = True,
values = [ gui.choiceItem('red', 'Red'),
gui.choiceItem('green', 'Green'),
gui.choiceItem('blue', 'Blue'),
gui.choiceItem('nonsense', 'Blagenta')
],
defvalue = '1' # Default value is the ID of the choicefield
)
machine = gui.ChoiceField(label = _("Base Machine"), order = 6, tooltip = _('Base machine for this service'), required = True )
passw = gui.PasswordField(order = 2,
label = translatable('Password'),
tooltip = translatable('Password for testing purposes'),
required = True,
defvalue = '1234' #: Default password are nonsense?? :-)
)
baseName = gui.TextField(order = 3,
label = translatable('Services names'),

View File

@ -38,10 +38,13 @@ from uds.core.services import ServiceProvider
from OVirtLinkedService import OVirtLinkedService
from uds.core.ui import gui
from client import oVirtClient
import logging
logger = logging.getLogger(__name__)
CACHE_TIME_FOR_SERVER = 1800
class Provider(ServiceProvider):
'''
@ -85,36 +88,39 @@ class Provider(ServiceProvider):
# If we don't indicate an order, the output order of fields will be
# "random"
host = gui.TextField(length=64, label = _('Host'), order = 1, tooltip = _('oVirt Server IP or Hostname'), required = True)
port = gui.NumericField(length=5, label = _('Port'), defvalue = '443', order = 2, tooltip = _('VMWare VC Server Port (usually 443)'), required = True)
username = gui.TextField(length=32, label = _('Username'), order = 3, tooltip = _('User with valid privileges on VC'), required = True)
password = gui.PasswordField(lenth=32, label = _('Password'), order = 4, tooltip = _('Password of the user of the VC'), required = True)
username = gui.TextField(length=32, label = _('Username'), order = 3, tooltip = _('User with valid privileges on oVirt, (use "user@domain" form'), required = True, defvalue='admin@internal')
password = gui.PasswordField(lenth=32, label = _('Password'), order = 4, tooltip = _('Password of the user of oVirt'), required = True)
timeout = gui.NumericField(length=3, label = _('Timeout'), defvalue = '10', order = 5, tooltip = _('Timeout in seconds of connection to VC'), required = True)
macsRange = gui.TextField(length=36, label = _('Macs range'), defvalue = '00:50:56:00:00:00-00:50:56:3F:FF:FF', order = 6, rdonly = True,
macsRange = gui.TextField(length=36, label = _('Macs range'), defvalue = '52:54:00:00:00:00-52:54:00:FF:FF:FF', order = 6, rdonly = True,
tooltip = _('Range of valids macs for created machines'), required = True)
# oVirt engine, right now, only permits a connection to one server and only one per instance
# If we want to connect to more than one server, we need keep locked access to api, change api server, etc..
# We have implemented an "exclusive access" client that will only connect to one server at a time (using locks)
# and this way all will be fine
def __getApi(self):
'''
Returns the connection API object for oVirt (using ovirtsdk)
'''
if self._api is None:
self._api = oVirtClient.Client(self.host.value, self.username.value, self.password.value, self.timeout.value, self.cache())
return self._api
# There is more fields type, but not here the best place to cover it
def initialize(self, values = None):
'''
We will use the "autosave" feature for form fields, that is more than
enought for most providers. (We simply need to store data provided by user
and, maybe, initialize some kind of connection with this values).
Normally provider values are rally used at sevice level, cause we never
instantiate nothing except a service from a provider.
We will use the "autosave" feature for form fields
'''
# If you say meth is alive, you are wrong!!! (i guess..)
# values are only passed from administration client. Internals
# instantiations are always empty.
#if values is not None and self.methAlive.isTrue():
# raise ServiceProvider.ValidationException(_('Methuselah is not alive!!! :-)'))
# Marshal and unmarshal are defaults ones, also enought
# Just reset _api connection variable
self._api = None
def testConnection(self):
api = self.__getApi()
return api.test()
# As we use "autosave" fields feature, dictValues is also provided by
# base class so we don't have to mess with all those things...
@staticmethod
def test(env, data):
@ -150,17 +156,12 @@ class Provider(ServiceProvider):
# logger.exception("Exception caugth!!!")
# return [False, str(e)]
#return [True, _('Nothing tested, but all went fine..')]
return [True, _('Connection test successful')]
# Congratulations!!!, the needed part of your first simple provider is done!
# Now you can go to administration panel, and check it
#
# From now onwards, we implement our own methods, that will be used by,
# for example, services derived from this provider
def host(self):
'''
Sample method, in fact in this we just return
the value of host field, that is an string
'''
return self.remoteHost.value
ov = Provider(env, data)
if ov.testConnection() is True:
return [True, _('Connection test successful')]
return [False, _("Connection failed. Check connection params")]

View File

@ -0,0 +1,196 @@
'''
Created on Nov 14, 2012
@author: dkmaster
'''
from ovirtsdk.xml import params
from ovirtsdk.api import API
import threading
import logging
logger = logging.getLogger(__name__)
lock = threading.Lock()
cached_api = None
cached_api_key = None
class Client(object):
'''
Module to manage oVirt connections using ovirtsdk.
Due to the fact that we can't create two proxy connections at same time, we serialize all access to ovirt platform.
Only one request and one live connection can exists at a time.
This can waste a lot of time, so use of cache here is more than important to achieve aceptable performance.
'''
CACHE_TIME_LOW = 60*5 # Cache time for requests are 5 minutes by default
CACHE_TIME_HIGH = 60*30 # Cache time for requests that are less probable to change (as cluster perteinance of a machine)
def __getKey(self, prefix = ''):
'''
Creates a key for the cache, using the prefix indicated as part of it
Returns:
The cache key, taking into consideration the prefix
'''
return prefix + self._host + self._username + self._password + str(self._timeout)
def __getApi(self):
'''
Gets the api connection.
Again, due to the fact that ovirtsdk don't allow (at this moment, but it's on the "TODO" list) concurrent access to
more than one server, we keep only one opened connection.
Must be acceses "locked", we can alter cached_api and cached_api_key
'''
global cached_api, cached_api_key
aKey = self.__getKey('o-host')
if cached_api_key == aKey:
return cached_api
if cached_api is not None:
try:
cached_api.disconnect()
except:
# Nothing happens, may it was already disconnected
pass
cached_api_key = aKey
cached_api = API(url='https://'+self._host, username=self._username, password=self._password, timeout=self._timeout, insecure=True, debug=False)
return cached_api
def __init__(self, host, username, password, timeout, cache):
self._host = host
self._username = username
self._password = password
self._timeout = int(timeout)
self._cache = cache
def test(self):
try:
lock.acquire(True)
return self.__getApi().test()
except Exception as e:
print e
return False
finally:
lock.release()
def getVms(self, force = False):
'''
Obtains the list of machines inside ovirt that do aren't part of uds
Args:
force: If true, force to update the cache, if false, tries to first
get data from cache and, if valid, return this.
Returns
An array of dictionaries, containing:
'name'
'id'
'cluster_id'
'''
vmsKey = self.__getKey('o-vms')
val = self._cache.get(vmsKey)
if val is not None and force is False:
return val
try:
lock.acquire(True)
api = self.__getApi()
vms = api.vms.list(query='name!=UDS*')
res = []
for vm in vms:
res.append({ 'name' : vm.get_name(), 'id' : vm.get_id(), 'cluster_id' : vm.get_cluster().get_id() })
self._cache.put(vmsKey, res, Client.CACHE_TIME_LOW)
return res
finally:
lock.release()
def getClusterInfo(self, clusterId, force = False):
'''
Obtains the cluster info
Args:
force: If true, force to update the cache, if false, tries to first
get data from cache and, if valid, return this.
Returns
A dictionary with following values
'name'
'id'
'description'
'datacenter_id'
'''
clKey = self.__getKey('o-cluster'+clusterId)
val = self._cache.get(clKey)
if val is not None and force is False:
return val
try:
lock.acquire(True)
api = self.__getApi()
c = api.clusters.get(id=clusterId)
res = { 'name' : c.get_name(), 'id' : c.get_id(), 'datacenter_id' : c.get_data_center().get_id() }
self._cache.put(clKey, res, Client.CACHE_TIME_HIGH)
return res
finally:
lock.release()
def getDatacenterInfo(self, datacenterId, force = False):
'''
Obtains the cluster info
Args:
force: If true, force to update the cache, if false, tries to first
get data from cache and, if valid, return this.
Returns
A dictionary with following values
'name'
'id'
'storage_type'
'storage_format'
'description'
'''
dcKey = self.__getKey('o-dc'+datacenterId)
val = self._cache.get(dcKey)
if val is not None and force is False:
return val
try:
lock.acquire(True)
api = self.__getApi()
d = api.datacenters.get(id=datacenterId)
res = { 'name' : d.get_name(), 'id' : d.get_id(), 'storage_type' : d.get_storage_type(),
'storage_format' : d.get_storage_format(), 'description' : d.get_description() }
self._cache.put(dcKey, res, Client.CACHE_TIME_HIGH)
finally:
lock.release()