diff --git a/server/src/uds/services/OVirt/OVirtLinkedService.py b/server/src/uds/services/OVirt/OVirtLinkedService.py index 00046b75..b5a9cb83 100644 --- a/server/src/uds/services/OVirt/OVirtLinkedService.py +++ b/server/src/uds/services/OVirt/OVirtLinkedService.py @@ -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'), diff --git a/server/src/uds/services/OVirt/OVirtProvider.py b/server/src/uds/services/OVirt/OVirtProvider.py index 9ab2befd..eabd5d3c 100644 --- a/server/src/uds/services/OVirt/OVirtProvider.py +++ b/server/src/uds/services/OVirt/OVirtProvider.py @@ -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")] + + + + diff --git a/server/src/uds/services/OVirt/client/__init__.py b/server/src/uds/services/OVirt/client/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/src/uds/services/OVirt/client/oVirtClient.py b/server/src/uds/services/OVirt/client/oVirtClient.py new file mode 100644 index 00000000..819ec49b --- /dev/null +++ b/server/src/uds/services/OVirt/client/oVirtClient.py @@ -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() +