forked from shaba/openuds
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:
parent
1b03851ace
commit
c8f8ee054c
@ -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'),
|
||||
|
@ -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")]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
0
server/src/uds/services/OVirt/client/__init__.py
Normal file
0
server/src/uds/services/OVirt/client/__init__.py
Normal file
196
server/src/uds/services/OVirt/client/oVirtClient.py
Normal file
196
server/src/uds/services/OVirt/client/oVirtClient.py
Normal 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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user