1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-01-22 22:03:54 +03:00

Fixed macosx CoRD transports && backported OpenGnsys

This commit is contained in:
Adolfo Gómez García 2017-09-18 08:33:17 +02:00
parent 201fb8ff9b
commit 34cd90f9a1
12 changed files with 1411 additions and 11 deletions

View File

@ -0,0 +1,358 @@
# -*- 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 __future__ import unicode_literals
from uds.core.services import UserDeployment
from uds.core.util.State import State
from uds.core.util import log
from uds.models.Util import getSqlDatetime
from . import og
import six
import pickle
import logging
__updated__ = '2017-05-19'
logger = logging.getLogger(__name__)
opCreate, opError, opFinish, opRemove, opRetry = range(5)
class OGDeployment(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 = 20
def initialize(self):
self._name = ''
self._ip = ''
self._mac = ''
self._machineId = ''
self._stamp = 0
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._machineId, self._reason, six.text_type(self._stamp), 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._machineId, self._reason, stamp, queue = vals[1:]
self._stamp = int(stamp)
self._queue = pickle.loads(queue)
def getName(self):
return self._name
def getUniqueId(self):
return self._mac.upper()
def getIp(self):
return self._ip
def setReady(self):
'''
Right now, this does nothing on OG.
The machine has been already been started.
The problem is that currently there is no way that a machine is in FACT started.
OpenGnsys will try it best by sending an WOL
'''
# if self.cache.get('ready') == '1':
# return State.FINISHED
# status = self.service().status(self._machineId)
# possible status are ("off", "oglive", "busy", "linux", "windows", "macos" o "unknown").
# if status['status'] != 'off':
# self.cache.put('ready', '1')
# return State.FINISHED
# Return back machine to preparing?...
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() # No Level2 Cache possible
return self.__executeQueue()
def __initQueueForDeploy(self):
self._queue = [opCreate, opFinish]
def __checkMachineReady(self):
logger.debug('Checking that state of machine {} ({}) is ready'.format(self._machineId, self._name))
try:
status = self.service().status(self._machineId)
except Exception as e:
logger.exception('Exception at checkMachineReady')
return self.__error('Error checking machine: {}'.format(e))
# possible status are ("off", "oglive", "busy", "linux", "windows", "macos" o "unknown").
if status['status'] in ("linux", "windows", "macos"):
return State.FINISHED
return State.RUNNING
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)
# TODO: Unreserve machine?? Maybe it just better to keep it assigned so UDS don't get it again in a while...
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,
opRemove: self.__remove,
}
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:
logger.exception('Got Exception')
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 __create(self):
'''
Deploys a machine from template for user/cache
'''
try:
r = self.service().reserve()
except Exception as e:
logger.exception('Creating machine')
return self.__error('Error creating reservation: {}'.format(e))
self._machineId = r['id']
self._name = r['name']
self._mac = r['mac']
self._ip = r['ip']
self._stamp = getSqlDatetime(unix=True)
# Store actor version
self.dbservice().setProperty('actor_version', '1.0-OpenGnsys')
def __remove(self):
'''
Removes a machine from system
'''
self.service().unreserve(self._machineId)
# Check methods
def __checkCreate(self):
'''
Checks the state of a deploy for an user or cache
'''
return self.__checkMachineReady()
def __checkRemoved(self):
'''
Checks if a machine has been removed
'''
return State.FINISHED # No check at all, always true
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,
opRemove: self.__checkRemoved,
}
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 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
self._queue = [opRemove, opFinish]
return self.__executeQueue()
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',
opRemove: 'remove',
opError: 'error',
opFinish: 'finish',
opRetry: 'retry',
}.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('_machineId {0}: {1}'.format(txt, self._machineId))
logger.debug('Queue at {0}: {1}'.format(txt, [OGDeployment.__op2str(op) for op in self._queue]))

View File

@ -0,0 +1,128 @@
# -*- 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 __future__ import unicode_literals
from uds.core.services import Publication
from uds.core.util.State import State
from uds.models.Util import getSqlDatetime
import logging
__updated__ = '2017-05-18'
logger = logging.getLogger(__name__)
class OGPublication(Publication):
'''
This class provides the publication of a oVirtLinkedService
'''
_name = ''
suggestedTime = 5 # : 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 = ''
def marshal(self):
'''
returns data from an instance of Sample Publication serialized
'''
return '\t'.join(['v1', self._name])
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 = vals[1]
def publish(self):
'''
Realizes the publication of the service
'''
self._name = 'Publication {}'.format(getSqlDatetime())
return State.FINISHED
def checkState(self):
'''
Checks state of publication creation
'''
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 'No error possible :)'
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.
'''
return State.FINISHED
def cancel(self):
'''
Do same thing as destroy
'''
return self.destroy()

View File

@ -0,0 +1,164 @@
# -*- 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 .OGDeployment import OGDeployment
from .OGPublication import OGPublication
from . import helpers
from uds.core.ui import gui
import logging
__updated__ = '2017-05-18'
logger = logging.getLogger(__name__)
class OGService(Service):
'''
OpenGnsys 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 = _('OpenGnsys Machines Service')
# : Type used internally to identify this provider
typeType = 'openGnsysMachine'
# : Description shown at administration interface for this provider
typeDescription = _('OpenGnsys physical machines')
# : 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 an 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 = False
# : 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 = OGPublication
# : Types of deploys (services in cache and/or assigned to users)
deployedType = OGDeployment
allowedProtocols = protocols.GENERIC
servicesTypeProvided = (serviceTypes.VDI,)
# Now the form part
ou = gui.ChoiceField(
label=_("OU"),
order=100,
fills={
'callbackName' : 'osFillData',
'function' : helpers.getResources,
'parameters' : ['ov', 'ev', 'ou']
},
tooltip=_('Organizational Unit'),
required=True
)
# Lab is not required, but maybe used as filter
lab = gui.ChoiceField(
label=_("lab"),
order=101,
tooltip=_('Laboratory'),
required=False
)
# Required, this is the base image
image = gui.ChoiceField(
label=_("OS Image"),
order=102,
tooltip=_('OpenGnsys Operanting System Image'),
required=True
)
maxReservationTime = gui.NumericField(
length=3,
label=_("Max. reservation time"),
order=110,
tooltip=_('Security parameter for OpenGnsys to kepp reservations at most this hours'),
defvalue='24',
tab=_('Advanced'),
required=False
)
ov = gui.HiddenField(value=None)
ev = gui.HiddenField(value=None) # We need to keep the env so we can instantiate the Provider
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:
pass
def initGui(self):
'''
Loads required values inside
'''
ous = [gui.choiceItem(r['id'], r['name']) for r in self.parent().api.getOus()]
self.ou.setValues(ous)
self.ov.setDefValue(self.parent().serialize())
self.ev.setDefValue(self.parent().env.key)
def status(self, machineId):
return self.parent().status(machineId)
def reserve(self):
return self.parent().reserve(self.ou.value, self.image.value, self.lab.value, self.maxReservationTime.num())
def unreserve(self, machineId):
return self.parent().unreserve(machineId)

View File

@ -0,0 +1,181 @@
# -*- 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 distributiog.
# * 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 permissiog.
#
# 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 .OGService import OGService
from . import og
import logging
import six
__updated__ = '2017-05-23'
logger = logging.getLogger(__name__)
class OGProvider(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 = [OGService]
# : 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 = _('OpenGnsys Platform Provider')
# : Type used internally to identify this provider
typeType = 'openGnsysPlatform'
# : Description shown at administration interface for this provider
typeDescription = _('OpenGnsys platform service provider (experimental)')
# : 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=_('OpenGnsys Host'), required=True)
port = gui.NumericField(length=5, label=_('Port'), defvalue='443', order=2, tooltip=_('OpenGnsys Port (default is 443, and only ssl connection is allowed)'), required=True)
checkCert = gui.CheckBoxField(label=_('Check Cert.'), order=3, tooltip=_('If checked, ssl certificate of OpenGnsys server must be valid, not self signed)'))
username = gui.TextField(length=32, label=_('Username'), order=4, tooltip=_('User with valid privileges on OpenGnsys'), required=True)
password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the user of OpenGnsys'), required=True)
udsServerAccessUrl = gui.TextField(length=32, label=_('UDS Server URL'), order=6, tooltip=_('URL used by OpenGnsys to access UDS. If empty, UDS will guess it.'), required=False, tab=gui.PARAMETERS_TAB)
maxPreparingServices = gui.NumericField(length=3, label=_('Creation concurrency'), defvalue='10', minValue=1, maxValue=65536, order=50, tooltip=_('Maximum number of concurrently creating VMs'), required=True, tab=gui.ADVANCED_TAB)
maxRemovingServices = gui.NumericField(length=3, label=_('Removal concurrency'), defvalue='5', minValue=1, maxValue=65536, order=51, tooltip=_('Maximum number of concurrently removing VMs'), required=True, tab=gui.ADVANCED_TAB)
timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=90, tooltip=_('Timeout in seconds of connection to OpenGnsys'), required=True, tab=gui.ADVANCED_TAB)
# 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.timeout.value = validators.validateTimeout(self.timeout.value, returnAsInteger=False)
logger.debug('Endpoint: {}'.format(self.endpoint))
@property
def endpoint(self):
return 'https://{}:{}/rest'.format(self.host.value, self.port.value)
@property
def api(self):
if self._api is None:
self._api = og.OpenGnsysClient(self.username.value, self.password.value, self.endpoint, self.cache, self.checkCert.isTrue())
logger.debug('Api: {}'.format(self._api))
return self._api
def resetApi(self):
self._api = None
def testConnection(self):
'''
Test that conection to OpenGnsys server is fine
Returns
True if all went fine, false if id didn't
'''
try:
if self.api.version[0:5] < '1.1.0':
return [False, 'OpenGnsys version is not supported (required version 1.1.0 or newer and found {})'.format(self.api.version)]
except Exception as e:
logger.exception('Error')
return [False, '{}'.format(e)]
return [True, _('OpenGnsys test connection passed')]
@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 OGProvider(env, data).testConnection()
def reserve(self, ou, image, lab=0, maxtime=0):
return self.api.reserve(ou, image, lab, maxtime)
def unreserve(self, machineId):
return self.api.unreserve(machineId)
def status(self, machineId):
return self.api.status(machineId)

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 OGProvider

View File

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from django.utils.translation import ugettext as _
import logging
from uds.core.ui import gui
logger = logging.getLogger(__name__)
def getResources(parameters):
'''
This helper is designed as a callback for Project Selector
'''
from .Provider import OGProvider
from uds.core.Environment import Environment
logger.debug('Parameters received by getResources Helper: {0}'.format(parameters))
env = Environment(parameters['ev'])
provider = OGProvider(env)
provider.unserialize(parameters['ov'])
api = provider.api
labs = [gui.choiceItem('0', _('All Labs'))] + [gui.choiceItem(l['id'], l['name']) for l in api.getLabs(ou=parameters['ou'])]
images = [gui.choiceItem(z['id'], z['name']) for z in api.getImages(ou=parameters['ou'])]
data = [
{'name': 'lab', 'values': labs },
{'name': 'image', 'values': images },
]
logger.debug('Return data: {}'.format(data))
return data

View File

@ -0,0 +1,228 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017 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 __future__ import unicode_literals
from . import urls
from . import fake
import sys
import imp
import re
import logging
import six
import requests
import json
__updated__ = '2017-05-18'
logger = logging.getLogger(__name__)
# URLS
# Fake part
FAKE = True
CACHE_VALIDITY = 180
# Decorator
def ensureConnected(fnc):
def inner(*args, **kwargs):
args[0].connect()
return fnc(*args, **kwargs)
return inner
# Result checker
def ensureResponseIsValid(response, errMsg=None):
if response.ok is False:
if errMsg is None:
errMsg = 'Invalid response'
# If response.code is not 200, the response is an error and should have a message
# FIX THIS
if response.code != 200:
try:
err = response.json()['message'] # Extract any key, in case of error is expected to have only one top key so this will work
except Exception:
err = response.content
errMsg = '{}: {}, ({})'.format(errMsg, err, response.code)
logger.error('{}: {}'.format(errMsg, response.content))
raise Exception(errMsg)
return json.loads(response.content)
class OpenGnsysClient(object):
def __init__(self, username, password, endpoint, cache, verifyCert=False):
self.username = username
self.password = password
self.endpoint = endpoint
self.auth = None
self.cache = cache
self.verifyCert = verifyCert
self.cachedVersion = None
@property
def headers(self):
headers = {'content-type': 'application/json'}
if self.auth is not None:
headers['Authorization'] = self.auth
return headers
def _ogUrl(self, path):
return self.endpoint + '/' + path
def _post(self, path, data, errMsg=None):
if not FAKE:
return ensureResponseIsValid(
requests.post(self._ogUrl(path), data=json.dumps(data), headers=self.headers, verify=self.verifyCert),
errMsg=errMsg
)
# FAKE Connection :)
return fake.post(path, data, errMsg)
def _get(self, path, errMsg=None):
if not FAKE:
return ensureResponseIsValid(
requests.get(self._ogUrl(path), headers=self.headers, verify=self.verifyCert),
errMsg=errMsg
)
# FAKE Connection :)
return fake.get(path, errMsg)
def _delete(self, path, errMsg=None):
if not FAKE:
return ensureResponseIsValid(
requests.delete(self._ogUrl(path), headers=self.headers, verify=self.verifyCert),
errMsg=errMsg
)
return fake.delete(path, errMsg)
def connect(self):
if self.auth is not None:
return
cacheKey = 'auth{}{}'.format(self.endpoint, self.username)
self.auth = self.cache.get(cacheKey)
if self.auth is not None:
return
auth = self._post(urls.LOGIN,
data={
'username': self.username,
'password': self.password
},
errMsg='Loggin in'
)
self.auth = auth['apikey']
self.cache.put(cacheKey, self.auth, CACHE_VALIDITY)
@property
def version(self):
logger.debug('Getting version')
if self.cachedVersion is None:
# Retrieve Version & keep it
info = self._get(urls.INFO, errMsg="Retrieving info")
self.cachedVersion = info['version']
return self.cachedVersion
@ensureConnected
def getOus(self):
# Returns an array of elements with:
# 'id': OpenGnsys Id
# 'name': OU name
# OpenGnsys already returns it in this format :)
return self._get(urls.OUS, errMsg='Getting list of ous')
@ensureConnected
def getLabs(self, ou):
# Returns a list of available labs on an ou
# /ous/{ouid}/labs
# Take into accout that we must exclude the ones with "inremotepc" set to false.
errMsg = 'Getting list of labs from ou {}'.format(ou)
return [{'id': l['id'], 'name': l['name']} for l in self._get(urls.LABS.format(ou=ou), errMsg=errMsg) if l.get('inremotepc', False) is True]
@ensureConnected
def getImages(self, ou):
# Returns a list of available labs on an ou
# /ous/{ouid}/images
# Take into accout that we must exclude the ones with "inremotepc" set to false.
errMsg = 'Getting list of images from ou {}'.format(ou)
return [{'id': l['id'], 'name': l['name']} for l in self._get(urls.IMAGES.format(ou=ou), errMsg=errMsg) if l.get('inremotepc', False) is True]
@ensureConnected
def reserve(self, ou, image, lab=0, maxtime=24):
# This method is inteded to "get" a machine from OpenGnsys
# The method used is POST
# invokes /ous/{ouid}}/images/{imageid}/reserve
# also remember to store "labid"
# Labid can be "0" that means "all laboratories"
errMsg = 'Reserving image {} in ou {}'.format(ou, image)
data = {
'labid': lab,
'maxtime': maxtime
}
res = self._post(urls.RESERVE.format(ou=ou, image=image), data, errMsg=errMsg)
return {
'ou': ou,
'image': image,
'lab': lab,
'client': res['id'],
'id': '.'.join((six.text_type(ou), six.text_type(lab), six.text_type(res['id']))),
'name': res['name'],
'ip': res['ip'],
'mac': ':'.join(re.findall('..', res['mac']))
}
@ensureConnected
def unreserve(self, id):
# This method releases the previous reservation
# Invoked every time we need to release a reservation (i mean, if a reservation is done, this will be called with the obtained id from that reservation)
ou, lab, client = id.split('.')
errMsg = 'Unreserving client {} in lab {} in ou {}'.format(client, lab, ou)
return self._delete(urls.UNRESERVE.format(ou=ou, lab=lab, client=client), errMsg=errMsg)
@ensureConnected
def status(self, id):
# This method gets the status of the machine
# /ous/{uoid}/labs/{labid}/clients/{clientid}/status
# possible status are ("off", "oglive", "busy", "linux", "windows", "macos" o "unknown").
# Look at api at informatica.us..
ou, lab, client = id.split('.')
return self._get(urls.STATUS.format(ou=ou, lab=lab, client=client))

View File

@ -0,0 +1,225 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017 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 __future__ import unicode_literals
from . import urls
import copy
import random
import six
import logging
__updated__ = '2017-05-19'
logger = logging.getLogger(__name__)
AUTH = {
"userid": 1001,
"apikey": "fakeAPIKeyJustForDeveloping"
}
INFO = {
"project": "OpenGnsys",
"version": "1.1.0pre",
"release": "r5299",
"services": [
"server",
"repository",
"tracker"
],
"oglive": [
{
"distribution": "xenial",
"kernel": "4.8.0-39-generic",
"architecture": "amd64",
"revision": "r5225",
"directory": "ogLive-xenial-4.8.0-amd64-r5225",
"iso": "ogLive-xenial-4.8.0-39-generic-amd64-r5225.iso"
},
{
"iso": "ogLive-precise-3.2.0-23-generic-r4820.iso",
"directory": "ogLive-precise-3.2.0-i386-r4820",
"revision": "r4820",
"architecture": "i386",
"kernel": "3.2.0-23-generic",
"distribution": "precise"
} ]
}
OUS = [
{
"id": "1",
"name": "Unidad Organizativa (Default)"
},
{
"id": "2",
"name": "Unidad Organizatva VACIA"
},
]
LABS = [
{
"id": "1",
"name": "Sala de control",
"inremotepc": True,
"group": {
"id": "0"
},
"ou": {
"id": "1"
}
},
{
"id": "2",
"name": "Sala de computación cuántica",
"inremotepc": True,
"group": {
"id": "0"
},
"ou": {
"id": "1"
}
}
]
IMAGES = [
{
"id": "1",
"name": "Basica1604",
"inremotepc": True,
"ou": {
"id": "1"
}
},
{
"id": "2",
"name": "Ubuntu16",
"inremotepc": True,
"ou": {
"id": "1"
}
},
{
"id": "3",
"name": "Ubuntu64 Not in Remote",
"inremotepc": False,
"ou": {
"id": "1"
}
},
{
"id": "4",
"name": "Ubuntu96 Not In Remote",
"inremotepc": False,
"ou": {
"id": "1"
}
},
]
RESERVE = {
"id": 4,
"name": "pcpruebas",
"mac": "4061860521FE",
"ip": "10.1.14.31",
"lab": {
"id": 1
},
"ou": {
"id": 1
}
}
UNRESERVE = ''
STATUS_OFF = {
"id": 4,
"ip": "10.1.14.31",
"status": "off",
"loggedin": False
}
# A couple of status for testing
STATUS_READY_LINUX = {
"id": 4,
"ip": "10.1.14.31",
"status": "linux",
"loggedin": False
}
STATUS_READY_WINDOWS = {
"id": 4,
"ip": "10.1.14.31",
"status": "windows",
"loggedin": False
}
# FAKE post
def post(path, data, errMsg):
logger.info('FAKE POST request to {} with {} data. ({})'.format(path, data, errMsg))
if path == urls.LOGIN:
return AUTH
elif path == urls.RESERVE.format(ou=1, image=1) or path == urls.RESERVE.format(ou=1, image=2):
res = copy.deepcopy(RESERVE)
res['name'] += six.text_type(random.randint(5000, 100000))
res['mac'] = ''.join(random.choice('0123456789ABCDEF') for __ in range(12))
return res
raise Exception('Unknown FAKE URL on POST: {}'.format(path))
# FAKE get
def get(path, errMsg):
logger.info('FAKE GET request to {}. ({})'.format(path, errMsg))
if path == urls.INFO:
return INFO
elif path == urls.OUS:
return OUS
elif path == urls.LABS.format(ou=1):
return LABS
elif path == urls.LABS.format(ou=2):
return [] # Empty
elif path == urls.IMAGES.format(ou=1):
return IMAGES
elif path == urls.IMAGES.format(ou=2):
return []
elif path[-6:] == 'status':
rnd = random.randint(0, 100)
if rnd < 25:
return STATUS_READY_LINUX
return STATUS_OFF
raise Exception('Unknown FAKE URL on GET: {}'.format(path))
def delete(path, errMsg):
logger.info('FAKE DELETE request to {}. ({})'.format(path, errMsg))
# Right now, only "unreserve" uses delete, so simply return
return UNRESERVE
# raise Exception('Unknown FAKE URL on DELETE: {}'.format(path))

View File

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017 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 __future__ import unicode_literals
# API URL 1: https://www.informatica.us.es/~ramon/opengnsys/?url=opengnsys-api.yml
# API URL 2: http://opengnsys.es/wiki/ApiRest
LOGIN = '/login'
INFO = '/info'
OUS = '/ous'
LABS = '/ous/{ou}/labs'
IMAGES = '/ous/{ou}/images'
RESERVE = '/ous/{ou}/images/{image}/reserve'
UNRESERVE = '/ous/{ou}/labs/{lab}/clients/{client}/unreserve'
STATUS = '/ous/{ou}/labs/{lab}/clients/{client}/status'

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -89,16 +89,16 @@ else: # CoRD
url += '?screenDepth={m.r.bpp}'
if {m.r.fullScreen}: # @UndefinedVariable
url += '&fullscreen=true'
url += '&fullscreen###true'
else:
url += 'screenWidth={m.r.width}&screenHeight={m.r.height}'
url += 'screenWidth###{m.r.width}&screenHeight###{m.r.height}'
url += '&forwardAudio=' + '01'[{m.r.redirectAudio}] # @UndefinedVariable
url += '&forwardAudio###' + '01'[{m.r.redirectAudio}] # @UndefinedVariable
if {m.r.redirectDrives}: # @UndefinedVariable
url += '&forwardDisks=true'
url += '&forwardDisks###true'
if {m.r.redirectPrinters}: # @UndefinedVariable
url += '&forwardPrinters=true'
url += '&forwardPrinters###true'
tools.addTaskToWait(subprocess.Popen(['open', url]))

View File

@ -95,19 +95,19 @@ else:
if domain != '':
url += domain
url += '?screenDepth={m.r.bpp}'
url += '?screenDepth###{m.r.bpp}'
if {m.r.fullScreen}: # @UndefinedVariable
url += '&fullscreen=true'
url += '&fullscreen###true'
else:
url += 'screenWidth={m.r.width}&screenHeight={m.r.height}'
url += 'screenWidth###{m.r.width}&screenHeight###{m.r.height}'
url += '&forwardAudio=' + '01'[{m.r.redirectAudio}] # @UndefinedVariable
# url += '&forwardAudio###' + '01'[{m.r.redirectAudio}] # @UndefinedVariable
if {m.r.redirectDrives}: # @UndefinedVariable
url += '&forwardDisks=true'
url += '&forwardDisks###true'
if {m.r.redirectPrinters}: # @UndefinedVariable
url += '&forwardPrinters=true'
url += '&forwardPrinters###true'
tools.addTaskToWait(subprocess.Popen(['open', url]))