1
0
mirror of https://github.com/dkmstr/openuds.git synced 2024-12-22 13:34:04 +03:00

* Important advances on OpenNebula service provider

* several semantic changes
* Minor improvements & fixes
This commit is contained in:
Adolfo Gómez García 2016-02-08 09:33:17 +01:00
parent d024d74529
commit de4aef3a5c
12 changed files with 1577 additions and 41 deletions

View File

@ -96,10 +96,10 @@ class OSManager(Module):
pass
# These methods must be overriden
def process(self, service, message, data, options=None):
def process(self, userService, message, data, options=None):
'''
This method must be overriden so your so manager can manage requests and responses from agent.
@param service: Service that sends the request (virtual machine or whatever)
@param userService: Service that sends the request (virtual machine or whatever)
@param message: message to process (os manager dependent)
@param data: Data for this message
'''

View File

@ -37,7 +37,7 @@ from uds.core import Module
from uds.core.transports import protocols
from . import types
__updated__ = '2015-05-28'
__updated__ = '2016-02-08'
class Service(Module):
@ -171,6 +171,10 @@ class Service(Module):
# : For example, VDI, VAPP, ...
servicesTypeProvided = types.ALL
# : If the service can provide any other option on release appart of "delete" & "keep assigned"
# : Defaults to None (no any other options are provided)
actionsOnRelease = None
def __init__(self, environment, parent, values=None):
'''
Do not forget to invoke this in your derived class using "super(self.__class__, self).__init__(environment, parent, values)".

View File

@ -55,7 +55,7 @@ from uds.models.Util import getSqlDatetime
import logging
__updated__ = '2015-11-06'
__updated__ = '2016-02-08'
logger = logging.getLogger(__name__)
@ -383,6 +383,12 @@ class UserService(UUIDModel):
self.setState(State.REMOVABLE)
self.save()
def release(self):
'''
A much more convenient method that "remove"
'''
self.remove()
def cancel(self):
'''
Asks the UserServiceManager to cancel the current operation of this user deployed service.

View File

@ -132,7 +132,7 @@ class LinuxOsManager(osmanagers.OSManager):
except Exception:
log.doLog(service, log.ERROR, "do not understand {0}".format(data), origin)
def process(self, service, msg, data, options):
def process(self, userService, msg, data, options):
'''
We understand this messages:
* msg = info, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class), old method
@ -141,47 +141,47 @@ class LinuxOsManager(osmanagers.OSManager):
* msg = logoff, data = Username, Informs that the username has logged out of the machine
* msg = ready, data = None, Informs machine ready to be used
'''
logger.info("Invoked LinuxOsManager for {0} with params: {1},{2}".format(service, msg, data))
# We get from storage the name for this service. If no name, we try to assign a new one
logger.info("Invoked LinuxOsManager for {0} with params: {1},{2}".format(userService, msg, data))
# We get from storage the name for this userService. If no name, we try to assign a new one
ret = "ok"
notifyReady = False
doRemove = False
state = service.os_state
state = userService.os_state
# Old "info" state, will be removed in a near future
if msg == "info":
ret = self.infoVal(service)
ret = self.infoVal(userService)
state = State.PREPARING
elif msg == "information":
ret = self.infoValue(service)
ret = self.infoValue(userService)
state = State.PREPARING
elif msg == "log":
self.doLog(service, data, log.ACTOR)
self.doLog(userService, data, log.ACTOR)
elif msg == "login":
self.loggedIn(service, data, False)
self.loggedIn(userService, data, False)
elif msg == "logout":
self.loggedOut(service, data, False)
self.loggedOut(userService, data, False)
if self._onLogout == 'remove':
doRemove = True
elif msg == "ip":
# This ocurss on main loop inside machine, so service is usable
# This ocurss on main loop inside machine, so userService is usable
state = State.USABLE
self.notifyIp(service.unique_id, service, data)
self.notifyIp(userService.unique_id, userService, data)
elif msg == "ready":
state = State.USABLE
notifyReady = True
self.notifyIp(service.unique_id, service, data)
self.notifyIp(userService.unique_id, userService, data)
service.setOsState(state)
userService.setOsState(state)
# If notifyReady is not true, save state, let UserServiceManager do it for us else
if doRemove is True:
service.remove()
userService.release()
else:
if notifyReady is False:
service.save()
userService.save()
else:
UserServiceManager.manager().notifyReadyFromOsManager(service, '')
UserServiceManager.manager().notifyReadyFromOsManager(userService, '')
logger.debug('Returning {0}'.format(ret))
return ret

View File

@ -138,7 +138,7 @@ class WindowsOsManager(osmanagers.OSManager):
logger.exception('WindowsOs Manager message log: ')
log.doLog(service, log.ERROR, "do not understand {0}".format(data), origin)
def process(self, service, msg, data, options):
def process(self, userService, msg, data, options):
'''
We understand this messages:
* msg = info, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class) (old method)
@ -147,51 +147,51 @@ class WindowsOsManager(osmanagers.OSManager):
* msg = logoff, data = Username, Informs that the username has logged out of the machine
* msg = ready, data = None, Informs machine ready to be used
'''
logger.info("Invoked WindowsOsManager for {0} with params: {1},{2}".format(service, msg, data))
# We get from storage the name for this service. If no name, we try to assign a new one
logger.info("Invoked WindowsOsManager for {0} with params: {1},{2}".format(userService, msg, data))
# We get from storage the name for this userService. If no name, we try to assign a new one
ret = "ok"
notifyReady = False
doRemove = False
state = service.os_state
state = userService.os_state
if msg == "info":
ret = self.infoVal(service)
ret = self.infoVal(userService)
state = State.PREPARING
elif msg == "information":
ret = self.infoValue(service)
ret = self.infoValue(userService)
state = State.PREPARING
elif msg == "log":
self.doLog(service, data, log.ACTOR)
self.doLog(userService, data, log.ACTOR)
elif msg == "logon" or msg == 'login':
if '\\' not in data:
self.loggedIn(service, data, False)
service.setInUse(True)
# We get the service logged hostname & ip and returns this
ip, hostname = service.getConnectionSource()
self.loggedIn(userService, data, False)
userService.setInUse(True)
# We get the userService logged hostname & ip and returns this
ip, hostname = userService.getConnectionSource()
ret = "{0}\t{1}".format(ip, hostname)
elif msg == "logoff" or msg == 'logout':
self.loggedOut(service, data, False)
self.loggedOut(userService, data, False)
if self._onLogout == 'remove':
doRemove = True
elif msg == "ip":
# This ocurss on main loop inside machine, so service is usable
# This ocurss on main loop inside machine, so userService is usable
state = State.USABLE
self.notifyIp(service.unique_id, service, data)
self.notifyIp(userService.unique_id, userService, data)
elif msg == "ready":
state = State.USABLE
notifyReady = True
self.notifyIp(service.unique_id, service, data)
self.notifyIp(userService.unique_id, userService, data)
service.setOsState(state)
userService.setOsState(state)
# If notifyReady is not true, save state, let UserServiceManager do it for us else
if doRemove is True:
service.remove()
userService.release()
else:
if notifyReady is False:
service.save()
userService.save()
else:
logger.debug('Notifying ready')
UserServiceManager.manager().notifyReadyFromOsManager(service, '')
UserServiceManager.manager().notifyReadyFromOsManager(userService, '')
logger.debug('Returning {} to {} message'.format(ret, msg))
if options is not None and options.get('scramble', True) is False:
return ret

View File

@ -0,0 +1,612 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 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
'''
from uds.core.services import UserDeployment
from uds.core.util.State import State
from uds.core.util import log
import pickle
import logging
__updated__ = '2016-01-28'
logger = logging.getLogger(__name__)
opCreate, opStart, opStop, opSuspend, opRemove, opWait, opError, opFinish, opRetry, opChangeMac = range(10)
NO_MORE_NAMES = 'NO-NAME-ERROR'
class LiveDeployment(UserDeployment):
'''
This class generates the user consumable elements of the service tree.
After creating at administration interface an Deployed Service, UDS will
create consumable services for users using UserDeployment class as
provider of this elements.
The logic for managing ovirt deployments (user machines in this case) is here.
'''
# : Recheck every six seconds by default (for task methods)
suggestedTime = 6
def initialize(self):
self._name = ''
self._ip = ''
self._mac = ''
self._vmid = ''
self._reason = ''
self._queue = []
# Serializable needed methods
def marshal(self):
'''
Does nothing right here, we will use envoronment storage in this sample
'''
return '\1'.join(['v1', self._name, self._ip, self._mac, self._vmid, self._reason, pickle.dumps(self._queue)])
def unmarshal(self, str_):
'''
Does nothing here also, all data are keeped at environment storage
'''
vals = str_.split('\1')
if vals[0] == 'v1':
self._name, self._ip, self._mac, self._vmid, self._reason, queue = vals[1:]
self._queue = pickle.loads(queue)
def getName(self):
'''
We override this to return a name to display. Default inplementation
(in base class), returns getUniqueIde() value
This name will help user to identify elements, and is only used
at administration interface.
We will use here the environment name provided generator to generate
a name for this element.
The namaGenerator need two params, the base name and a length for a
numeric incremental part for generating unique names. This are unique for
all UDS names generations, that is, UDS will not generate this name again
until this name is freed, or object is removed, what makes its environment
to also get removed, that makes all uniques ids (names and macs right now)
to also get released.
Every time get method of a generator gets called, the generator creates
a new unique name, so we keep the first generated name cached and don't
generate more names. (Generator are simple utility classes)
'''
if self._name == '':
try:
self._name = self.nameGenerator().get(self.service().getBaseName(), self.service().getLenName())
except KeyError:
return NO_MORE_NAMES
return self._name
def setIp(self, ip):
'''
In our case, there is no OS manager associated with this, so this method
will never get called, but we put here as sample.
Whenever an os manager actor notifies the broker the state of the service
(mainly machines), the implementation of that os manager can (an probably will)
need to notify the IP of the deployed service. Remember that UDS treats with
IP services, so will probable needed in every service that you will create.
:note: This IP is the IP of the "consumed service", so the transport can
access it.
'''
logger.debug('Setting IP to {}'.format(ip))
self._ip = ip
def getUniqueId(self):
'''
Return and unique identifier for this service.
In our case, we will generate a mac name, that can be also as sample
of 'mac' generator use, and probably will get used something like this
at some services.
The get method of a mac generator takes one param, that is the mac range
to use to get an unused mac.
'''
if self._mac == '':
self._mac = self.macGenerator().get(self.service().getMacRange())
return self._mac
def getIp(self):
'''
We need to implement this method, so we can return the IP for transports
use. If no IP is known for this service, this must return None
If our sample do not returns an IP, IP transport will never work with
this service. Remember in real cases to return a valid IP address if
the service is accesible and you alredy know that (for example, because
the IP has been assigend via setIp by an os manager) or because
you get it for some other method.
Storage returns None if key is not stored.
:note: Keeping the IP address is responsibility of the User Deployment.
Every time the core needs to provide the service to the user, or
show the IP to the administrator, this method will get called
'''
return self._ip
def setReady(self):
'''
The method is invoked whenever a machine is provided to an user, right
before presenting it (via transport rendering) to the user.
'''
if self.cache().get('ready') == '1':
return State.FINISHED
state = self.service().getMachineState(self._vmid)
if state == 'unknown':
return self.__error('Machine is not available anymore')
if state not in ('up', 'powering_up', 'restoring_state'):
self._queue = [opStart, opFinish]
return self.__executeQueue()
self.cache().put('ready', '1')
return State.FINISHED
def getConsoleConnection(self):
return self.service().getConsoleConnection(self._vmid)
def desktopLogin(self, username, password, domain=''):
return self.service().desktopLogin(self._vmId, username, password, domain)
def notifyReadyFromOsManager(self, data):
# Here we will check for suspending the VM (when full ready)
logger.debug('Checking if cache 2 for {0}'.format(self._name))
if self.__getCurrentOp() == opWait:
logger.debug('Machine is ready. Moving to level 2')
self.__popCurrentOp() # Remove current state
return self.__executeQueue()
# Do not need to go to level 2 (opWait is in fact "waiting for moving machine to cache level 2)
return State.FINISHED
def deployForUser(self, user):
'''
Deploys an service instance for an user.
'''
logger.debug('Deploying for user')
self.__initQueueForDeploy(False)
return self.__executeQueue()
def deployForCache(self, cacheLevel):
'''
Deploys an service instance for cache
'''
self.__initQueueForDeploy(cacheLevel == self.L2_CACHE)
return self.__executeQueue()
def __initQueueForDeploy(self, forLevel2=False):
if forLevel2 is False:
self._queue = [opCreate, opChangeMac, opStart, opFinish]
else:
self._queue = [opCreate, opChangeMac, opStart, opWait, opSuspend, opFinish]
def __checkMachineState(self, chkState):
logger.debug('Checking that state of machine {} ({}) is {}'.format(self._vmid, self._name, chkState))
state = self.service().getMachineState(self._vmid)
# If we want to check an state and machine does not exists (except in case that we whant to check this)
if state == 'unknown' and chkState != 'unknown':
return self.__error('Machine not found')
ret = State.RUNNING
if type(chkState) is list:
for cks in chkState:
if state == cks:
ret = State.FINISHED
break
else:
if state == chkState:
ret = State.FINISHED
return ret
def __getCurrentOp(self):
if len(self._queue) == 0:
return opFinish
return self._queue[0]
def __popCurrentOp(self):
if len(self._queue) == 0:
return opFinish
res = self._queue.pop(0)
return res
def __pushFrontOp(self, op):
self._queue.insert(0, op)
def __pushBackOp(self, op):
self._queue.append(op)
def __error(self, reason):
'''
Internal method to set object as error state
Returns:
State.ERROR, so we can do "return self.__error(reason)"
'''
logger.debug('Setting error state, reason: {0}'.format(reason))
self.doLog(log.ERROR, reason)
if self._vmid != '': # Powers off
try:
state = self.service().getMachineState(self._vmid)
if state in ('up', 'suspended'):
self.service().stopMachine(self._vmid)
except:
logger.debug('Can\t set machine state to stopped')
self._queue = [opError]
self._reason = str(reason)
return State.ERROR
def __executeQueue(self):
self.__debug('executeQueue')
op = self.__getCurrentOp()
if op == opError:
return State.ERROR
if op == opFinish:
return State.FINISHED
fncs = {
opCreate: self.__create,
opRetry: self.__retry,
opStart: self.__startMachine,
opStop: self.__stopMachine,
opSuspend: self.__suspendMachine,
opWait: self.__wait,
opRemove: self.__remove,
opChangeMac: self.__changeMac
}
try:
execFnc = fncs.get(op, None)
if execFnc is None:
return self.__error('Unknown operation found at execution queue ({0})'.format(op))
execFnc()
return State.RUNNING
except Exception as e:
return self.__error(e)
# Queue execution methods
def __retry(self):
'''
Used to retry an operation
In fact, this will not be never invoked, unless we push it twice, because
checkState method will "pop" first item when a check operation returns State.FINISHED
At executeQueue this return value will be ignored, and it will only be used at checkState
'''
return State.FINISHED
def __wait(self):
'''
Executes opWait, it simply waits something "external" to end
'''
return State.RUNNING
def __create(self):
'''
Deploys a machine from template for user/cache
'''
templateId = self.publication().getTemplateId()
name = self.getName()
if name == NO_MORE_NAMES:
raise Exception('No more names available for this service. (Increase digits for this service to fix)')
name = self.service().sanitizeVmName(name) # oVirt don't let us to create machines with more than 15 chars!!!
comments = 'UDS Linked clone'
self._vmid = self.service().deployFromTemplate(name, comments, templateId)
if self._vmid is None:
raise Exception('Can\'t create machine')
def __remove(self):
'''
Removes a machine from system
'''
state = self.service().getMachineState(self._vmid)
if state == 'unknown':
raise Exception('Machine not found')
if state != 'down':
self.__pushFrontOp(opStop)
self.__executeQueue()
else:
self.service().removeMachine(self._vmid)
def __startMachine(self):
'''
Powers on the machine
'''
state = self.service().getMachineState(self._vmid)
if state == 'unknown':
raise Exception('Machine not found')
if state == 'up': # Already started, return
return
if state != 'down' and state != 'suspended':
self.__pushFrontOp(opRetry) # Will call "check Retry", that will finish inmediatly and again call this one
else:
self.service().startMachine(self._vmid)
def __stopMachine(self):
'''
Powers off the machine
'''
state = self.service().getMachineState(self._vmid)
if state == 'unknown':
raise Exception('Machine not found')
if state == 'down': # Already stoped, return
return
if state != 'up' and state != 'suspended':
self.__pushFrontOp(opRetry) # Will call "check Retry", that will finish inmediatly and again call this one
else:
self.service().stopMachine(self._vmid)
def __suspendMachine(self):
'''
Suspends the machine
'''
state = self.service().getMachineState(self._vmid)
if state == 'unknown':
raise Exception('Machine not found')
if state == 'suspended': # Already suspended, return
return
if state != 'up':
self.__pushFrontOp(opRetry) # Remember here, the return State.FINISH will make this retry be "poped" right ar return
else:
self.service().suspendMachine(self._vmid)
def __changeMac(self):
'''
Changes the mac of the first nic
'''
self.service().updateMachineMac(self._vmid, self.getUniqueId())
# Check methods
def __checkCreate(self):
'''
Checks the state of a deploy for an user or cache
'''
return self.__checkMachineState('down')
def __checkStart(self):
'''
Checks if machine has started
'''
return self.__checkMachineState('up')
def __checkStop(self):
'''
Checks if machine has stoped
'''
return self.__checkMachineState('down')
def __checkSuspend(self):
'''
Check if the machine has suspended
'''
return self.__checkMachineState('suspended')
def __checkRemoved(self):
'''
Checks if a machine has been removed
'''
return self.__checkMachineState('unknown')
def __checkMac(self):
'''
Checks if change mac operation has finished.
Changing nic configuration es 1-step operation, so when we check it here, it is already done
'''
return State.FINISHED
def checkState(self):
'''
Check what operation is going on, and acts acordly to it
'''
self.__debug('checkState')
op = self.__getCurrentOp()
if op == opError:
return State.ERROR
if op == opFinish:
return State.FINISHED
fncs = {
opCreate: self.__checkCreate,
opRetry: self.__retry,
opWait: self.__wait,
opStart: self.__checkStart,
opStop: self.__checkStop,
opSuspend: self.__checkSuspend,
opRemove: self.__checkRemoved,
opChangeMac: self.__checkMac
}
try:
chkFnc = fncs.get(op, None)
if chkFnc is None:
return self.__error('Unknown operation found at check queue ({0})'.format(op))
state = chkFnc()
if state == State.FINISHED:
self.__popCurrentOp() # Remove runing op
return self.__executeQueue()
return state
except Exception as e:
return self.__error(e)
def finish(self):
'''
Invoked when the core notices that the deployment of a service has finished.
(No matter wether it is for cache or for an user)
'''
self.__debug('finish')
pass
def assignToUser(self, user):
'''
This method is invoked whenever a cache item gets assigned to an user.
This gives the User Deployment an oportunity to do whatever actions
are required so the service puts at a correct state for using by a service.
'''
pass
def moveToCache(self, newLevel):
'''
Moves machines between cache levels
'''
if opRemove in self._queue:
return State.RUNNING
if newLevel == self.L1_CACHE:
self._queue = [opStart, opFinish]
else:
self._queue = [opStart, opSuspend, opFinish]
return self.__executeQueue()
def userLoggedIn(self, user):
'''
This method must be available so os managers can invoke it whenever
an user get logged into a service.
The user provided is just an string, that is provided by actor.
'''
# We store the value at storage, but never get used, just an example
pass
def userLoggedOut(self, user):
'''
This method must be available so os managers can invoke it whenever
an user get logged out if a service.
The user provided is just an string, that is provided by actor.
'''
pass
def reasonOfError(self):
'''
Returns the reason of the error.
Remember that the class is responsible of returning this whenever asked
for it, and it will be asked everytime it's needed to be shown to the
user (when the administation asks for it).
'''
return self._reason
def destroy(self):
'''
Invoked for destroying a deployed service
'''
self.__debug('destroy')
# If executing something, wait until finished to remove it
# We simply replace the execution queue
op = self.__getCurrentOp()
if op == opError:
return self.__error('Machine is already in error state!')
if op == opFinish or op == opWait:
self._queue = [opStop, opRemove, opFinish]
return self.__executeQueue()
self._queue = [op, opStop, opRemove, opFinish]
# Do not execute anything.here, just continue normally
return State.RUNNING
def cancel(self):
'''
This is a task method. As that, the excepted return values are
State values RUNNING, FINISHED or ERROR.
This can be invoked directly by an administration or by the clean up
of the deployed service (indirectly).
When administrator requests it, the cancel is "delayed" and not
invoked directly.
'''
return self.destroy()
@staticmethod
def __op2str(op):
return {
opCreate: 'create',
opStart: 'start',
opStop: 'stop',
opSuspend: 'suspend',
opRemove: 'remove',
opWait: 'wait',
opError: 'error',
opFinish: 'finish',
opRetry: 'retry',
opChangeMac: 'changing mac'
}.get(op, '????')
def __debug(self, txt):
logger.debug('_name {0}: {1}'.format(txt, self._name))
logger.debug('_ip {0}: {1}'.format(txt, self._ip))
logger.debug('_mac {0}: {1}'.format(txt, self._mac))
logger.debug('_vmid {0}: {1}'.format(txt, self._vmid))
logger.debug('Queue at {0}: {1}'.format(txt, [LiveDeployment.__op2str(op) for op in self._queue]))

View File

@ -0,0 +1,165 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 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
'''
from django.utils.translation import ugettext as _
from uds.core.services import Publication
from uds.core.util.State import State
from datetime import datetime
import logging
__updated__ = '2016-02-08'
logger = logging.getLogger(__name__)
class LivePublication(Publication):
'''
This class provides the publication of a oVirtLinkedService
'''
suggestedTime = 2 # : Suggested recheck time if publication is unfinished in seconds
def initialize(self):
'''
This method will be invoked by default __init__ of base class, so it gives
us the oportunity to initialize whataver we need here.
In our case, we setup a few attributes..
'''
# We do not check anything at marshal method, so we ensure that
# default values are correctly handled by marshal.
self._name = ''
self._reason = ''
self._templateId = ''
self._state = 'r'
def marshal(self):
'''
returns data from an instance of Sample Publication serialized
'''
return '\t'.join(['v1', self._name, self._reason, self._templateId, self._state])
def unmarshal(self, data):
'''
deserializes the data and loads it inside instance.
'''
logger.debug('Data: {0}'.format(data))
vals = data.split('\t')
if vals[0] == 'v1':
self._name, self._reason, self._templateId, self._state = vals[1:]
def publish(self):
'''
Realizes the publication of the service
'''
self._name = self.service().sanitizeVmName('UDSP ' + self.dsName() + "-" + str(self.revision()))
self._reason = '' # No error, no reason for it
self._state = 'ok'
try:
self._templateId = self.service().makeTemplate(self._name)
except Exception as e:
self._state = 'error'
self._reason = str(e)
return State.ERROR
return State.RUNNING
def checkState(self):
'''
Checks state of publication creation
'''
if self._state == 'error':
return State.ERROR
if self._state == 'ok':
return State.FINISHED
self._state = 'ok'
return State.FINISHED
def finish(self):
'''
In our case, finish does nothing
'''
pass
def reasonOfError(self):
'''
If a publication produces an error, here we must notify the reason why
it happened. This will be called just after publish or checkState
if they return State.ERROR
Returns an string, in our case, set at checkState
'''
return self._reason
def destroy(self):
'''
This is called once a publication is no more needed.
This method do whatever needed to clean up things, such as
removing created "external" data (environment gets cleaned by core),
etc..
The retunred value is the same as when publishing, State.RUNNING,
State.FINISHED or State.ERROR.
'''
# We do not do anything else to destroy this instance of publication
try:
self.service().removeTemplate(self._templateId)
except Exception as e:
self._state = 'error'
self._reason = str(e)
return State.ERROR
return State.FINISHED
def cancel(self):
'''
Do same thing as destroy
'''
return self.destroy()
# Here ends the publication needed methods.
# Methods provided below are specific for this publication
# and will be used by user deployments that uses this kind of publication
def getTemplateId(self):
'''
Returns the template id associated with the publication
'''
return self._templateId

View File

@ -0,0 +1,294 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 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
'''
from django.utils.translation import ugettext_noop as _, ugettext
from uds.core.transports import protocols
from uds.core.services import Service, types as serviceTypes
from .LivePublication import LivePublication
from .LiveDeployment import LiveDeployment
from . import Helpers
from uds.core.ui import gui
import logging
__updated__ = '2016-02-08'
logger = logging.getLogger(__name__)
class LiveService(Service):
'''
Opennebula Live Service
'''
# : Name to show the administrator. This string will be translated BEFORE
# : sending it to administration interface, so don't forget to
# : mark it as _ (using ugettext_noop)
typeName = _('OpenNebula Live Images')
# : Type used internally to identify this provider
typeType = 'openNebulaLiveService'
# : Description shown at administration interface for this provider
typeDescription = _('OpenNebula live images bases service')
# : 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 _ (using ugettext_noop)
iconFile = 'provider.png'
# Functional related data
# : If the service provides more than 1 "deployed user" (-1 = no limit,
# : 0 = ???? (do not use it!!!), N = max number to deploy
maxDeployed = -1
# : 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 = True
# : Tooltip shown to user when this item is pointed at admin interface, none
# : because we don't use it
cacheTooltip = _('Number of desired machines to keep running waiting for a user')
# : If the service needs a s.o. manager (managers are related to agents
# : provided by services itselfs, i.e. virtual machines with actors)
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 = LivePublication
# : Types of deploys (services in cache and/or assigned to users)
deployedType = LiveDeployment
allowedProtocols = protocols.GENERIC + (protocols.SPICE,)
servicesTypeProvided = (serviceTypes.VDI,)
# Now the form part
template = gui.ChoiceField(label=_("Base Template"), order=1, tooltip=_('Service base template'), required=True)
datastore = gui.ChoiceField(label=_("Datastore"), order=2, tooltip=_('Service clones datastore'), required=True)
baseName = gui.TextField(
label=_('Machine Names'),
rdonly=False,
order=6,
tooltip=('Base name for clones from this machine'),
required=True
)
lenName = gui.NumericField(
length=1,
label=_('Name Length'),
defvalue=5,
order=7,
tooltip=_('Size of numeric part for the names of these machines (between 3 and 6)'),
required=True
)
def initialize(self, values):
'''
We check here form values to see if they are valid.
Note that we check them throught FROM variables, that already has been
initialized by __init__ method of base class, before invoking this.
'''
if values is not None:
length = int(self.lenName.value)
if len(self.baseName.value) + length > 15:
raise Service.ValidationException(_('The length of basename plus length must not be greater than 15'))
if self.baseName.value.isdigit():
raise Service.ValidationException(_('The machine name can\'t be only numbers'))
def initGui(self):
'''
Loads required values inside
'''
templates = self.parent().getTemplates()
vals = []
for t in templates:
vals.append(gui.choiceItem(t.id, t.name))
# This is not the same case, values is not the "value" of the field, but
# the list of values shown because this is a "ChoiceField"
self.template.setValues(vals)
datastores = self.parent().getDatastores()
vals = []
for d in datastores:
vals.append(gui.choiceItem(d.id, d.name))
self.datastore.setValues(vals)
def sanitizeVmName(self, name):
return self.parent().sanitizeVmName(name)
def makeTemplate(self, templateName):
return self.parent().makeTemplate(self.template.value, templateName, self.datastore.value)
def getTemplateState(self, templateId):
'''
Invokes getTemplateState from parent provider
Args:
templateId: templateId to remove
Returns nothing
Raises an exception if operation fails.
'''
return self.parent().getTemplateState(templateId)
def deployFromTemplate(self, name, comments, templateId):
'''
Deploys a virtual machine on selected cluster from selected template
Args:
name: Name (sanitized) of the machine
comments: Comments for machine
templateId: Id of the template to deploy from
displayType: 'vnc' or 'spice'. Display to use ad oVirt admin interface
memoryMB: Memory requested for machine, in MB
guaranteedMB: Minimum memory guaranteed for this machine
Returns:
Id of the machine being created form template
'''
logger.debug('Deploying from template {0} machine {1}'.format(templateId, name))
self.datastoreHasSpace()
return self.parent().deployFromTemplate(name, comments, templateId, self.cluster.value,
self.display.value, int(self.memory.value), int(self.memoryGuaranteed.value))
def removeTemplate(self, templateId):
'''
invokes removeTemplate from parent provider
'''
return self.parent().removeTemplate(templateId)
def getMachineState(self, machineId):
'''
Invokes getMachineState from parent provider
(returns if machine is "active" or "inactive"
Args:
machineId: If of the machine to get state
Returns:
one of this values:
unassigned, down, up, powering_up, powered_down,
paused, migrating_from, migrating_to, unknown, not_responding,
wait_for_launch, reboot_in_progress, saving_state, restoring_state,
suspended, image_illegal, image_locked or powering_down
Also can return'unknown' if Machine is not known
'''
return self.parent().getMachineState(machineId)
def startMachine(self, machineId):
'''
Tries to start a machine. No check is done, it is simply requested to oVirt.
This start also "resume" suspended/paused machines
Args:
machineId: Id of the machine
Returns:
'''
return self.parent().startMachine(machineId)
def stopMachine(self, machineId):
'''
Tries to start a machine. No check is done, it is simply requested to oVirt
Args:
machineId: Id of the machine
Returns:
'''
return self.parent().stopMachine(machineId)
def suspendMachine(self, machineId):
'''
Tries to start a machine. No check is done, it is simply requested to oVirt
Args:
machineId: Id of the machine
Returns:
'''
return self.parent().suspendMachine(machineId)
def removeMachine(self, machineId):
'''
Tries to delete a machine. No check is done, it is simply requested to oVirt
Args:
machineId: Id of the machine
Returns:
'''
return self.parent().removeMachine(machineId)
def updateMachineMac(self, machineId, macAddres):
'''
Changes the mac address of first nic of the machine to the one specified
'''
return self.parent().updateMachineMac(machineId, macAddres)
def getMacRange(self):
'''
Returns de selected mac range
'''
return self.parent().getMacRange()
def getBaseName(self):
'''
Returns the base name
'''
return self.baseName.value
def getLenName(self):
'''
Returns the length of numbers part
'''
return int(self.lenName.value)
def getDisplay(self):
'''
Returns the selected display type (for created machines, for administration
'''
return self.display.value
def getConsoleConnection(self, machineId):
return self.parent().getConsoleConnection(machineId)
def desktopLogin(self, machineId, username, password, domain):
return self.parent().desktopLogin(machineId, username, password, domain)

View File

@ -0,0 +1,422 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 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.
'''
Created on Jun 22, 2012
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from django.utils.translation import ugettext_noop as _
from uds.core.services import ServiceProvider
from uds.core.ui import gui
from uds.core.util import validators
from defusedxml import minidom
from .LiveService import LiveService
import logging
import six
# Python bindings for OpenNebula
import oca
__updated__ = '2016-02-08'
logger = logging.getLogger(__name__)
class Provider(ServiceProvider):
'''
This class represents the sample services provider
In this class we provide:
* The Provider functionality
* The basic configuration parameters for the provider
* The form fields needed by administrators to configure this provider
:note: At class level, the translation must be simply marked as so
using ugettext_noop. This is so cause we will translate the string when
sent to the administration client.
For this class to get visible at administration client as a provider type,
we MUST register it at package __init__.
'''
# : What kind of services we offer, this are classes inherited from Service
offers = [LiveService]
# : Name to show the administrator. This string will be translated BEFORE
# : sending it to administration interface, so don't forget to
# : mark it as _ (using ugettext_noop)
typeName = _('OpenNebula Platform Provider')
# : Type used internally to identify this provider
typeType = 'openNebulaPlatform'
# : Description shown at administration interface for this provider
typeDescription = _('OpenNebula platform service provider')
# : 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 _ (using ugettext_noop)
iconFile = 'provider.png'
# now comes the form fields
# There is always two fields that are requested to the admin, that are:
# Service Name, that is a name that the admin uses to name this provider
# Description, that is a short description that the admin gives to this provider
# Now we are going to add a few fields that we need to use this provider
# Remember that these are "dummy" fields, that in fact are not required
# but used for sample purposes
# 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=_('OpenNebula Host'), required=True)
port = gui.NumericField(length=5, label=_('Port'), defvalue='2633', order=2, tooltip=_('OpenNebula Port (default is 2633 for non ssl connection)'), required=True)
ssl = gui.CheckBoxField(label=_('Use SSL'), order=3, tooltip=_('If checked, the connection will be forced to be ssl (will not work if server is not providing ssl)'))
username = gui.TextField(length=32, label=_('Username'), order=4, tooltip=_('User with valid privileges on OpenNebula'), required=True, defvalue='oneadmin')
password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the user of OpenNebula'), required=True)
timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=6, tooltip=_('Timeout in seconds of connection to OpenNebula'), required=True)
macsRange = gui.TextField(length=36, label=_('Macs range'), defvalue='52:54:00:00:00:00-52:54:00:FF:FF:FF', order=7, rdonly=True,
tooltip=_('Range of valid macs for UDS managed machines'), required=True)
# Own variables
_api = None
def initialize(self, values=None):
'''
We will use the "autosave" feature for form fields
'''
# Just reset _api connection variable
self._api = None
if values is not None:
self.macsRange.value = validators.validateMacRange(self.macsRange.value)
self.timeout.value = validators.validateTimeout(self.timeout.value, returnAsInteger=False)
logger.debug('Endpoint: {}'.format(self.endPoint))
@property
def endpoint(self):
return 'http{}://{}:{}/RPC2'.format('s' if self.ssl.isTrue() else '', self.host.value, self.port.value)
@property
def api(self):
if self._api is None:
self._api = oca.Client('{}:{}'.format(self.username.value, self.password.value), self.endpoint)
return self._api
def resetApi(self):
self._api = None
def sanitizeVmName(self, name):
'''
Ovirt only allows machine names with [a-zA-Z0-9_-]
'''
import re
return re.sub("[^a-zA-Z0-9_-]", "_", name)
def testConnection(self):
'''
Test that conection to OpenNebula server is fine
Returns
True if all went fine, false if id didn't
'''
try:
if self.api.version() < '4.1':
return [False, 'OpenNebula version is not supported (required version 4.1 or newer)']
except Exception as e:
return [False, '{}'.format(e)]
return [True, _('Opennebula test connection passed')]
def getMachines(self, force=False):
'''
Obtains the list of machines inside OpenNebula.
Machines starting with UDS are filtered out
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'
'''
vmpool = oca.VirtualMachinePool(self.api)
vmpool.info()
return vmpool
def getDatastores(self, datastoreType=0):
'''
0 seems to be images datastore
'''
datastores = oca.DatastorePool(self.api)
datastores.info()
for ds in datastores:
if ds.type == datastoreType:
yield ds
def getTemplates(self, force=False):
logger.debug('Api: {}'.format(self.api))
templatesPool = oca.VmTemplatePool(self.api)
templatesPool.info()
for t in templatesPool:
if t.name[:4] != 'UDSP':
yield t
def makeTemplate(self, fromTemplateId, name, toDataStore):
'''
Publish the machine (makes a template from it so we can create COWs) and returns the template id of
the creating machine
Args:
fromTemplateId: id of the base template
name: Name of the machine (care, only ascii characters and no spaces!!!)
Returns
Raises an exception if operation could not be acomplished, or returns the id of the template being created.
Note:
Maybe we need to also clone the hard disk?
'''
try:
# First, we clone the themplate itself
templateId = self.api.call('template.clone', int(fromTemplateId), name)
# Now copy cloned images if possible
try:
imgs = oca.ImagePool(self.api)
imgs.info()
imgs = dict(((i.name, i.id) for i in imgs))
info = self.api.call('template.info', templateId)
template = minidom.parseString(info).getElementsByTagName('TEMPLATE')[0]
logger.debug('XML: {}'.format(template.toxml()))
counter = 0
for dsk in template.getElementsByTagName('DISK'):
counter += 1
imgIds = dsk.getElementsByTagName('IMAGE_ID')
if len(imgIds) == 0:
fromId = False
node = dsk.getElementsByTagName('IMAGE')[0].childNodes[0]
imgName = node.data
# Locate
imgId = imgs[imgName]
else:
fromId = True
node = imgIds[0].childNodes[0]
imgId = node.data
logger.debug('Found {} for cloning'.format(imgId))
# Now clone the image
imgName = self.sanitizeVmName(name + ' DSK ' + six.text_type(counter))
newId = self.api.call('image.clone', int(imgId), imgName, int(toDataStore))
if fromId is True:
node.data = six.text_type(newId)
else:
node.data = imgName
# Now update the clone
self.api.call('template.update', templateId, template.toxml())
except:
logger.exception('Exception cloning image')
return six.text_type(templateId)
except Exception as e:
logger.error('Creating template on OpenNebula: {}'.format(e))
raise
def removeTemplate(self, templateId):
'''
Removes a template from ovirt server
Returns nothing, and raises an Exception if it fails
'''
try:
# 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 = oca.ImagePool(self.api)
imgs.info()
imgs = dict(((i.name, i.id) for i in imgs))
info = self.api.call('template.info', int(templateId))
template = minidom.parseString(info).getElementsByTagName('TEMPLATE')[0]
logger.debug('XML: {}'.format(template.toxml()))
counter = 0
for dsk in template.getElementsByTagName('DISK'):
imgIds = dsk.getElementsByTagName('IMAGE_ID')
if len(imgIds) == 0:
node = dsk.getElementsByTagName('IMAGE')[0].childNodes[0]
imgId = imgs[node.data]
else:
node = imgIds[0].childNodes[0]
imgId = node.data
logger.debug('Found {} for cloning'.format(imgId))
# Now delete the image
self.api.call('image.delete', int(imgId))
except:
logger.exception('Exception cloning image')
self.api.call('template.delete', int(templateId))
except Exception as e:
logger.error('Creating template on OpenNebula: {}'.format(e))
def getMachineState(self, machineId):
'''
Returns the state of the machine
This method do not uses cache at all (it always tries to get machine state from OpenNebula server)
Args:
machineId: Id of the machine to get state
Returns:
one of this values:
unassigned, down, up, powering_up, powered_down,
paused, migrating_from, migrating_to, unknown, not_responding,
wait_for_launch, reboot_in_progress, saving_state, restoring_state,
suspended, image_illegal, image_locked or powering_down
Also can return'unknown' if Machine is not known
'''
return self.__getApi().getMachineState(machineId)
def deployFromTemplate(self, name, comments, templateId, clusterId, displayType, memoryMB, guaranteedMB):
'''
Deploys a virtual machine on selected cluster from selected template
Args:
name: Name (sanitized) of the machine
comments: Comments for machine
templateId: Id of the template to deploy from
clusterId: Id of the cluster to deploy to
displayType: 'vnc' or 'spice'. Display to use ad OpenNebula admin interface
memoryMB: Memory requested for machine, in MB
guaranteedMB: Minimum memory guaranteed for this machine
Returns:
Id of the machine being created form template
'''
return self.__getApi().deployFromTemplate(name, comments, templateId, clusterId, displayType, memoryMB, guaranteedMB)
def startMachine(self, machineId):
'''
Tries to start a machine. No check is done, it is simply requested to OpenNebula.
This start also "resume" suspended/paused machines
Args:
machineId: Id of the machine
Returns:
'''
return self.__getApi().startMachine(machineId)
def stopMachine(self, machineId):
'''
Tries to start a machine. No check is done, it is simply requested to OpenNebula
Args:
machineId: Id of the machine
Returns:
'''
return self.__getApi().stopMachine(machineId)
def suspendMachine(self, machineId):
'''
Tries to start a machine. No check is done, it is simply requested to OpenNebula
Args:
machineId: Id of the machine
Returns:
'''
return self.__getApi().suspendMachine(machineId)
def removeMachine(self, machineId):
'''
Tries to delete a machine. No check is done, it is simply requested to OpenNebula
Args:
machineId: Id of the machine
Returns:
'''
return self.__getApi().removeMachine(machineId)
def updateMachineMac(self, machineId, macAddres):
'''
Changes the mac address of first nic of the machine to the one specified
'''
return self.__getApi().updateMachineMac(machineId, macAddres)
def getMacRange(self):
return self.macsRange.value
def getConsoleConnection(self, machineId):
return self.__getApi().getConsoleConnection(machineId)
def desktopLogin(self, machineId, username, password, domain):
'''
'''
return self.__getApi().desktopLogin(machineId, username, password, domain)
@staticmethod
def test(env, data):
'''
Test ovirt Connectivity
Args:
env: environment passed for testing (temporal environment passed)
data: data passed for testing (data obtained from the form
definition)
Returns:
Array of two elements, first is True of False, depending on test
(True is all right, false is error),
second is an String with error, preferably internacionalizated..
'''
return Provider(env, data).testConnection()

View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 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.
from .Provider import Provider

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -448,9 +448,10 @@ gui.servicesPools.link = (event) ->
css: "disabled"
disabled: true
click: (val, value, btn, tbl, refreshFnc) ->
gui.promptModal gettext("Publish"), gettext("Cancel publication"),
gui.doLog val, val[0]
gui.promptModal gettext("Publish"), gettext("Cancel publication?"),
onYes: ->
pubApi.invoke val.id + "/cancel", ->
pubApi.invoke val[0].id + "/cancel", ->
refreshFnc()
return