Example #1
0
class LinuxOsManager(osmanagers.OSManager):
    typeName = _('Linux OS Manager')
    typeType = 'LinuxManager'
    typeDescription = _('Os Manager to control Linux virtual machines')
    iconFile = 'losmanager.png'

    servicesType = (serviceTypes.VDI,)

    onLogout = gui.ChoiceField(
        label=_('Logout Action'),
        order=10,
        rdonly=True,
        tooltip=_('What to do when user logs out from service'),
        values=[
            {'id': 'keep', 'text': ugettext_lazy('Keep service assigned')},
            {'id': 'remove', 'text': ugettext_lazy('Remove service')},
            {'id': 'keep-always', 'text': ugettext_lazy('Keep service assigned even on new publication')},
        ],
        defvalue='keep')

    idle = gui.NumericField(
        label=_("Max.Idle time"),
        length=4,
        defvalue=-1,
        rdonly=False, order=11,
        tooltip=_('Maximum idle time (in seconds) before session is automatically closed to the user (<= 0 means no max idle time).'),
        required=True)

    def __setProcessUnusedMachines(self):
        self.processUnusedMachines = self._onLogout == 'remove'

    def __init__(self, environment, values):
        super(LinuxOsManager, self).__init__(environment, values)
        if values is not None:
            self._onLogout = values['onLogout']
            self._idle = int(values['idle'])
        else:
            self._onLogout = ''
            self._idle = -1

        self.__setProcessUnusedMachines()

    def release(self, userService: 'UserService') -> None:
        pass

    def isRemovableOnLogout(self, userService: 'UserService') -> bool:
        '''
        Says if a machine is removable on logout
        '''
        if not userService.in_use:
            if (self._onLogout == 'remove') or (not userService.isValidPublication() and self._onLogout == 'keep'):
                return True

        return False

    def getName(self, service):
        """
        gets name from deployed
        """
        return service.getName()

    def infoVal(self, service):
        return 'rename:' + self.getName(service)

    def infoValue(self, service):
        return 'rename\r' + self.getName(service)

    def notifyIp(self, uid, service, data):
        si = service.getInstance()

        ip = ''
        # Notifies IP to deployed
        for p in data['ips']:
            if p[0].lower() == uid.lower():
                si.setIp(p[1])
                ip = p[1]
                break

        self.logKnownIp(service, ip)
        service.updateData(si)

    def doLog(self, service, data, origin=log.OSMANAGER):
        # Stores a log associated with this service
        try:
            msg, level = data.split('\t')
            try:
                level = int(level)
            except Exception:
                logger.debug('Do not understand level %s', level)
                level = log.INFO
            log.doLog(service, level, msg, origin)
        except Exception:
            log.doLog(service, log.ERROR, "do not understand {0}".format(data), origin)

    # default "ready received" does nothing
    def readyReceived(self, userService, data):
        pass

    def loginNotified(self, userService, userName=None):
        if '\\' not in userName:
            self.loggedIn(userService, userName)

    def logoutNotified(self, userService, userName=None):
        self.loggedOut(userService, userName)
        if self.isRemovableOnLogout(userService):
            userService.release()

    def readyNotified(self, userService):
        return

    def actorData(self, userService: 'UserService') -> typing.MutableMapping[str, typing.Any]:
        return {
            'action': 'rename',
            'name': userService.getName()
        }

    def process(self, userService: 'UserService', message: str, data: typing.Any, options: typing.Optional[typing.Dict[str, typing.Any]] = None) -> str:
        """
        We understand this messages:
        * msg = info, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class), old method
        * msg = information, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class), new method
        * msg = logon, data = Username, Informs that the username has logged in inside the machine
        * 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 %s with params: %s, %s", userService, message, 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 = userService.os_state
        if message in ('ready', 'ip'):
            if not isinstance(data, dict):  # Older actors?, previous to 2.5, convert it information..
                data = {
                    'ips': [v.split('=') for v in typing.cast(str, data).split(',')],
                    'hostname': userService.friendly_name
                }

        # Old "info" state, will be removed in a near future
        if message == "info":
            ret = self.infoVal(userService)
            state = State.PREPARING
        elif message == "information":
            ret = self.infoValue(userService)
            state = State.PREPARING
        elif message == "log":
            self.doLog(userService, data, log.ACTOR)
        elif message == "login":
            self.loggedIn(userService, data)
            ip, hostname = userService.getConnectionSource()
            deadLine = userService.deployed_service.getDeadline()
            ret = "{}\t{}\t{}".format(ip, hostname, 0 if deadLine is None else deadLine)
        elif message == "logout":
            self.loggedOut(userService, data)
            doRemove = self.isRemovableOnLogout(userService)
        elif message == "ip":
            # This ocurss on main loop inside machine, so userService is usable
            state = State.USABLE
            self.notifyIp(userService.unique_id, userService, data)
        elif message == "ready":
            self.toReady(userService)
            state = State.USABLE
            notifyReady = True
            self.notifyIp(userService.unique_id, userService, data)

        userService.setOsState(state)

        # If notifyReady is not true, save state, let UserServiceManager do it for us else
        if doRemove is True:
            userService.release()
        else:
            if notifyReady:
                userServiceManager().notifyReadyFromOsManager(userService, '')
        logger.debug('Returning %s', ret)
        return ret

    def processUnused(self, userService):
        """
        This will be invoked for every assigned and unused user service that has been in this state at least 1/2 of Globalconfig.CHECK_UNUSED_TIME
        This function can update userService values. Normal operation will be remove machines if this state is not valid
        """
        if self.isRemovableOnLogout(userService):
            userService.remove()

    def isPersistent(self):
        return self._onLogout == 'keep-always'

    def checkState(self, userService: 'UserService') -> str:
        logger.debug('Checking state for service %s', userService)
        return State.RUNNING

    def maxIdle(self):
        """
        On production environments, will return no idle for non removable machines
        """
        if self._idle <= 0:  # or (settings.DEBUG is False and self._onLogout != 'remove'):
            return None

        return self._idle

    def marshal(self) -> bytes:
        """
        Serializes the os manager data so we can store it in database
        """
        return '\t'.join(['v2', self._onLogout, str(self._idle)]).encode('utf8')

    def unmarshal(self, data: bytes):
        values = data.decode('utf8').split('\t')
        if values[0] == 'v1':
            self._onLogout = values[1]
            self._idle = -1
        elif values[0] == 'v2':
            self._onLogout, self._idle = values[1], int(values[2])

        self.__setProcessUnusedMachines()

    def valuesDict(self):
        return {'onLogout': self._onLogout, 'idle': self._idle}
Example #2
0
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 = _('OpenStack Platform Provider')
    # : Type used internally to identify this provider
    typeType = 'openStackPlatform'
    # : Description shown at administration interface for this provider
    typeDescription = _('OpenStack 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=_('OpenStack Host'),
                         required=True)
    port = gui.NumericField(length=5,
                            label=_('Port'),
                            defvalue='5000',
                            order=2,
                            tooltip=_('OpenStack Port'),
                            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)'
          ))

    access = gui.ChoiceField(label=_('Access interface'),
                             order=4,
                             tooltip=_('Access interface to be used'),
                             values=INTERFACE_VALUES,
                             defvalue='public')

    domain = gui.TextField(length=64,
                           label=_('Domain'),
                           order=8,
                           tooltip=_('Domain name (default is Default)'),
                           required=True,
                           defvalue='Default')
    username = gui.TextField(
        length=64,
        label=_('Username'),
        order=9,
        tooltip=_('User with valid privileges on OpenStack'),
        required=True,
        defvalue='admin')
    password = gui.PasswordField(
        lenth=32,
        label=_('Password'),
        order=10,
        tooltip=_('Password of the user of OpenStack'),
        required=True)

    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',
        minValue=1,
        maxValue=128,
        order=99,
        tooltip=_('Timeout in seconds of connection to OpenStack'),
        required=True,
        tab=gui.ADVANCED_TAB)

    # tenant = gui.TextField(length=64, label=_('Project'), order=6, tooltip=_('Project (tenant) for this provider'), required=True, defvalue='')
    # region = gui.TextField(length=64, label=_('Region'), order=7, tooltip=_('Region for this provider'), required=True, defvalue='RegionOne')

    # Own variables
    _api = None

    def initialize(self, values=None):
        """
        We will use the "autosave" feature for form fields
        """
        # Just reset _api connection variable

        if values is not None:
            self.timeout.value = validators.validateTimeout(
                self.timeout.value, returnAsInteger=False)

    def api(self, projectId=None, region=None):
        return openStack.Client(self.host.value,
                                self.port.value,
                                self.domain.value,
                                self.username.value,
                                self.password.value,
                                useSSL=self.ssl.isTrue(),
                                projectId=projectId,
                                region=region,
                                access=self.access.value)

    def sanitizeVmName(self, name):
        return openStack.sanitizeName(name)

    def testConnection(self):
        """
        Test that conection to OpenStack server is fine

        Returns

            True if all went fine, false if id didn't
        """

        try:
            if self.api().testConnection() is False:
                raise Exception('Check connection credentials, server, etc.')
        except Exception as e:
            return [False, '{}'.format(e)]

        return [True, _('OpenStack 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 Provider(env, data).testConnection()
Example #3
0
class NXTransport(BaseNXTransport):
    """
    Provides access via NX to service.
    This transport can use an domain. If username processed by authenticator contains '@', it will split it and left-@-part will be username, and right password
    """
    typeName = _('NX v3.5 (DEPRECATED)')
    typeType = 'NXTransport'
    typeDescription = _('NX Protocol v3.5. Direct connection.')
    iconFile = 'nx.png'
    protocol = transports.protocols.NX

    useEmptyCreds = gui.CheckBoxField(
        label=_('Empty creds'),
        order=1,
        tooltip=_('If checked, the credentials used to connect will be emtpy'),
        tab=gui.CREDENTIALS_TAB)
    fixedName = gui.TextField(
        label=_('Username'),
        order=2,
        tooltip=_(
            'If not empty, this username will be always used as credential'),
        tab=gui.CREDENTIALS_TAB)
    fixedPassword = gui.PasswordField(
        label=_('Password'),
        order=3,
        tooltip=_(
            'If not empty, this password will be always used as credential'),
        tab=gui.CREDENTIALS_TAB)
    listenPort = gui.NumericField(
        label=_('Listening port'),
        length=5,
        order=4,
        tooltip=_('Listening port of NX (ssh) at client machine'),
        defvalue='22')
    connection = gui.ChoiceField(
        label=_('Connection'),
        order=6,
        tooltip=_('Connection speed for this transport (quality)'),
        values=[{
            'id': 'modem',
            'text': 'modem'
        }, {
            'id': 'isdn',
            'text': 'isdn'
        }, {
            'id': 'adsl',
            'text': 'adsl'
        }, {
            'id': 'wan',
            'text': 'wan'
        }, {
            'id': 'lan',
            'text': 'lan'
        }],
        tab=gui.PARAMETERS_TAB)
    session = gui.ChoiceField(label=_('Session'),
                              order=7,
                              tooltip=_('Desktop session'),
                              values=[
                                  {
                                      'id': 'gnome',
                                      'text': 'gnome'
                                  },
                                  {
                                      'id': 'kde',
                                      'text': 'kde'
                                  },
                                  {
                                      'id': 'cde',
                                      'text': 'cde'
                                  },
                              ],
                              tab=gui.PARAMETERS_TAB)
    cacheDisk = gui.ChoiceField(label=_('Disk Cache'),
                                order=8,
                                tooltip=_('Cache size en Mb stored at disk'),
                                values=[
                                    {
                                        'id': '0',
                                        'text': '0 Mb'
                                    },
                                    {
                                        'id': '32',
                                        'text': '32 Mb'
                                    },
                                    {
                                        'id': '64',
                                        'text': '64 Mb'
                                    },
                                    {
                                        'id': '128',
                                        'text': '128 Mb'
                                    },
                                    {
                                        'id': '256',
                                        'text': '256 Mb'
                                    },
                                    {
                                        'id': '512',
                                        'text': '512 Mb'
                                    },
                                ],
                                tab=gui.PARAMETERS_TAB)
    cacheMem = gui.ChoiceField(label=_('Memory Cache'),
                               order=9,
                               tooltip=_('Cache size en Mb kept at memory'),
                               values=[
                                   {
                                       'id': '4',
                                       'text': '4 Mb'
                                   },
                                   {
                                       'id': '8',
                                       'text': '8 Mb'
                                   },
                                   {
                                       'id': '16',
                                       'text': '16 Mb'
                                   },
                                   {
                                       'id': '32',
                                       'text': '32 Mb'
                                   },
                                   {
                                       'id': '64',
                                       'text': '64 Mb'
                                   },
                                   {
                                       'id': '128',
                                       'text': '128 Mb'
                                   },
                               ],
                               tab=gui.PARAMETERS_TAB)
    screenSize = gui.ChoiceField(label=_('Screen size'),
                                 order=10,
                                 tooltip=_('Screen size'),
                                 defvalue=CommonPrefs.SZ_FULLSCREEN,
                                 values=[{
                                     'id': CommonPrefs.SZ_640x480,
                                     'text': '640x480'
                                 }, {
                                     'id': CommonPrefs.SZ_800x600,
                                     'text': '800x600'
                                 }, {
                                     'id': CommonPrefs.SZ_1024x768,
                                     'text': '1024x768'
                                 }, {
                                     'id': CommonPrefs.SZ_1366x768,
                                     'text': '1366x768'
                                 }, {
                                     'id': CommonPrefs.SZ_1920x1080,
                                     'text': '1920x1080'
                                 }, {
                                     'id': CommonPrefs.SZ_FULLSCREEN,
                                     'text': ugettext_lazy('Full Screen')
                                 }],
                                 tab=gui.PARAMETERS_TAB)

    _useEmptyCreds: bool = False
    _fixedName: str = ''
    _fixedPassword: str = ''
    _listenPort: str = ''
    _connection: str = ''
    _session: str = ''
    _cacheDisk: str = ''
    _cacheMem: str = ''
    _screenSize: str = ''

    def initialize(self, values: 'Module.ValuesType'):
        if values:
            self._useEmptyCreds = gui.strToBool(values['useEmptyCreds'])
            self._fixedName = values['fixedName']
            self._fixedPassword = values['fixedPassword']
            self._listenPort = values['listenPort']
            self._connection = values['connection']
            self._session = values['session']
            self._cacheDisk = values['cacheDisk']
            self._cacheMem = values['cacheMem']
            self._screenSize = values['screenSize']

    def marshal(self) -> bytes:
        """
        Serializes the transport data so we can store it in database
        """
        return str.join('\t', [
            'v2',
            gui.boolToStr(self._useEmptyCreds), self._fixedName,
            self._fixedPassword, self._listenPort, self._connection,
            self._session, self._cacheDisk, self._cacheMem, self._screenSize
        ]).encode('utf8')

    def unmarshal(self, data: bytes) -> None:
        values = data.decode('utf8').split('\t')
        if values[0] in ('v1', 'v2'):
            self._useEmptyCreds = gui.strToBool(values[1])
            self._fixedName, self._fixedPassword, self._listenPort, self._connection, self._session, self._cacheDisk, self._cacheMem = values[
                2:9]
            self._screenSize = values[9] if values[
                0] == 'v2' else CommonPrefs.SZ_FULLSCREEN

    def valuesDict(self) -> gui.ValuesDictType:
        return {
            'useEmptyCreds': gui.boolToStr(self._useEmptyCreds),
            'fixedName': self._fixedName,
            'fixedPassword': self._fixedPassword,
            'listenPort': self._listenPort,
            'connection': self._connection,
            'session': self._session,
            'cacheDisk': self._cacheDisk,
            'cacheMem': self._cacheMem
        }

    def getUDSTransportScript(  # pylint: disable=too-many-locals
        self, userService: 'models.UserService', transport: 'models.Transport',
        ip: str, os: typing.Dict[str, str], user: '******', password: str,
        request: 'HttpRequest'
    ) -> typing.Tuple[str, str, typing.Dict[str, typing.Any]]:
        username = user.getUsernameForAuth()
        proc = username.split('@')
        username = proc[0]

        if self._fixedName:
            username = self._fixedName
        if self._fixedPassword:
            password = self._fixedPassword
        if self._useEmptyCreds:
            username, password = '', ''

        # We have the credentials right now, let os manager

        width, height = CommonPrefs.getWidthHeight(self._screenSize)

        # Fix username/password acording to os manager
        username, password = userService.processUserPassword(
            username, password)

        r = NXFile(username=username,
                   password=password,
                   width=width,
                   height=height)
        r.host = ip
        r.port = self._listenPort
        r.linkSpeed = self._connection
        r.desktop = self._session
        r.cachedisk = self._cacheDisk
        r.cachemem = self._cacheMem

        osName = {
            OsDetector.Windows: 'windows',
            OsDetector.Linux: 'linux',
            OsDetector.Macintosh: 'macosx'
        }.get(os['OS'])

        if osName is None:
            return super().getUDSTransportScript(userService, transport, ip,
                                                 os, user, password, request)

        sp = {
            'as_file': r.as_file,
        }

        return self.getScript('scripts/{}/direct.py', osName, sp)
Example #4
0
class WindowsOsManager(osmanagers.OSManager):
    typeName = _('Windows Basic OS Manager')
    typeType = 'WindowsManager'
    typeDescription = _('Os Manager to control windows machines without domain.')
    iconFile = 'wosmanager.png'
    servicesType = (serviceTypes.VDI,)

    onLogout = gui.ChoiceField(
        label=_('Logout Action'),
        order=10,
        rdonly=True,
        tooltip=_('What to do when user logs out from service'),
        values=[
            {'id': 'keep', 'text': ugettext_lazy('Keep service assigned')},
            {'id': 'remove', 'text': ugettext_lazy('Remove service')},
            {
                'id': 'keep-always',
                'text': ugettext_lazy('Keep service assigned even on new publication'),
            },
        ],
        defvalue='keep',
    )

    idle = gui.NumericField(
        label=_("Max.Idle time"),
        length=4,
        defvalue=-1,
        rdonly=False,
        order=11,
        tooltip=_(
            'Maximum idle time (in seconds) before session is automatically closed to the user (<= 0 means no max. idle time)'
        ),
        required=True,
    )
    _onLogout: str
    _idle: int

    @staticmethod
    def validateLen(length):
        try:
            length = int(length)
        except Exception:
            raise osmanagers.OSManager.ValidationException(
                _('Length must be numeric!!')
            )
        if length > 6 or length < 1:
            raise osmanagers.OSManager.ValidationException(
                _('Length must be betwen 1 and 6')
            )
        return length

    def __setProcessUnusedMachines(self):
        self.processUnusedMachines = self._onLogout == 'remove'

    def __init__(self, environment, values):
        super(WindowsOsManager, self).__init__(environment, values)
        if values is not None:
            self._onLogout = values['onLogout']
            self._idle = int(values['idle'])
        else:
            self._onLogout = ''
            self._idle = -1

        self.__setProcessUnusedMachines()

    def isRemovableOnLogout(self, userService: 'UserService') -> bool:
        """
        Says if a machine is removable on logout
        """
        if not userService.in_use:
            if (self._onLogout == 'remove') or (
                not userService.isValidPublication() and self._onLogout == 'keep'
            ):
                return True

        return False

    def release(self, userService: 'UserService') -> None:
        pass

    def getName(self, userService: 'UserService') -> str:
        return userService.getName()

    def infoVal(self, userService: 'UserService') -> str:
        return 'rename:' + self.getName(userService)

    def infoValue(self, userService: 'UserService') -> str:
        return 'rename\r' + self.getName(userService)

    def notifyIp(
        self, uid: str, userService, data: typing.Dict[str, typing.Any]
    ) -> None:
        userServiceInstance = userService.getInstance()

        ip = ''
        # Notifies IP to deployed
        for p in data['ips']:
            if p[0].lower() == uid.lower():
                userServiceInstance.setIp(p[1])
                ip = p[1]
                break

        self.logKnownIp(userService, ip)
        userService.updateData(userServiceInstance)

    def doLog(self, userService: 'UserService', data: str, origin=log.OSMANAGER):
        # Stores a log associated with this service
        try:
            msg, levelStr = data.split('\t')
            try:
                level = int(levelStr)
            except Exception:
                logger.debug('Do not understand level %s', levelStr)
                level = log.INFO

            log.doLog(userService, level, msg, origin)
        except Exception:
            logger.exception('WindowsOs Manager message log: ')
            log.doLog(
                userService, log.ERROR, "do not understand {0}".format(data), origin
            )

    # default "ready received" does nothing
    def readyReceived(self, userService, data):
        pass

    def loginNotified(self, userService, userName=None):
        if '\\' not in userName:
            osmanagers.OSManager.loggedIn(userService, userName)

    def logoutNotified(self, userService, userName=None):
        osmanagers.OSManager.loggedOut(userService, userName)
        if self.isRemovableOnLogout(userService):
            userService.release()

    def readyNotified(self, userService):
        return

    def actorData(
        self, userService: 'UserService'
    ) -> typing.MutableMapping[str, typing.Any]:
        return {'action': 'rename', 'name': userService.getName()}

    def process(
        self,
        userService: 'UserService',
        message: str,
        data: typing.Any,
        options: typing.Optional[typing.Dict[str, typing.Any]] = None,
    ) -> str:  # pylint: disable=too-many-branches
        """
        We understand this messages:
        * msg = info, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class) (old method)
        * msg = information, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class) (new method)
        * msg = logon, data = Username, Informs that the username has logged in inside the machine
        * 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 %s with params: %s,%s",
            userService,
            message,
            data,
        )

        if message in ('ready', 'ip') and not isinstance(
            data, dict
        ):  # Old actors, previous to 2.5, convert it information..
            data = {
                'ips': [v.split('=') for v in data.split(',')],
                'hostname': userService.friendly_name,
            }

        # 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 = userService.os_state
        if message == "info":
            ret = self.infoVal(userService)
            state = State.PREPARING
        elif message == "information":
            ret = self.infoValue(userService)
            state = State.PREPARING
        elif message == "log":
            self.doLog(userService, str(data), log.ACTOR)
        elif message in ("logon", 'login'):
            if '\\' not in data:
                osmanagers.OSManager.loggedIn(userService, str(data))
            userService.setInUse(True)
            # We get the userService logged hostname & ip and returns this
            ip, hostname = userService.getConnectionSource()
            deadLine = userService.deployed_service.getDeadline()
            if (
                typing.cast(str, userService.getProperty('actor_version', '0.0.0'))
                >= '2.0.0'
            ):
                ret = "{0}\t{1}\t{2}".format(
                    ip, hostname, 0 if deadLine is None else deadLine
                )
            else:
                ret = "{0}\t{1}".format(ip, hostname)
        elif message in ('logoff', 'logout'):
            osmanagers.OSManager.loggedOut(userService, str(data))
            doRemove = self.isRemovableOnLogout(userService)
        elif message == "ip":
            # This ocurss on main loop inside machine, so userService is usable
            state = State.USABLE
            self.notifyIp(userService.unique_id, userService, data)
        elif message == "ready":
            self.toReady(userService)
            state = State.USABLE
            notifyReady = True
            self.notifyIp(userService.unique_id, userService, data)
            self.readyReceived(userService, data)

        userService.setOsState(state)

        # If notifyReady is not true, save state, let UserServiceManager do it for us else
        if doRemove is True:
            userService.release()
        else:
            if notifyReady is False:
                userService.save(
                    update_fields=['in_use', 'in_use_date', 'os_state', 'state', 'data']
                )
            else:
                logger.debug('Notifying ready')
                userServiceManager().notifyReadyFromOsManager(userService, '')
        logger.debug('Returning %s to %s message', ret, message)
        if options is not None and options.get('scramble', True) is False:
            return ret
        return scrambleMsg(ret)

    def processUserPassword(
        self, userService: 'UserService', username: str, password: str
    ) -> typing.Tuple[str, str]:
        if userService.getProperty('sso_available') == '1':
            # Generate a ticket, store it and return username with no password
            domain = ''
            if '@' in username:
                username, domain = username.split('@')
            elif '\\' in username:
                username, domain = username.split('\\')

            creds = {'username': username, 'password': password, 'domain': domain}
            ticket = TicketStore.create(
                creds, validatorFnc=None, validity=300
            )  # , owner=SECURE_OWNER, secure=True)
            return ticket, ''

        return osmanagers.OSManager.processUserPassword(
            self, userService, username, password
        )

    def processUnused(self, userService: 'UserService') -> None:
        """
        This will be invoked for every assigned and unused user service that has been in this state at least 1/2 of Globalconfig.CHECK_UNUSED_TIME
        This function can update userService values. Normal operation will be remove machines if this state is not valid
        """
        if self.isRemovableOnLogout(userService):
            log.doLog(
                userService,
                log.INFO,
                'Unused user service for too long. Removing due to OS Manager parameters.',
                log.OSMANAGER,
            )
            userService.remove()

    def isPersistent(self):
        return self._onLogout == 'keep-always'

    def checkState(self, userService: 'UserService') -> str:
        # will alway return true, because the check is done by an actor callback
        logger.debug('Checking state for service %s', userService)
        return State.RUNNING

    def maxIdle(self):
        """
        On production environments, will return no idle for non removable machines
        """
        if (
            self._idle <= 0
        ):  # or (settings.DEBUG is False and self._onLogout != 'remove'):
            return None

        return self._idle

    def marshal(self) -> bytes:
        """
        Serializes the os manager data so we can store it in database
        """
        return '\t'.join(['v2', self._onLogout, str(self._idle)]).encode('utf8')

    def unmarshal(self, data: bytes) -> None:
        vals = data.decode('utf8').split('\t')
        try:
            if vals[0] == 'v1':
                self._onLogout = vals[1]
                self._idle = -1
            elif vals[0] == 'v2':
                self._onLogout, self._idle = vals[1], int(vals[2])
        except Exception:
            logger.exception(
                'Exception unmarshalling. Some values left as default ones'
            )

        self.__setProcessUnusedMachines()

    def valuesDict(self) -> gui.ValuesDictType:
        return {'onLogout': self._onLogout, 'idle': str(self._idle)}
Example #5
0
class ProxmoxLinkedService(Service):  # pylint: disable=too-many-public-methods
    """
    Proxmox Linked clones service. This is based on creating a template from selected vm, and then use it to
    """
    # : 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 = _('Proxmox Linked Clone')
    # : Type used internally to identify this provider
    typeType = 'ProxmoxLinkedService'
    # : Description shown at administration interface for this provider
    typeDescription = _('Proxmox Services based on templates and COW (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 = 'service.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 we need to generate a "Level 2" cache for this service (i.e., L1
    # : could be running machines and L2 suspended machines)
    usesCache_L2 = True
    # : Tooltip shown to user when this item is pointed at admin interface, None
    # : also because we don't use it
    cacheTooltip_L2 = _('Number of desired VMs to keep stopped waiting for use')

    # : If the service needs a s.o. manager (managers are related to agents
    # : provided by services itselfs, i.e. virtual machines with actors)
    needsManager = True
    # : If true, the system can't do an automatic assignation of a deployed user
    # : service from this service
    mustAssignManually = False
    canReset = True

    # : Types of publications (preparated data for deploys)
    # : In our case, we do no need a publication, so this is None
    publicationType = ProxmoxPublication
    # : Types of deploys (services in cache and/or assigned to users)
    deployedType = ProxmoxDeployment

    allowedProtocols = protocols.GENERIC # + (protocols.SPICE,)
    servicesTypeProvided = (serviceTypes.VDI,)

    pool = gui.ChoiceField(
        label=_("Pool"),
        order=1,
        tooltip=_('Pool that will contain UDS created vms'),
        # tab=_('Machine'),
        # required=True,
        defvalue=''
    )

    ha = gui.ChoiceField(
        label=_('HA'),
        order=2,
        tooltip=_('Select if HA is enabled and HA group for machines of this service'),
        rdonly=True
    )

    machine = gui.ChoiceField(
        label=_("Base Machine"),
        order=110,
        fills={
            'callbackName': 'pmFillResourcesFromMachine',
            'function': helpers.getStorage,
            'parameters': ['machine', 'ov', 'ev']
        },
        tooltip=_('Service base machine'),
        tab=_('Machine'),
        required=True
    )

    datastore = gui.ChoiceField(
        label=_("Storage"),
        rdonly=False,
        order=111,
        tooltip=_('Storage for publications & machines.'),
        tab=_('Machine'),
        required=True
    )

    baseName = gui.TextField(
        label=_('Machine Names'),
        rdonly=False,
        order=115,
        tooltip=_('Base name for clones from this machine'),
        tab=_('Machine'),
        required=True
    )

    lenName = gui.NumericField(
        length=1,
        label=_('Name Length'),
        defvalue=5,
        order=116,
        tooltip=_('Size of numeric part for the names of these machines'),
        tab=_('Machine'),
        required=True
    )

    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: 'Module.ValuesType') -> None:
        if values:
            self.baseName.value = validators.validateHostname(self.baseName.value, 15, asPattern=True)
            # if int(self.memory.value) < 128:
            #     raise Service.ValidationException(_('The minimum allowed memory is 128 Mb'))

    def initGui(self) -> None:
        # Here we have to use "default values", cause values aren't used at form initialization
        # This is that value is always '', so if we want to change something, we have to do it
        # at defValue
        self.ov.defValue = self.parent().serialize()
        self.ev.defValue = self.parent().env.key

        # 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.machine.setValues([gui.choiceItem(str(m.vmid), '{}\\{}'.format(m.node, m.name or m.vmid)) for m in self.parent().listMachines() if m.name and m.name[:3] != 'UDS'])
        self.pool.setValues([gui.choiceItem('', _('None'))] + [gui.choiceItem(p.poolid, p.poolid) for p in self.parent().listPools()])
        self.ha.setValues(
            [
                gui.choiceItem('', _('Enabled')), gui.choiceItem('__', _('Disabled'))
            ] + 
            [
                gui.choiceItem(group, group) for group in self.parent().listHaGroups()
            ]
        )

    def parent(self) -> 'ProxmoxProvider':
        return typing.cast('ProxmoxProvider', super().parent())

    def sanitizeVmName(self, name: str) -> str:
        """
        Proxmox only allows machine names with [a-zA-Z0-9_-]
        """
        return re.sub("[^a-zA-Z0-9_-]", "-", name)

    def makeTemplate(self, vmId: int) -> None:
        self.parent().makeTemplate(vmId)

    def cloneMachine(self, name: str, description: str, vmId: int = -1) -> 'client.types.VmCreationResult':
        name = self.sanitizeVmName(name)
        pool = self.pool.value or None
        if vmId == -1:  # vmId == -1 if cloning for template
            return self.parent().cloneMachine(
                self.machine.value,
                name,
                description,
                linkedClone=False,
                toStorage=self.datastore.value,
                toPool=pool
            )

        return self.parent().cloneMachine(
            vmId,
            name,
            description,
            linkedClone=True,
            toStorage=self.datastore.value,
            toPool=pool
        )

    def getMachineInfo(self, vmId: int) -> 'client.types.VMInfo':
        return self.parent().getMachineInfo(vmId)

    def getMac(self, vmId: int) -> str:
        config = self.parent().getMachineConfiguration(vmId)
        return config.networks[0].mac

    def getTaskInfo(self, node: str, upid: str) -> 'client.types.TaskStatus':
        return self.parent().getTaskInfo(node, upid)

    def startMachine(self,vmId: int) -> 'client.types.UPID':
        return self.parent().startMachine(vmId)

    def stopMachine(self, vmId: int) -> 'client.types.UPID':
        return self.parent().stopMachine(vmId)

    def resetMachine(self, vmId: int) -> 'client.types.UPID':
        return self.parent().resetMachine(vmId)

    def suspendMachine(self, vmId: int) -> 'client.types.UPID':
        return self.parent().suspendMachine(vmId)

    def shutdownMachine(self, vmId: int) -> 'client.types.UPID':
        return self.parent().shutdownMachine(vmId)

    def removeMachine(self, vmId: int) -> 'client.types.UPID':
        # First, remove from HA if needed
        self.disableHA(vmId)
        # And remove it
        return self.parent().removeMachine(vmId)

    def enableHA(self, vmId: int, started: bool = False) -> None:
        if self.ha.value == '__':
            return
        self.parent().enableHA(vmId, started, self.ha.value or None)

    def disableHA(self, vmId: int) -> None:
        if self.ha.value == '__':
            return
        self.parent().disableHA(vmId)

    def setProtection(self, vmId: int, node: typing.Optional[str] = None, protection: bool=False) -> None:
        self.parent().setProtection(vmId, node, protection)

    def getBaseName(self) -> str:
        return self.baseName.value

    def getLenName(self) -> int:
        return int(self.lenName.value)

    def isHaEnabled(self) -> bool:
        return self.ha.isTrue()

    def getConsoleConnection(self, machineId: str) -> typing.Optional[typing.MutableMapping[str, typing.Any]]:
        return self.parent().getConsoleConnection(machineId)
Example #6
0
class LiveService(Service):
    """
    OpenStack 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 = _('OpenStack Live Volume')
    # : Type used internally to identify this provider
    typeType = 'openStackLiveService'
    # : Description shown at administration interface for this provider
    typeDescription = _('OpenStack live images based 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 = 'openstack.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')

    usesCache_L2 = True  # L2 Cache are running machines in suspended state
    cacheTooltip_L2 = _('Number of desired machines to keep suspended waiting for use')
    # : If the service needs a s.o. manager (managers are related to agents
    # : provided by services itselfs, i.e. virtual machines with actors)
    needsManager = True
    # : If true, the system can't do an automatic assignation of a deployed user
    # : service from this service
    mustAssignManually = False
    canReset = True

    # : 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
    region = gui.ChoiceField(label=_('Region'), order=1, tooltip=_('Service region'), required=True, rdonly=True)
    project = gui.ChoiceField(label=_('Project'), order=2,
        fills={
            'callbackName' : 'osFillResources',
            'function' : helpers.getResources,
            'parameters' : ['ov', 'ev', 'project', 'region', 'legacy']
            },
        tooltip=_('Project for this service'), required=True, rdonly=True
    )
    availabilityZone = gui.ChoiceField(label=_('Availability Zones'), order=3,
        fills={
            'callbackName' : 'osFillVolumees',
            'function' : helpers.getVolumes,
            'parameters' : ['ov', 'ev', 'project', 'region', 'availabilityZone', 'legacy']
            },
        tooltip=_('Service availability zones'), required=True, rdonly=True
    )
    volume = gui.ChoiceField(label=_('Volume'), order=4, tooltip=_('Base volume for service (restricted by availability zone)'), required=True, tab=_('Machine'))
    # volumeType = gui.ChoiceField(label=_('Volume Type'), order=5, tooltip=_('Volume type for service'), required=True)
    network = gui.ChoiceField(label=_('Network'), order=6, tooltip=_('Network to attach to this service'), required=True, tab=_('Machine'))
    flavor = gui.ChoiceField(label=_('Flavor'), order=7, tooltip=_('Flavor for service'), required=True, tab=_('Machine'))

    securityGroups = gui.MultiChoiceField(label=_('Security Groups'), order=8, tooltip=_('Service security groups'), required=True, tab=_('Machine'))

    baseName = gui.TextField(
        label=_('Machine Names'),
        rdonly=False,
        order=9,
        tooltip=_('Base name for clones from this machine'),
        required=True,
        tab=_('Machine')
    )

    lenName = gui.NumericField(
        length=1,
        label=_('Name Length'),
        defvalue=5,
        order=10,
        tooltip=_('Size of numeric part for the names of these machines (between 3 and 6)'),
        required=True,
        tab=_('Machine')
    )

    ov = gui.HiddenField(value=None)
    ev = gui.HiddenField(value=None)
    legacy = 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 through 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'))

        # self.ov.value = self.parent().serialize()
        # self.ev.value = self.parent().env.key

        self._api = None

    def initGui(self):
        """
        Loads required values inside
        """
        api = self.parent().api()
        regions = [gui.choiceItem(r['id'], r['id']) for r in api.listRegions()]
        self.region.setValues(regions)

        tenants = [gui.choiceItem(t['id'], t['name']) for t in api.listProjects()]
        self.project.setValues(tenants)

        # So we can instantiate parent to get API
        logger.debug(self.parent().serialize())

        self.ov.setDefValue(self.parent().serialize())
        self.ev.setDefValue(self.parent().env.key)
        self.legacy.setDefValue(self.parent().legacy and 'true' or 'false')

    @property
    def api(self):
        if self._api is None:
            self._api = self.parent().api(projectId=self.project.value, region=self.region.value)

        return self._api

    def sanitizeVmName(self, name):
        return self.parent().sanitizeVmName(name)

    def makeTemplate(self, templateName, description=None):
        # First, ensures that volume has not any running instances
        # if self.api.getVolume(self.volume.value)['status'] != 'available':
        #    raise Exception('The Volume is in use right now. Ensure that there is no machine running before publishing')

        description = 'UDS Template snapshot' if description is None else description
        return self.api.createVolumeSnapshot(self.volume.value, templateName, description)

    def getTemplate(self, snapshotId):
        """
        Checks current state of a template (an snapshot)
        """
        return self.api.getSnapshot(snapshotId)

    def deployFromTemplate(self, name, snapshotId):
        """
        Deploys a virtual machine on selected cluster from selected template

        Args:
            name: Name (sanitized) of the machine
            comments: Comments for machine
            snapshotId: Id of the snapshot to deploy from

        Returns:
            Id of the machine being created form template
        """
        logger.debug('Deploying from template {0} machine {1}'.format(snapshotId, name))
        # self.datastoreHasSpace()
        return self.api.createServerFromSnapshot(snapshotId=snapshotId,
                                          name=name,
                                          availabilityZone=self.availabilityZone.value,
                                          flavorId=self.flavor.value,
                                          networkId=self.network.value,
                                          securityGroupsIdsList=self.securityGroups.value)['id']

    def removeTemplate(self, templateId):
        """
        invokes removeTemplate from parent provider
        """
        self.api.deleteSnapshot(templateId)

    def getMachineState(self, machineId):
        """
        Invokes getServer from openstack client

        Args:
            machineId: If of the machine to get state

        Returns:
            one of this values:
                ACTIVE. The server is active.
                BUILDING. The server has not finished the original build process.
                DELETED. The server is permanently deleted.
                ERROR. The server is in error.
                HARD_REBOOT. The server is hard rebooting. This is equivalent to pulling the power plug on a physical server, plugging it back in, and rebooting it.
                MIGRATING. The server is being migrated to a new host.
                PASSWORD. The password is being reset on the server.
                PAUSED. In a paused state, the state of the server is stored in RAM. A paused server continues to run in frozen state.
                REBOOT. The server is in a soft reboot state. A reboot command was passed to the operating system.
                REBUILD. The server is currently being rebuilt from an image.
                RESCUED. The server is in rescue mode. A rescue image is running with the original server image attached.
                RESIZED. Server is performing the differential copy of data that changed during its initial copy. Server is down for this stage.
                REVERT_RESIZE. The resize or migration of a server failed for some reason. The destination server is being cleaned up and the original source server is restarting.
                SOFT_DELETED. The server is marked as deleted but the disk images are still available to restore.
                STOPPED. The server is powered off and the disk image still persists.
                SUSPENDED. The server is suspended, either by request or necessity. This status appears for only the XenServer/XCP, KVM, and ESXi hypervisors. Administrative users can suspend an instance if it is infrequently used or to perform system maintenance. When you suspend an instance, its VM state is stored on disk, all memory is written to disk, and the virtual machine is stopped. Suspending an instance is similar to placing a device in hibernation; memory and vCPUs become available to create other instances.
                VERIFY_RESIZE. System is awaiting confirmation that the server is operational after a move or resize.
        """
        return self.api.getServer(machineId)['status']

    def startMachine(self, machineId):
        """
        Tries to start a machine. No check is done, it is simply requested to OpenStack.

        This start also "resume" suspended/paused machines

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.api.startServer(machineId)

    def stopMachine(self, machineId):
        """
        Tries to stop a machine. No check is done, it is simply requested to OpenStack

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.api.stopServer(machineId)

    def resetMachine(self, machineId):
        """
        Tries to stop a machine. No check is done, it is simply requested to OpenStack

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.api.resetServer(machineId)

    def suspendMachine(self, machineId):
        """
        Tries to suspend a machine. No check is done, it is simply requested to OpenStack

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.api.suspendServer(machineId)

    def resumeMachine(self, machineId):
        """
        Tries to start a machine. No check is done, it is simply requested to OpenStack

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.api.resumeServer(machineId)

    def removeMachine(self, machineId):
        """
        Tries to delete a machine. No check is done, it is simply requested to OpenStack

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.api.deleteServer(machineId)

    def getNetInfo(self, machineId):
        """
        Gets the mac address of first nic of the machine
        """
        net = self.api.getServer(machineId)['addresses']
        vals = six.next(six.itervalues(net))[0]  # Returns "any" mac address of any interface. We just need only one interface info
        return vals['OS-EXT-IPS-MAC:mac_addr'].upper(), vals['addr']

    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)
Example #7
0
class HTML5RDPTransport(transports.Transport):
    """
    Provides access via RDP to service.
    This transport can use an domain. If username processed by authenticator contains '@', it will split it and left-@-part will be username, and right password
    """
    typeName = _('HTML5 RDP')
    typeType = 'HTML5RDPTransport'
    typeDescription = _('RDP protocol using HTML5 client')
    iconFile = 'html5.png'

    ownLink = True
    supportedOss = OsDetector.allOss
    protocol = transports.protocols.RDP
    group = transports.TUNNELED_GROUP

    guacamoleServer = gui.TextField(label=_('Tunnel Server'), order=1, tooltip=_('Host of the tunnel server (use http/https & port if needed) as accesible from users'), defvalue='https://', length=64, required=True, tab=gui.TUNNEL_TAB)
    useEmptyCreds = gui.CheckBoxField(label=_('Empty creds'), order=2, tooltip=_('If checked, the credentials used to connect will be emtpy'), tab=gui.CREDENTIALS_TAB)
    fixedName = gui.TextField(label=_('Username'), order=3, tooltip=_('If not empty, this username will be always used as credential'), tab=gui.CREDENTIALS_TAB)
    fixedPassword = gui.PasswordField(label=_('Password'), order=4, tooltip=_('If not empty, this password will be always used as credential'), tab=gui.CREDENTIALS_TAB)
    withoutDomain = gui.CheckBoxField(label=_('Without Domain'), order=5, tooltip=_('If checked, the domain part will always be emptied (to connecto to xrdp for example is needed)'), tab=gui.CREDENTIALS_TAB)
    fixedDomain = gui.TextField(label=_('Domain'), order=6, tooltip=_('If not empty, this domain will be always used as credential (used as DOMAIN\\user)'), tab=gui.CREDENTIALS_TAB)
    wallpaper = gui.CheckBoxField(label=_('Show wallpaper'), order=20, tooltip=_('If checked, the wallpaper and themes will be shown on machine (better user experience, more bandwidth)'), tab=gui.PARAMETERS_TAB)
    desktopComp = gui.CheckBoxField(label=_('Allow Desk.Comp.'), order=22, tooltip=_('If checked, desktop composition will be allowed'), tab=gui.PARAMETERS_TAB)
    smooth = gui.CheckBoxField(label=_('Font Smoothing'), order=23, tooltip=_('If checked, fonts smoothing will be allowed (windows clients only)'), tab=gui.PARAMETERS_TAB)
    enableAudio = gui.CheckBoxField(label=_('Enable Audio'), order=24, tooltip=_('If checked, the audio will be redirected to client (if client browser supports it)'), tab=gui.PARAMETERS_TAB)
    enablePrinting = gui.CheckBoxField(label=_('Enable Printing'), order=25, tooltip=_('If checked, the printing will be redirected to client (if client browser supports it)'), tab=gui.PARAMETERS_TAB)
    enableFileSharing = gui.CheckBoxField(label=_('Enable File Sharing'), order=8, tooltip=_('If checked, the user will be able to upload/download files (if client browser supports it)'), tab=gui.PARAMETERS_TAB)
    serverLayout = gui.ChoiceField(
        order=26,
        label=_('Layout'),
        tooltip=_('Keyboards Layout of server'),
        required=True,
        values=[
            gui.choiceItem('-', 'default'),
            gui.choiceItem('en-us-qwerty', _('English (US) keyboard')),
            gui.choiceItem('en-gb-qwerty', _('English (GB) keyboard')),
            gui.choiceItem('es-es-qwerty', _('Spanish keyboard')),
            gui.choiceItem('latam-qwerty', _('Latin American keyboard')),
            gui.choiceItem('de-de-qwertz', _('German keyboard (qwertz)')),
            gui.choiceItem('fr-fr-azerty', _('French keyboard (azerty)')),
            gui.choiceItem('fr-ch-qwertz', _('Swiss French keyboard (qwertz)')),
            gui.choiceItem('de-ch-qwertz', _('Swiss German keyboard (qwertz)')),
            gui.choiceItem('it-it-qwerty', _('Italian keyboard')),
            gui.choiceItem('sv-se-qwerty', _('Swedish keyboard')),
            gui.choiceItem('ja-jp-qwerty', _('Japanese keyboard')),
            gui.choiceItem('pt-br-qwerty', _('Brazilian keyboard')),
            gui.choiceItem('failsafe', _('Failsafe')),
        ],
        defvalue='-',
        tab=gui.PARAMETERS_TAB
    )
    security = gui.ChoiceField(
        order=27,
        label=_('Security'),
        tooltip=_('Connection security mode for Guacamole RDP connection'),
        required=True,
        values=[
            gui.choiceItem('any', _('Any (Allow the server to choose the type of auth)')),
            gui.choiceItem('rdp', _('RDP (Standard RDP encryption. Should be supported by all servers)')),
            gui.choiceItem('nla', _('NLA (Network Layer authentication. Requires VALID username&password, or connection will fail)')),
            gui.choiceItem('tls', _('TLS (Transport Security Layer encryption)')),
        ],
        defvalue='rdp',
        tab=gui.PARAMETERS_TAB
    )

    ticketValidity = gui.NumericField(
        length=3,
        label=_('Ticket Validity'),
        defvalue='60',
        order=90,
        tooltip=_('Allowed time, in seconds, for HTML5 client to reload data from UDS Broker. The default value of 60 is recommended.'),
        required=True,
        minValue=60,
        tab=gui.ADVANCED_TAB
    )
    forceNewWindow = gui.CheckBoxField(
        label=_('Force new HTML Window'),
        order=91,
        tooltip=_('If checked, every connection will try to open its own window instead of reusing the "global" one.'),
        defvalue=gui.FALSE,
        tab=gui.ADVANCED_TAB
    )

    def initialize(self, values: 'Module.ValuesType'):
        if not values:
            return
        # Strip spaces
        self.guacamoleServer.value = self.guacamoleServer.value.strip()
        if self.guacamoleServer.value[0:4] != 'http':
            raise transports.Transport.ValidationException(_('The server must be http or https'))
        if self.useEmptyCreds.isTrue() and self.security.value != 'rdp':
            raise transports.Transport.ValidationException(_('Empty credentials (on Credentials tab) is only allowed with Security level (on Parameters tab) set to "RDP"'))

    # Same check as normal RDP transport
    def isAvailableFor(self, userService: 'models.UserService', ip: str) -> bool:
        """
        Checks if the transport is available for the requested destination ip
        Override this in yours transports
        """
        logger.debug('Checking availability for %s', ip)
        ready = self.cache.get(ip)
        if not ready:
            # Check again for readyness
            if self.testServer(userService, ip, '3389') is True:
                self.cache.put(ip, 'Y', READY_CACHE_TIMEOUT)
                return True
            self.cache.put(ip, 'N', READY_CACHE_TIMEOUT)
        return ready == 'Y'

    def processedUser(self, userService: 'models.UserService', user: '******') -> str:
        v = self.processUserAndPassword(userService, user, '')
        return v['username']

    def processUserAndPassword(self, userService: 'models.UserService', user: '******', password: str) -> typing.Dict[str, str]:
        username: str = user.getUsernameForAuth()

        if self.fixedName.value != '':
            username = self.fixedName.value

        proc = username.split('@')
        domain = proc[1] if len(proc) > 1 else ''
        username = proc[0]

        if self.fixedPassword.value != '':
            password = self.fixedPassword.value

        if self.fixedDomain.value != '':
            domain = self.fixedDomain.value

        if self.useEmptyCreds.isTrue():
            username, password, domain = '', '', ''

        # If no domain to be transfered, set it to ''
        if self.withoutDomain.isTrue():
            domain = ''

        if '.' in domain:  # Dotter domain form
            username = username + '@' + domain
            domain = ''

        # Fix username/password acording to os manager
        username, password = userService.processUserPassword(username, password)

        return {'protocol': self.protocol, 'username': username, 'password': password, 'domain': domain}

    def getLink(  # pylint: disable=too-many-locals
            self,
            userService: 'models.UserService',
            transport: 'models.Transport',
            ip: str,
            os: typing.Dict[str, str],
            user: '******',
            password: str,
            request: 'HttpRequest'
        ) -> str:
        credsInfo = self.processUserAndPassword(userService, user, password)
        username, password, domain = credsInfo['username'], credsInfo['password'], credsInfo['domain']

        if domain != '':
            username = domain + '\\' + username

        scrambler = cryptoManager().randomString(32)
        passwordCrypted = cryptoManager().symCrypt(password, scrambler)

        # Build params dict
        params = {
            'protocol': 'rdp',
            'hostname': ip,
            'username': username,
            'password': passwordCrypted,
            'ignore-cert': 'true',
            'security': self.security.value,
            'drive-path': '/share/{}'.format(user.uuid),
            'create-drive-path': 'true'
        }

        if self.enableFileSharing.isTrue():
            params['enable-drive'] = 'true'

        if self.serverLayout.value != '-':
            params['server-layout'] = self.serverLayout.value

        if self.enableAudio.isTrue() is False:
            params['disable-audio'] = 'true'

        if self.enablePrinting.isTrue() is True:
            params['enable-printing'] = 'true'
            params['printer-name'] = 'UDS-Printer'

        if self.wallpaper.isTrue() is True:
            params['enable-wallpaper'] = 'true'

        if self.desktopComp.isTrue() is True:
            params['enable-desktop-composition'] = 'true'

        if self.smooth.isTrue() is True:
            params['enable-font-smoothing'] = 'true'

        logger.debug('RDP Params: %s', params)

        ticket = models.TicketStore.create(params, validity=self.ticketValidity.num())

        return HttpResponseRedirect(
            "{}/transport/{}?{}.{}&{}".format(
                self.guacamoleServer.value,
                'o_n_w' if self.forceNewWindow.isTrue() else '',
                ticket,
                scrambler,
                'javascript:window.close();'
            )
        )
Example #8
0
class ServiceOne(Service):
    '''
    Basic service, the first part (variables) include the description of the service.
    
    Remember to fill all variables needed, but at least you must define:
        * typeName
        * typeType
        * typeDescription
        * iconFile (defaults to service.png)
        * publicationType, type of publication in case it needs publication. 
          If this is not provided, core will assume that the service do not 
          needs publishing.
        * deployedType, type of deployed user service. Do not forget this!!!
        
    The rest of them can be ommited, but its recommended that you fill all
    declarations shown in this sample (that in fact, are all)
    
    This description informs the core what this service really provides,
    and how this is done. Look at description of class variables for more
    information.
    
    '''
    #: Name to show the administrator. This string will be translated BEFORE
    #: sending it to administration interface, so don't forget to
    #: mark it as _ (using ugettext_noop)
    typeName = _('Sample Service One')
    #: Type used internally to identify this provider
    typeType = 'SampleService1'
    #: Description shown at administration interface for this provider
    typeDescription = _('Sample (and dummy) service ONE')
    #: 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 = 'service.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 = False
    #: Tooltip shown to user when this item is pointed at admin interface, none
    #: because we don't use it
    cacheTooltip = _('None')
    #: If we need to generate a "Level 2" cache for this service (i.e., L1
    #: could be running machines and L2 suspended machines)
    usesCache_L2 = False
    #: Tooltip shown to user when this item is pointed at admin interface, None
    #: also because we don't use it
    cacheTooltip_L2 = _('None')

    #: 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 = None
    #: Types of deploys (services in cache and/or assigned to users)
    deployedType = SampleUserDeploymentOne

    # Now the form part, this service will have only two "dummy" fields
    # If we don't indicate an order, the output order of fields will be
    # "random"

    colour = gui.ChoiceField(
        order=1,
        label=_('Colour'),
        tooltip=_('Colour of the field'),
        # In this case, the choice can have none value selected by default
        required=True,
        values=[
            gui.choiceItem('red', 'Red'),
            gui.choiceItem('green', 'Green'),
            gui.choiceItem('blue', 'Blue'),
            gui.choiceItem('nonsense', 'Blagenta')
        ],
        defvalue='1'  # Default value is the ID of the choicefield
    )

    passw = gui.PasswordField(
        order=2,
        label=_('Password'),
        tooltip=_('Password for testing purposes'),
        required=True,
        defvalue='1234'  #: Default password are nonsense?? :-)
    )

    baseName = gui.TextField(
        order=3,
        label=_('Services names'),
        tooltip=_('Base name for this user services'),
        # In this case, the choice can have none value selected by default
        required=True,
        defvalue=''  # Default value is the ID of the choicefield
    )

    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.
        '''

        # We don't need to check anything, bat because this is a sample, we do
        # As in provider, we receive values only at new Service creation,
        # so we only need to validate params if values is not None
        if values is not None:
            if self.colour.value == 'nonsense':
                raise Service.ValidationException(
                    'The selected colour is invalid!!!')

    # Services itself are non testeable right now, so we don't even have
    # to provide one!!!

    # Congratulations!!!, the needed part of your first simple service is done!
    # Now you can go to administration panel, and check it
    #
    # From now onwards, we implement our own methods, that will be used by,
    # for example, services derived from this provider

    def getColour(self):
        '''
        Simply returns colour, for deployed user services.
        
        Remember that choiceField.value returns the id part of the ChoiceItem
        '''
        return self.colour.value

    def getPassw(self):
        '''
        Simply returns passwd, for deloyed user services
        '''
        return self.passw.value

    def getBaseName(self):
        '''
        '''
        return self.baseName.value
Example #9
0
class OpenStackProvider(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 = _('OpenStack Platform Provider')
    # : Type used internally to identify this provider
    typeType = 'openStackPlatformNew'
    # : Description shown at administration interface for this provider
    typeDescription = _('OpenStack 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 = 'openstack.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"
    endpoint = gui.TextField(
        length=128,
        label=_('Identity endpoint'),
        order=1,
        tooltip=_(
            'OpenStack identity endpoint API Access (for example, https://10.0.0.1/identity). Do not include /v3.'
        ),
        required=True,
    )

    access = gui.ChoiceField(
        label=_('Access interface'),
        order=5,
        tooltip=_('Access interface to be used'),
        values=INTERFACE_VALUES,
        defvalue='public',
    )

    domain = gui.TextField(
        length=64,
        label=_('Domain'),
        order=8,
        tooltip=_('Domain name (default is Default)'),
        required=True,
        defvalue='Default',
    )
    username = gui.TextField(
        length=64,
        label=_('Username'),
        order=9,
        tooltip=_('User with valid privileges on OpenStack'),
        required=True,
        defvalue='admin',
    )
    password = gui.PasswordField(
        lenth=32,
        label=_('Password'),
        order=10,
        tooltip=_('Password of the user of OpenStack'),
        required=True,
    )

    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',
        minValue=1,
        maxValue=128,
        order=99,
        tooltip=_('Timeout in seconds of connection to OpenStack'),
        required=True,
        tab=gui.ADVANCED_TAB,
    )

    tenant = gui.TextField(
        length=64,
        label=_('Project Id'),
        order=6,
        tooltip=_(
            'Project (tenant) for this provider. Set only if required by server.'
        ),
        required=False,
        defvalue='',
        tab=gui.ADVANCED_TAB,
    )
    region = gui.TextField(
        length=64,
        label=_('Region'),
        order=7,
        tooltip=_('Region for this provider. Set only if required by server.'),
        required=False,
        defvalue='',
        tab=gui.ADVANCED_TAB,
    )

    useSubnetsName = gui.CheckBoxField(
        label=_('Subnets names'),
        order=8,
        tooltip=_(
            'If checked, the name of the subnets will be used instead of the names of networks'
        ),
        defvalue=gui.FALSE,
        tab=gui.ADVANCED_TAB,
    )

    httpsProxy = gui.TextField(
        length=96,
        label=_('Proxy'),
        order=91,
        tooltip=_('Proxy used for connection to azure for HTTPS connections (use PROTOCOL://host:port, i.e. http://10.10.0.1:8080)'),
        required=False,
        tab=gui.ADVANCED_TAB,
    )

    legacy = False

    # Own variables
    _api: typing.Optional[openstack.Client] = None

    def initialize(self, values: 'Module.ValuesType' = None):
        """
        We will use the "autosave" feature for form fields
        """
        self._api = None

        if values is not None:
            self.timeout.value = validators.validateTimeout(self.timeout.value)

    def api(self, projectId=None, region=None) -> openstack.Client:
        projectId = projectId or self.tenant.value or None
        region = region or self.region.value or None
        if self._api is None:
            proxies = None
            if self.httpsProxy.value.strip():
                proxies = {
                    'https': self.httpsProxy.value
                }
            self._api = openstack.Client(
                self.endpoint.value,
                -1,
                self.domain.value,
                self.username.value,
                self.password.value,
                legacyVersion=False,
                useSSL=False,
                projectId=projectId,
                region=region,
                access=self.access.value,
                proxies=proxies
            )
        return self._api

    def sanitizeVmName(self, name: str) -> str:
        return openstack.sanitizeName(name)

    def testConnection(self):
        """
        Test that conection to OpenStack server is fine

        Returns

            True if all went fine, false if id didn't
        """
        logger.debug('Testing connection to OpenStack')
        try:
            if self.api().testConnection() is False:
                raise Exception('Check connection credentials, server, etc.')
        except Exception as e:
            logger.exception('Error')
            return [False, '{}'.format(e)]

        return [True, _('OpenStack 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 OpenStackProvider(env, data).testConnection()
Example #10
0
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 based 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 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 = 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
    datastore = gui.ChoiceField(label=_("Datastore"),
                                order=100,
                                tooltip=_('Service clones datastore'),
                                required=True)

    template = gui.ChoiceField(label=_("Base Template"),
                               order=110,
                               tooltip=_('Service base template'),
                               tab=_('Machine'),
                               required=True)

    baseName = gui.TextField(
        label=_('Machine Names'),
        rdonly=False,
        order=111,
        tooltip=('Base name for clones from this machine'),
        tab=_('Machine'),
        required=True)

    lenName = gui.NumericField(
        length=1,
        label=_('Name Length'),
        defvalue=5,
        order=112,
        tooltip=
        _('Size of numeric part for the names of these machines (between 3 and 6)'
          ),
        tab=_('Machine'),
        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[0], t[1]))

        # 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[0], d[1]))

        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 checkTemplatePublished(self, templateId):
        return self.parent().checkTemplatePublished(templateId)

    def deployFromTemplate(self, name, 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 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
        '''
        logger.debug('Deploying from template {0} machine {1}'.format(
            templateId, name))
        # self.datastoreHasSpace()
        return self.parent().deployFromTemplate(name, templateId)

    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 getMachineSubState(self, machineId):
        '''
        On OpenNebula, the machine can be "active" but not "running".
        Any active machine will have a LCM_STATE, that is what we get here
        '''
        return self.parent().getMachineSubState(machineId)

    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.parent().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.parent().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.parent().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.parent().removeMachine(machineId)

    def getNetInfo(self, machineId, networkId=None):
        '''
        Changes the mac address of first nic of the machine to the one specified
        '''
        return self.parent().getNetInfo(machineId, networkId=None)

    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 getConsoleConnection(self, machineId):
        return self.parent().getConsoleConnection(machineId)
Example #11
0
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 Operating System Image'),
        required=True,
    )

    maxReservationTime = gui.NumericField(
        length=3,
        label=_("Max. reservation time"),
        order=110,
        tooltip=
        _('Security parameter for OpenGnsys to keep reservations at most this hours. Handle with care!'
          ),
        defvalue='2400',  # 1 hundred days
        minValue='24',
        tab=_('Advanced'),
        required=False,
    )

    startIfUnavailable = gui.CheckBoxField(
        label=_('Start if unavailable'),
        defvalue=gui.TRUE,
        order=111,
        tooltip=
        _('If active, machines that are not available on user connect (on some OS) will try to power on through OpenGnsys.'
          ),
    )

    ov = gui.HiddenField(value=None)
    ev = gui.HiddenField(
        value=None
    )  # We need to keep the env so we can instantiate the Provider

    def initGui(self) -> None:
        """
        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 parent(self) -> 'OGProvider':
        return typing.cast('OGProvider', super().parent())

    def status(self, machineId: str) -> typing.Any:
        return self.parent().status(machineId)

    def reserve(self) -> typing.Any:
        return self.parent().reserve(
            self.ou.value,
            self.image.value,
            self.lab.value,
            self.maxReservationTime.num(),
        )

    def unreserve(self, machineId: str) -> typing.Any:
        return self.parent().unreserve(machineId)

    def notifyEvents(self, machineId: str, token: str,
                     uuid: str) -> typing.Any:
        return self.parent().notifyEvents(machineId,
                                          self.getLoginNotifyURL(uuid, token),
                                          self.getLogoutNotifyURL(uuid, token),
                                          self.getReleaseURL(uuid, token))

    def notifyDeadline(self, machineId: str,
                       deadLine: typing.Optional[int]) -> typing.Any:
        return self.parent().notifyDeadline(machineId, deadLine)

    def powerOn(self, machineId: str) -> typing.Any:
        return self.parent().powerOn(machineId, self.image.value)

    def _notifyURL(self, uuid: str, token: str, message: str) -> str:
        # The URL is "GET messages URL".
        return '{accessURL}uds/ognotify/{message}/{token}/{uuid}'.format(
            accessURL=self.parent().getUDSServerAccessUrl(),
            uuid=uuid,
            token=token,
            message=message)

    def getLoginNotifyURL(self, uuid: str, token: str) -> str:
        return self._notifyURL(uuid, token, 'login')

    def getLogoutNotifyURL(self, uuid: str, token: str) -> str:
        return self._notifyURL(uuid, token, 'logout')

    def getReleaseURL(self, uuid: str, token: str) -> str:
        return self._notifyURL(uuid, token, 'release')

    def isRemovableIfUnavailable(self):
        return self.startIfUnavailable.isTrue()
Example #12
0
class HTML5VNCTransport(transports.Transport):
    """
    Provides access via VNC to service.
    This transport can use an domain. If username processed by authenticator contains '@', it will split it and left-@-part will be username, and right password
    """
    typeName = _('HTML5 VNC Experimental')
    typeType = 'HTML5VNCTransport'
    typeDescription = _('VNC protocol using HTML5 client (EXPERIMENTAL)')
    iconFile = 'html5vnc.png'

    ownLink = True
    supportedOss = OsDetector.allOss
    protocol = transports.protocols.VNC
    group = transports.TUNNELED_GROUP

    guacamoleServer = gui.TextField(
        label=_('Tunnel Server'),
        order=1,
        tooltip=
        _('Host of the tunnel server (use http/https & port if needed) as accesible from users'
          ),
        defvalue='https://',
        length=64,
        required=True,
        tab=gui.TUNNEL_TAB)

    username = gui.TextField(
        label=_('Username'),
        order=20,
        tooltip=_('Username for VNC connection authentication.'),
        tab=gui.PARAMETERS_TAB)
    password = gui.PasswordField(
        label=_('Password'),
        order=21,
        tooltip=_('Password for VNC connection authentication'),
        tab=gui.PARAMETERS_TAB)

    vncPort = gui.NumericField(length=22,
                               label=_('VNC Server port'),
                               defvalue='5900',
                               order=2,
                               tooltip=_('Port of the VNC server.'),
                               required=True,
                               tab=gui.PARAMETERS_TAB)

    colorDepth = gui.ChoiceField(
        order=26,
        label=_('Color depth'),
        tooltip=_(
            'Color depth for VNC connection. Use this to control bandwidth.'),
        required=True,
        values=[
            gui.choiceItem('-', 'default'),
            gui.choiceItem('8', '8 bits'),
            gui.choiceItem('16', '16 bits'),
            gui.choiceItem('24', '24 bits'),
            gui.choiceItem('32', '33 bits'),
        ],
        defvalue='-',
        tab=gui.PARAMETERS_TAB)
    swapRedBlue = gui.CheckBoxField(
        label=_('Swap red/blue'),
        order=27,
        tooltip=
        _('Use this if your colours seems incorrect (blue appears red, ..) to swap them.'
          ),
        tab=gui.PARAMETERS_TAB)
    cursor = gui.CheckBoxField(
        label=_('Remote cursor'),
        order=28,
        tooltip=_('If set, force to show remote cursor'),
        tab=gui.PARAMETERS_TAB)
    readOnly = gui.CheckBoxField(
        label=_('Read only'),
        order=29,
        tooltip=_('If set, the connection will be read only'),
        tab=gui.PARAMETERS_TAB)

    ticketValidity = gui.NumericField(
        length=3,
        label=_('Ticket Validity'),
        defvalue='60',
        order=90,
        tooltip=
        _('Allowed time, in seconds, for HTML5 client to reload data from UDS Broker. The default value of 60 is recommended.'
          ),
        required=True,
        minValue=60,
        tab=gui.ADVANCED_TAB)
    forceNewWindow = gui.CheckBoxField(
        label=_('Force new HTML Window'),
        order=91,
        tooltip=
        _('If checked, every connection will try to open its own window instead of reusing the "global" one.'
          ),
        defvalue=gui.FALSE,
        tab=gui.ADVANCED_TAB)

    def initialize(self, values: 'Module.ValuesType'):
        if not values:
            return
        # Strip spaces
        self.guacamoleServer.value = self.guacamoleServer.value.strip()
        if self.guacamoleServer.value[0:4] != 'http':
            raise transports.Transport.ValidationException(
                _('The server must be http or https'))

    def isAvailableFor(self, userService: 'models.UserService',
                       ip: str) -> bool:
        """
        Checks if the transport is available for the requested destination ip
        Override this in yours transports
        """
        logger.debug('Checking availability for %s', ip)
        ready = self.cache.get(ip)
        if not ready:
            # Check again for readyness
            if self.testServer(userService, ip, self.vncPort.value) is True:
                self.cache.put(ip, 'Y', READY_CACHE_TIMEOUT)
                return True
            self.cache.put(ip, 'N', READY_CACHE_TIMEOUT)
        return ready == 'Y'

    def getLink(  # pylint: disable=too-many-locals
            self, userService: 'models.UserService',
            transport: 'models.Transport', ip: str, os: typing.Dict[str, str],
            user: '******', password: str, request: 'HttpRequest') -> str:
        # Build params dict
        params = {
            'protocol': 'vnc',
            'hostname': ip,
            'port': str(self.vncPort.num()),
        }

        if self.username.value.strip():
            params['username'] = self.username.value.strip()

        if self.password.value.strip():
            params['password'] = self.password.value.strip()

        if self.colorDepth.value != '-':
            params['color-depth'] = self.colorDepth.value

        if self.swapRedBlue.isTrue():
            params['swap-red-blue'] = 'true'

        if self.cursor.isTrue():
            params['cursor'] = 'remote'

        if self.readOnly.isTrue():
            params['read-only'] = 'true'

        logger.debug('VNC Params: %s', params)

        scrambler = cryptoManager().randomString(32)
        ticket = models.TicketStore.create(params,
                                           validity=self.ticketValidity.num())

        onw = 'o_n_w={};'.format(hash(
            transport.name)) if self.forceNewWindow.isTrue() else ''
        return str("{}/transport/?{}.{}&{}".format(self.guacamoleServer.value,
                                                   ticket, scrambler, onw))
Example #13
0
class UsageByPool(StatsReport):
    filename = 'pools_usage.pdf'
    name = _('Pools usage by users')  # Report name
    description = _('Pools usage by user report')  # Report description
    uuid = '38ec12dc-beaf-11e5-bd0a-10feed05884b'

    # Input fields
    pool = gui.ChoiceField(
        order=1,
        label=_('Pool'),
        tooltip=_('Pool for report'),
        required=True
    )

    startDate = gui.DateField(
        order=2,
        label=_('Starting date'),
        tooltip=_('starting date for report'),
        defvalue=datetime.date.min,
        required=True
    )

    endDate = gui.DateField(
        order=3,
        label=_('Finish date'),
        tooltip=_('finish date for report'),
        defvalue=datetime.date.max,
        required=True
    )

    def initialize(self, values):
        pass

    def initGui(self):
        logger.debug('Initializing gui')
        vals = [
            gui.choiceItem(v.uuid, v.name) for v in ServicePool.objects.all()
        ]
        self.pool.setValues(vals)

    def getData(self) -> typing.Tuple[typing.List[typing.Dict[str, typing.Any]], str]:
        # Generate the sampling intervals and get dataUsers from db
        start = self.startDate.stamp()
        end = self.endDate.stamp()
        logger.debug(self.pool.value)
        pool = ServicePool.objects.get(uuid=self.pool.value)

        items = events.statsManager().getEvents(events.OT_DEPLOYED, (events.ET_LOGIN, events.ET_LOGOUT), owner_id=pool.id, since=start, to=end).order_by('stamp')

        logins = {}
        data = []
        for i in items:
            # if '\\' in i.fld1:
            #    continue

            if i.event_type == events.ET_LOGIN:
                logins[i.fld4] = i.stamp
            else:
                if i.fld4 in logins:
                    stamp = logins[i.fld4]
                    del logins[i.fld4]
                    total = i.stamp - stamp
                    data.append({
                        'name': i.fld4,
                        'date': datetime.datetime.fromtimestamp(stamp),
                        'time': total
                    })

        logger.debug('data: %s', data)

        return data, pool.name

    def generate(self):
        items, poolName = self.getData()

        return self.templateAsPDF(
            'uds/reports/stats/usage-by-pool.html',
            dct={
                'data': items,
                'pool': poolName,
            },
            header=ugettext('Users usage list for {}').format(poolName),
            water=ugettext('UDS Report of users in {}').format(poolName)
        )
Example #14
0
class UsageSummaryByUsersPool(StatsReport):
    filename = 'pool_user_usage.pdf'
    name = _('Pool Usage by users')  # Report name
    description = _('Generates a report with the summary of users usage for a pool')  # Report description
    uuid = '202c6438-30a8-11e7-80e4-77c1e4cb9e09'

    # Input fields
    pool = gui.ChoiceField(
        order=1,
        label=_('Pool'),
        tooltip=_('Pool for report'),
        required=True
    )

    startDate = gui.DateField(
        order=2,
        label=_('Starting date'),
        tooltip=_('starting date for report'),
        defvalue=datetime.date.min,
        required=True
    )

    endDate = gui.DateField(
        order=3,
        label=_('Finish date'),
        tooltip=_('finish date for report'),
        defvalue=datetime.date.max,
        required=True
    )

    def initialize(self, values):
        pass

    def initGui(self):
        logger.debug('Initializing gui')
        vals = [
            gui.choiceItem(v.uuid, v.name) for v in ServicePool.objects.all()
        ]
        self.pool.setValues(vals)

    def getPoolData(self, pool) -> typing.Tuple[typing.List[typing.Dict[str, typing.Any]], str]:
        start = self.startDate.stamp()
        end = self.endDate.stamp()
        logger.debug(self.pool.value)

        items = events.statsManager().getEvents(events.OT_DEPLOYED, (events.ET_LOGIN, events.ET_LOGOUT), owner_id=pool.id, since=start, to=end).order_by('stamp')

        logins: typing.Dict[str, int] = {}
        users: typing.Dict[str, typing.Dict] = {}
        for i in items:
            # if '\\' in i.fld1:
            #    continue
            username = i.fld4
            if i.event_type == events.ET_LOGIN:
                logins[username] = i.stamp
            else:
                if username in logins:
                    stamp = logins[username]
                    del logins[username]
                    total = i.stamp - stamp
                    if username not in users:
                        users[username] = {'sessions': 0, 'time': 0}
                    users[username]['sessions'] += 1
                    users[username]['time'] += total
                    # data.append({
                    #    'name': i.fld4,
                    #    'date': datetime.datetime.fromtimestamp(stamp),
                    #    'time': total
                    # })

        # Extract different number of users
        data = [{
            'user': k,
            'sessions': v['sessions'],
            'hours': '{:.2f}'.format(float(v['time']) / 3600),
            'average': '{:.2f}'.format(float(v['time']) / 3600 / v['sessions'])
        } for k, v in users.items()]

        return data, pool.name

    def getData(self) -> typing.Tuple[typing.List[typing.Dict[str, typing.Any]], str]:
        return self.getPoolData(ServicePool.objects.get(uuid=self.pool.value))

    def generate(self):
        items, poolName = self.getData()

        return self.templateAsPDF(
            'uds/reports/stats/pool-users-summary.html',
            dct={
                'data': items,
                'pool': poolName,
                'beginning': self.startDate.date(),
                'ending': self.endDate.date(),
            },
            header=ugettext('Users usage list for {}').format(poolName),
            water=ugettext('UDS Report of users in {}').format(poolName)
        )
Example #15
0
class BaseX2GOTransport(transports.Transport):
    """
    Provides access via X2GO to service.
    This transport can use an domain. If username processed by authenticator contains '@', it will split it and left-@-part will be username, and right password
    """
    iconFile = 'x2go.png'
    protocol = transports.protocols.X2GO
    supportedOss = (OsDetector.Linux, OsDetector.Windows)

    fixedName = gui.TextField(
        order=2,
        label=_('Username'),
        tooltip=_(
            'If not empty, this username will be always used as credential'),
        tab=gui.CREDENTIALS_TAB)

    screenSize = gui.ChoiceField(label=_('Screen size'),
                                 order=10,
                                 tooltip=_('Screen size'),
                                 defvalue=CommonPrefs.SZ_FULLSCREEN,
                                 values=[{
                                     'id': CommonPrefs.SZ_640x480,
                                     'text': '640x480'
                                 }, {
                                     'id': CommonPrefs.SZ_800x600,
                                     'text': '800x600'
                                 }, {
                                     'id': CommonPrefs.SZ_1024x768,
                                     'text': '1024x768'
                                 }, {
                                     'id': CommonPrefs.SZ_1366x768,
                                     'text': '1366x768'
                                 }, {
                                     'id': CommonPrefs.SZ_1920x1080,
                                     'text': '1920x1080'
                                 }, {
                                     'id': CommonPrefs.SZ_FULLSCREEN,
                                     'text': ugettext_lazy('Full Screen')
                                 }],
                                 tab=gui.PARAMETERS_TAB)

    desktopType = gui.ChoiceField(
        label=_('Desktop'),
        order=11,
        tooltip=_('Desktop session'),
        values=[
            {
                'id': 'XFCE',
                'text': 'Xfce'
            },
            {
                'id': 'MATE',
                'text': 'Mate'
            },
            {
                'id': 'LXDE',
                'text': 'Lxde'
            },
            {
                'id': 'GNOME',
                'text': 'Gnome (see docs)'
            },
            {
                'id': 'KDE',
                'text': 'Kde (see docs)'
            },
            # {'id': 'UNITY', 'text': 'Unity (see docs)'},
            {
                'id': 'gnome-session-cinnamon',
                'text': 'Cinnamon 1.4 (see docs)'
            },
            {
                'id': 'gnome-session-cinnamon2d',
                'text': 'Cinnamon 2.2 (see docs)'
            },
            {
                'id': 'UDSVAPP',
                'text': 'UDS vAPP'
            },
        ],
        tab=gui.PARAMETERS_TAB)

    customCmd = gui.TextField(
        order=12,
        label=_('vAPP'),
        tooltip=
        _('If UDS vAPP is selected as "Desktop", the FULL PATH of the app to be executed. If UDS vAPP is not selected, this field will be ignored.'
          ),
        tab=gui.PARAMETERS_TAB)

    sound = gui.CheckBoxField(order=13,
                              label=_('Enable sound'),
                              tooltip=_('If checked, sound will be available'),
                              defvalue=gui.TRUE,
                              tab=gui.PARAMETERS_TAB)

    exports = gui.CheckBoxField(
        order=14,
        label=_('Redirect home folder'),
        tooltip=
        _('If checked, user home folder will be redirected. (On linux, also redirects /media)'
          ),
        defvalue=gui.FALSE,
        tab=gui.PARAMETERS_TAB)

    speed = gui.ChoiceField(label=_('Speed'),
                            order=15,
                            tooltip=_('Connection speed'),
                            defvalue='3',
                            values=[
                                {
                                    'id': '0',
                                    'text': 'MODEM'
                                },
                                {
                                    'id': '1',
                                    'text': 'ISDN'
                                },
                                {
                                    'id': '2',
                                    'text': 'ADSL'
                                },
                                {
                                    'id': '3',
                                    'text': 'WAN'
                                },
                                {
                                    'id': '4',
                                    'text': 'LAN'
                                },
                            ],
                            tab=gui.PARAMETERS_TAB)

    soundType = gui.ChoiceField(label=_('Sound'),
                                order=30,
                                tooltip=_('Sound server'),
                                defvalue='pulse',
                                values=[
                                    {
                                        'id': 'pulse',
                                        'text': 'Pulse'
                                    },
                                    {
                                        'id': 'esd',
                                        'text': 'ESD'
                                    },
                                ],
                                tab=gui.ADVANCED_TAB)

    keyboardLayout = gui.TextField(
        label=_('Keyboard'),
        order=31,
        tooltip=_(
            'Keyboard layout (es, us, fr, ...). Empty value means autodetect.'
        ),
        defvalue='',
        tab=gui.ADVANCED_TAB)
    # 'nopack', '8', '64', '256', '512', '4k', '32k', '64k', '256k', '2m', '16m'
    # '256-rdp', '256-rdp-compressed', '32k-rdp', '32k-rdp-compressed', '64k-rdp'
    # '64k-rdp-compressed', '16m-rdp', '16m-rdp-compressed'
    # 'rfb-hextile', 'rfb-tight', 'rfb-tight-compressed'
    # '8-tight', '64-tight', '256-tight', '512-tight', '4k-tight', '32k-tight'
    # '64k-tight', '256k-tight', '2m-tight', '16m-tight'
    # '8-jpeg-%', '64-jpeg', '256-jpeg', '512-jpeg', '4k-jpeg', '32k-jpeg'
    # '64k-jpeg', '256k-jpeg', '2m-jpeg', '16m-jpeg-%'
    # '8-png-jpeg-%', '64-png-jpeg', '256-png-jpeg', '512-png-jpeg', '4k-png-jpeg'
    # '32k-png-jpeg', '64k-png-jpeg', '256k-png-jpeg', '2m-png-jpeg', '16m-png-jpeg-%'
    # '8-png-%', '64-png', '256-png', '512-png', '4k-png'
    # '32k-png', '64k-png', '256k-png', '2m-png', '16m-png-%'
    # '16m-rgb-%', '16m-rle-%'
    pack = gui.TextField(label=_('Pack'),
                         order=32,
                         tooltip=_('Pack format. Change with care!'),
                         defvalue='16m-jpeg',
                         tab=gui.ADVANCED_TAB)

    quality = gui.NumericField(
        label=_('Quality'),
        order=33,
        tooltip=_('Quality value used on some pack formats.'),
        length=1,
        defvalue='6',
        minValue=1,
        maxValue=9,
        required=True,
        tab=gui.ADVANCED_TAB)

    def isAvailableFor(self, userService: 'models.UserService',
                       ip: str) -> bool:
        """
        Checks if the transport is available for the requested destination ip
        Override this in yours transports
        """
        logger.debug('Checking availability for %s', ip)
        ready = self.cache.get(ip)
        if ready is None:
            # Check again for ready
            if connection.testServer(ip, 22):
                self.cache.put(ip, 'Y', READY_CACHE_TIMEOUT)
                return True
            self.cache.put(ip, 'N', READY_CACHE_TIMEOUT)
        return ready == 'Y'

    def getScreenSize(self) -> typing.Tuple[int, int]:
        return CommonPrefs.getWidthHeight(self.screenSize.value)

    def processedUser(self, userService: 'models.UserService',
                      user: '******') -> str:
        v = self.processUserPassword(userService, user, '')
        return v['username']

    def processUserPassword(self, userService: 'models.UserService',
                            user: '******',
                            password: str) -> typing.Dict[str, str]:
        username = user.getUsernameForAuth()

        if self.fixedName.value != '':
            username = self.fixedName.value

        # Fix username/password acording to os manager
        username, password = userService.processUserPassword(
            username, password)

        return {
            'protocol': self.protocol,
            'username': username,
            'password': ''
        }

    def getConnectionInfo(self,
                          userService: typing.Union['models.UserService',
                                                    'models.ServicePool'],
                          user: '******',
                          password: str) -> typing.Dict[str, str]:
        return self.processUserPassword(userService, user, password)

    def genKeyPairForSsh(self) -> typing.Tuple[str, str]:
        """
        Generates a key pair for use with x2go
        The private part is used by client
        the public part must be "appended" to authorized_keys if it is not already added.
        If .ssh folder does not exists, it must be created
        if authorized_keys does not exists, it must be created
        On key adition, we can look for every key that has a "UDS@X2GOCLIENT" as comment, so we can remove them before adding new ones

        Windows (tested):
            C:\\Program Files (x86)\\x2goclient>x2goclient.exe --session-conf=c:/temp/sessions --session=UDS/test-session --close-disconnect --hide --no-menu
        Linux (tested):
            HOME=[temporal folder, where we create a .x2goclient folder and a sessions inside] pyhoca-cli -P UDS/test-session
        """
        key = paramiko.RSAKey.generate(SSH_KEY_LENGTH)
        privFile = io.StringIO()
        key.write_private_key(privFile)
        priv = privFile.getvalue()

        pub = key.get_base64(
        )  # 'ssh-rsa {} UDS@X2GOCLIENT'.format(key.get_base64())
        return priv, pub

    def getAuthorizeScript(self, user: str, pubKey: str) -> str:
        with open(
                os.path.join(os.path.dirname(__file__),
                             'scripts/authorize.py')) as f:
            data = f.read()

        return data.replace('__USER__', user).replace('__KEY__', pubKey)

    def getAndPushKey(
            self, userName: str,
            userService: 'models.UserService') -> typing.Tuple[str, str]:
        priv, pub = self.genKeyPairForSsh()
        authScript = self.getAuthorizeScript(userName, pub)
        userServiceManager().sendScript(userService, authScript)
        return priv, pub

    def getScript(
        self, scriptNameTemplate: str, osName: str,
        params: typing.Dict[str, typing.Any]
    ) -> typing.Tuple[str, str, typing.Dict[str, typing.Any]]:
        # Reads script
        scriptNameTemplate = scriptNameTemplate.format(osName)
        with open(os.path.join(os.path.dirname(__file__),
                               scriptNameTemplate)) as f:
            script = f.read()
        # Reads signature
        with open(
                os.path.join(os.path.dirname(__file__),
                             scriptNameTemplate + '.signature')) as f:
            signature = f.read()
        return script, signature, params
Example #16
0
class TSNXTransport(BaseNXTransport):
    """
    Provides access via NX to service.
    This transport can use an domain. If username processed by authenticator contains '@', it will split it and left-@-part will be username, and right password
    """
    typeName = _('NX v3.5 (DEPRECATED)')
    typeType = 'TSNXTransport'
    typeDescription = _('NX protocol v3.5. Tunneled connection.')
    iconFile = 'nx.png'
    protocol = transports.protocols.NX
    group = transports.TUNNELED_GROUP

    tunnelServer = gui.TextField(
        label=_('Tunnel server'),
        order=1,
        tooltip=
        _('IP or Hostname of tunnel server sent to client device ("public" ip) and port. (use HOST:PORT format)'
          ),
        tab=gui.TUNNEL_TAB)

    useEmptyCreds = gui.CheckBoxField(
        label=_('Empty creds'),
        order=3,
        tooltip=_('If checked, the credentials used to connect will be emtpy'),
        tab=gui.CREDENTIALS_TAB)
    fixedName = gui.TextField(
        label=_('Username'),
        order=4,
        tooltip=_(
            'If not empty, this username will be always used as credential'),
        tab=gui.CREDENTIALS_TAB)
    fixedPassword = gui.PasswordField(
        label=_('Password'),
        order=5,
        tooltip=_(
            'If not empty, this password will be always used as credential'),
        tab=gui.CREDENTIALS_TAB)
    listenPort = gui.NumericField(
        label=_('Listening port'),
        length=5,
        order=6,
        tooltip=_('Listening port of NX (ssh) at client machine'),
        defvalue='22')
    connection = gui.ChoiceField(
        label=_('Connection'),
        order=7,
        tooltip=_('Connection speed for this transport (quality)'),
        values=[
            {
                'id': 'modem',
                'text': 'modem'
            },
            {
                'id': 'isdn',
                'text': 'isdn'
            },
            {
                'id': 'adsl',
                'text': 'adsl'
            },
            {
                'id': 'wan',
                'text': 'wan'
            },
            {
                'id': 'lan',
                'text': 'lan'
            },
        ],
        tab=gui.PARAMETERS_TAB)
    session = gui.ChoiceField(label=_('Session'),
                              order=8,
                              tooltip=_('Desktop session'),
                              values=[
                                  {
                                      'id': 'gnome',
                                      'text': 'gnome'
                                  },
                                  {
                                      'id': 'kde',
                                      'text': 'kde'
                                  },
                                  {
                                      'id': 'cde',
                                      'text': 'cde'
                                  },
                              ],
                              tab=gui.PARAMETERS_TAB)
    cacheDisk = gui.ChoiceField(label=_('Disk Cache'),
                                order=9,
                                tooltip=_('Cache size en Mb stored at disk'),
                                values=[
                                    {
                                        'id': '0',
                                        'text': '0 Mb'
                                    },
                                    {
                                        'id': '32',
                                        'text': '32 Mb'
                                    },
                                    {
                                        'id': '64',
                                        'text': '64 Mb'
                                    },
                                    {
                                        'id': '128',
                                        'text': '128 Mb'
                                    },
                                    {
                                        'id': '256',
                                        'text': '256 Mb'
                                    },
                                    {
                                        'id': '512',
                                        'text': '512 Mb'
                                    },
                                ],
                                tab=gui.PARAMETERS_TAB)
    cacheMem = gui.ChoiceField(label=_('Memory Cache'),
                               order=10,
                               tooltip=_('Cache size en Mb kept at memory'),
                               values=[
                                   {
                                       'id': '4',
                                       'text': '4 Mb'
                                   },
                                   {
                                       'id': '8',
                                       'text': '8 Mb'
                                   },
                                   {
                                       'id': '16',
                                       'text': '16 Mb'
                                   },
                                   {
                                       'id': '32',
                                       'text': '32 Mb'
                                   },
                                   {
                                       'id': '64',
                                       'text': '64 Mb'
                                   },
                                   {
                                       'id': '128',
                                       'text': '128 Mb'
                                   },
                               ],
                               tab=gui.PARAMETERS_TAB)
    screenSize = gui.ChoiceField(label=_('Screen size'),
                                 order=10,
                                 tooltip=_('Screen size'),
                                 defvalue=CommonPrefs.SZ_FULLSCREEN,
                                 values=[{
                                     'id': CommonPrefs.SZ_640x480,
                                     'text': '640x480'
                                 }, {
                                     'id': CommonPrefs.SZ_800x600,
                                     'text': '800x600'
                                 }, {
                                     'id': CommonPrefs.SZ_1024x768,
                                     'text': '1024x768'
                                 }, {
                                     'id': CommonPrefs.SZ_1366x768,
                                     'text': '1366x768'
                                 }, {
                                     'id': CommonPrefs.SZ_1920x1080,
                                     'text': '1920x1080'
                                 }, {
                                     'id': CommonPrefs.SZ_FULLSCREEN,
                                     'text': ugettext_lazy('Full Screen')
                                 }],
                                 tab=gui.PARAMETERS_TAB)

    _tunnelServer: str = ''
    _tunnelCheckServer: str = ''
    _useEmptyCreds: bool = False
    _fixedName: str = ''
    _fixedPassword: str = ''
    _listenPort: str = ''
    _connection: str = ''
    _session: str = ''
    _cacheDisk: str = ''
    _cacheMem: str = ''
    _screenSize: str = ''

    def initialize(self, values: 'Module.ValuesType'):
        if values:
            if values['tunnelServer'].find(':') == -1:
                raise transports.Transport.ValidationException(
                    _('Must use HOST:PORT in Tunnel Server Field'))
            self._tunnelServer = values['tunnelServer']
            self._tunnelCheckServer = ''
            self._useEmptyCreds = gui.strToBool(values['useEmptyCreds'])
            self._fixedName = values['fixedName']
            self._fixedPassword = values['fixedPassword']
            self._listenPort = values['listenPort']
            self._connection = values['connection']
            self._session = values['session']
            self._cacheDisk = values['cacheDisk']
            self._cacheMem = values['cacheMem']
            self._screenSize = values['screenSize']

    def marshal(self) -> bytes:
        """
        Serializes the transport data so we can store it in database
        """
        return str.join('\t', [
            'v2',
            gui.boolToStr(self._useEmptyCreds), self._fixedName,
            self._fixedPassword, self._listenPort, self._connection,
            self._session, self._cacheDisk, self._cacheMem, self._tunnelServer,
            self._tunnelCheckServer, self._screenSize
        ]).encode('utf8')

    def unmarshal(self, data: bytes) -> None:
        values = data.decode('utf8').split('\t')
        if values[0] in ('v1', 'v2'):
            self._useEmptyCreds = gui.strToBool(values[1])
            (self._fixedName, self._fixedPassword, self._listenPort,
             self._connection, self._session, self._cacheDisk, self._cacheMem,
             self._tunnelServer, self._tunnelCheckServer) = values[2:11]
            self._screenSize = values[11] if values[
                0] == 'v2' else CommonPrefs.SZ_FULLSCREEN

    def valuesDict(self) -> gui.ValuesDictType:
        return {
            'useEmptyCreds': gui.boolToStr(self._useEmptyCreds),
            'fixedName': self._fixedName,
            'fixedPassword': self._fixedPassword,
            'listenPort': self._listenPort,
            'connection': self._connection,
            'session': self._session,
            'cacheDisk': self._cacheDisk,
            'cacheMem': self._cacheMem,
            'tunnelServer': self._tunnelServer
        }

    def getUDSTransportScript(  # pylint: disable=too-many-locals
        self, userService: 'models.UserService', transport: 'models.Transport',
        ip: str, os: typing.Dict[str, str], user: '******', password: str,
        request: 'HttpRequest'
    ) -> typing.Tuple[str, str, typing.Dict[str, typing.Any]]:
        prefs = self.screenSize.value

        username = user.getUsernameForAuth()
        proc = username.split('@')
        username = proc[0]
        if self._fixedName is not '':
            username = self._fixedName
        if self._fixedPassword is not '':
            password = self._fixedPassword
        if self._useEmptyCreds is True:
            usernamsizerd = '', ''

        tunpass = ''.join(random.SystemRandom().choice(string.ascii_letters +
                                                       string.digits)
                          for _i in range(12))
        tunuser = TicketStore.create(tunpass)

        sshServer = self._tunnelServer
        if ':' not in sshServer:
            sshServer += ':443'

        sshHost, sshPort = sshServer.split(':')

        logger.debug('Username generated: %s, password: %s', tunuser, tunpass)

        width, height = CommonPrefs.getWidthHeight(prefs)
        # Fix username/password acording to os manager
        username, password = userService.processUserPassword(
            username, password)

        r = NXFile(username=username,
                   password=password,
                   width=width,
                   height=height)
        r.host = '{address}'
        r.port = '{port}'
        r.linkSpeed = self._connection
        r.desktop = self._session
        r.cachedisk = self._cacheDisk
        r.cachemem = self._cacheMem

        osName = {
            OsDetector.Windows: 'windows',
            OsDetector.Linux: 'linux',
            OsDetector.Macintosh: 'macosx'
        }.get(os['OS'])

        if osName is None:
            return super().getUDSTransportScript(userService, transport, ip,
                                                 os, user, password, request)

        sp = {
            'ip': ip,
            'tunUser': tunuser,
            'tunPass': tunpass,
            'tunHost': sshHost,
            'tunPort': sshPort,
            'port': self._listenPort,
            'as_file_for_format': r.as_file_for_format
        }

        return self.getScript('scripts/{}/direct.py', osName, sp)
Example #17
0
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 Operating System Image'),
                            required=True)

    maxReservationTime = gui.NumericField(
        length=3,
        label=_("Max. reservation time"),
        order=110,
        tooltip=
        _('Security parameter for OpenGnsys to keep 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)

    def notifyEvents(self, machineId, serviceUUID):
        return self.parent().notifyEvents(machineId,
                                          self.getLoginNotifyURL(serviceUUID),
                                          self.getLogoutNotifyURL(serviceUUID))

    def notifyDeadline(self, machineId, deadLine):
        return self.parent().notifyDeadline(machineId, deadLine)

    def _notifyURL(self, uuid, message):
        # The URL is "GET messages URL".
        return '{accessURL}rest/actor/PostThoughGet/{uuid}/{message}'.format(
            accessURL=self.parent().getUDSServerAccessUrl(),
            uuid=uuid,
            message=message)

    def getLoginNotifyURL(self, serviceUUID):
        return self._notifyURL(serviceUUID, 'login')

    def getLogoutNotifyURL(self, serviceUUID):
        return self._notifyURL(serviceUUID, 'logout')
Example #18
0
class XenLinkedService(Service):
    """
    Xen Linked clones service. This is based on creating a template from selected vm, and then use it to


    """
    # : 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 = _('Xen Linked Clone')
    # : Type used internally to identify this provider
    typeType = 'XenLinkedService'
    # : Description shown at administration interface for this provider
    typeDescription = _('Xen Services based on templates')
    # : 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 = 'service.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 we need to generate a "Level 2" cache for this service (i.e., L1
    # : could be running machines and L2 suspended machines)
    usesCache_L2 = True
    # : Tooltip shown to user when this item is pointed at admin interface, None
    # : also because we don't use it
    cacheTooltip_L2 = _(
        'Number of desired machines to keep suspended waiting for use')

    # : If the service needs a s.o. manager (managers are related to agents
    # : provided by services itselfs, i.e. virtual machines with actors)
    needsManager = True
    # : If true, the system can't do an automatic assignation of a deployed user
    # : service from this service
    mustAssignManually = False
    canReset = True

    # : Types of publications (preparated data for deploys)
    # : In our case, we do no need a publication, so this is None
    publicationType = XenPublication
    # : Types of deploys (services in cache and/or assigned to users)
    deployedType = XenLinkedDeployment

    servicesTypeProvided = (serviceTypes.VDI, )

    # Now the form part
    datastore = gui.ChoiceField(
        label=_("Storage SR"),
        rdonly=False,
        order=100,
        tooltip=_('Storage where to publish and put incrementals'),
        required=True)

    minSpaceGB = gui.NumericField(length=3,
                                  label=_('Reserved Space'),
                                  defvalue='32',
                                  order=101,
                                  tooltip=_('Minimal free space in GB'),
                                  required=True)

    machine = gui.ChoiceField(label=_("Base Machine"),
                              order=110,
                              tooltip=_('Service base machine'),
                              tab=_('Machine'),
                              required=True)

    network = gui.ChoiceField(label=_("Network"),
                              rdonly=False,
                              order=111,
                              tooltip=_('Network used for virtual machines'),
                              tab=_('Machine'),
                              required=True)

    memory = gui.NumericField(label=_("Memory (Mb)"),
                              length=4,
                              defvalue=512,
                              rdonly=False,
                              order=112,
                              tooltip=_('Memory assigned to machines'),
                              tab=_('Machine'),
                              required=True)

    shadow = gui.NumericField(
        label=_("Shadow"),
        length=1,
        defvalue=4,
        rdonly=False,
        order=113,
        tooltip=_('Shadow memory multiplier (memory overcommit)'),
        tab=_('Machine'),
        required=True)

    baseName = gui.TextField(
        label=_('Machine Names'),
        rdonly=False,
        order=114,
        tooltip=_('Base name for clones from this machine'),
        tab=_('Machine'),
        required=True)

    lenName = gui.NumericField(
        length=1,
        label=_('Name Length'),
        defvalue=5,
        order=115,
        tooltip=
        _('Length of numeric part for the names of this machines (beetwen 3 and 6'
          ),
        tab=_('Machine'),
        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'))
            if int(self.memory.value) < 256:
                raise Service.ValidationException(
                    _('The minimum allowed memory is 256 Mb'))

    def initGui(self):
        """
        Loads required values inside
        """

        # Here we have to use "default values", cause values aren't used at form initialization
        # This is that value is always '', so if we want to change something, we have to do it
        # at defValue

        machines = self.parent().getMachines()
        storages = self.parent().getStorages()
        networks = self.parent().getNetworks()
        machines_list = []
        for m in machines:
            machines_list.append(gui.choiceItem(m['id'], m['name']))
        storages_list = []
        for storage in storages:
            space, free = storage['size'] / 1024, (storage['size'] -
                                                   storage['used']) / 1024
            storages_list.append(
                gui.choiceItem(
                    storage['id'],
                    "%s (%4.2f Gb/%4.2f Gb)" % (storage['name'], space, free)))
        network_list = []
        for net in networks:
            network_list.append(gui.choiceItem(net['id'], net['name']))

        self.machine.setValues(machines_list)
        self.datastore.setValues(storages_list)
        self.network.setValues(network_list)

    def checkTaskFinished(self, task):
        return self.parent().checkTaskFinished(task)

    def datastoreHasSpace(self):
        # Get storages for that datacenter
        logger.debug('Checking datastore space for {0}'.format(
            self.datastore.value))
        info = self.parent().getStorageInfo(self.datastore.value)
        logger.debug('Datastore Info: {0}'.format(info))
        availableGB = (info['size'] - info['used']) / 1024
        if availableGB < self.minSpaceGB.num():
            raise Exception(
                'Not enough free space available: (Needs at least {0} GB and there is only {1} GB '
                .format(self.minSpaceGB.num(), availableGB))

    def sanitizeVmName(self, name):
        """
        Xen Seems to allow all kind of names
        """
        return name

    def startDeployTemplate(self, name, comments):
        """
        Invokes makeTemplate from parent provider, completing params

        Args:
            name: Name to assign to template (must be previously "sanitized"
            comments: Comments (UTF-8) to add to template

        Returns:
            template Id of the template created

        Raises an exception if operation fails.
        """

        logger.debug(
            'Starting deploy of template from machine {0} on datastore {1}'.
            format(self.machine.value, self.datastore.value))

        # Checks datastore size
        self.datastoreHasSpace()
        return self.parent().cloneForTemplate(name, comments,
                                              self.machine.value,
                                              self.datastore.value)

    def convertToTemplate(self, machineId):
        """
        """
        self.parent().convertToTemplate(machineId, self.shadow.value)

    def startDeployFromTemplate(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 Xen 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().startDeployFromTemplate(name, comments,
                                                     templateId)

    def removeTemplate(self, templateId):
        """
        invokes removeTemplate from parent provider
        """
        return self.parent().removeTemplate(templateId)

    def getVMPowerState(self, machineId):
        """
        Invokes getMachineState from parent provider

        Args:
            machineId: If of the machine to get state

        Returns:
            one of this values:
        """
        return self.parent().getVMPowerState(machineId)

    def startVM(self, machineId, async=True):
        """
        Tries to start a machine. No check is done, it is simply requested to Xen.

        This start also "resume" suspended/paused machines

        Args:
            machineId: Id of the machine

        Returns:
        """
        return self.parent().startVM(machineId, async)
Example #19
0
class BaseRDPTransport(transports.Transport):
    """
    Provides access via RDP to service.
    This transport can use an domain. If username processed by authenticator contains '@', it will split it and left-@-part will be username, and right password
    """
    iconFile = 'rdp.png'
    protocol = transports.protocols.RDP

    useEmptyCreds = gui.CheckBoxField(label=_('Empty creds'), order=11, tooltip=_('If checked, the credentials used to connect will be emtpy'), tab=gui.CREDENTIALS_TAB)
    fixedName = gui.TextField(label=_('Username'), order=12, tooltip=_('If not empty, this username will be always used as credential'), tab=gui.CREDENTIALS_TAB)
    fixedPassword = gui.PasswordField(label=_('Password'), order=13, tooltip=_('If not empty, this password will be always used as credential'), tab=gui.CREDENTIALS_TAB)
    withoutDomain = gui.CheckBoxField(label=_('Without Domain'), order=14, tooltip=_('If checked, the domain part will always be emptied (to connect to xrdp for example is needed)'), tab=gui.CREDENTIALS_TAB)
    fixedDomain = gui.TextField(label=_('Domain'), order=15, tooltip=_('If not empty, this domain will be always used as credential (used as DOMAIN\\user)'), tab=gui.CREDENTIALS_TAB)

    allowSmartcards = gui.CheckBoxField(label=_('Allow Smartcards'), order=20, tooltip=_('If checked, this transport will allow the use of smartcards'), tab=gui.PARAMETERS_TAB)
    allowPrinters = gui.CheckBoxField(label=_('Allow Printers'), order=21, tooltip=_('If checked, this transport will allow the use of user printers'), tab=gui.PARAMETERS_TAB)
    allowDrives = gui.ChoiceField(
        label=_('Local drives policy'),
        order=22,
        tooltip=_('Local drives redirection policy'),
        defvalue='false',
        values=[
            {'id': 'false', 'text': 'Allow none'},
            {'id': 'dynamic', 'text': 'Allow PnP drives'},
            {'id': 'true', 'text': 'Allow any drive'},
        ],
        tab=gui.PARAMETERS_TAB
    )
    enforceDrives = gui.TextField(
        label=_('Force drives'),
        order=23,
        tooltip=_('Use comma separated values, for example "C:,D:". If drives policy is disallowed, this will be ignored'),
        tab=gui.PARAMETERS_TAB
    )

    allowSerials = gui.CheckBoxField(label=_('Allow Serials'), order=24, tooltip=_('If checked, this transport will allow the use of user serial ports'), tab=gui.PARAMETERS_TAB)
    allowClipboard = gui.CheckBoxField(label=_('Enable clipboard'), order=25, tooltip=_('If checked, copy-paste functions will be allowed'), tab=gui.PARAMETERS_TAB, defvalue=gui.TRUE)
    allowAudio = gui.CheckBoxField(label=_('Enable sound'), order=26, tooltip=_('If checked, sound will be redirected.'), tab=gui.PARAMETERS_TAB, defvalue=gui.TRUE)
    credssp = gui.CheckBoxField(label=_('Credssp Support'), order=27, tooltip=_('If checked, will enable Credentials Provider Support)'), tab=gui.PARAMETERS_TAB, defvalue=gui.TRUE)

    screenSize = gui.ChoiceField(
        label=_('Screen Size'),
        order=30,
        tooltip=_('Screen size for this transport'),
        defvalue='-1x-1',
        values=[
            {'id': '640x480', 'text': '640x480'},
            {'id': '800x600', 'text': '800x600'},
            {'id': '1024x768', 'text': '1024x768'},
            {'id': '1366x768', 'text': '1366x768'},
            {'id': '1920x1080', 'text': '1920x1080'},
            {'id': '-1x-1', 'text': 'Full screen'},
        ],
        tab=gui.DISPLAY_TAB
    )

    colorDepth = gui.ChoiceField(
        label=_('Color depth'),
        order=31,
        tooltip=_('Color depth for this connection'),
        defvalue='24',
        values=[
            {'id': '8', 'text': '8'},
            {'id': '16', 'text': '16'},
            {'id': '24', 'text': '24'},
            {'id': '32', 'text': '32'},
        ],
        tab=gui.DISPLAY_TAB
    )

    wallpaper = gui.CheckBoxField(label=_('Wallpaper/theme'), order=32, tooltip=_('If checked, the wallpaper and themes will be shown on machine (better user experience, more bandwidth)'), tab=gui.DISPLAY_TAB)
    multimon = gui.CheckBoxField(label=_('Multiple monitors'), order=33, tooltip=_('If checked, all client monitors will be used for displaying (only works on windows clients)'), tab=gui.DISPLAY_TAB)
    aero = gui.CheckBoxField(label=_('Allow Desk.Comp.'), order=34, tooltip=_('If checked, desktop composition will be allowed'), tab=gui.DISPLAY_TAB)
    smooth = gui.CheckBoxField(label=_('Font Smoothing'), order=35, tooltip=_('If checked, fonts smoothing will be allowed'), tab=gui.DISPLAY_TAB)
    showConnectionBar = gui.CheckBoxField(label=_('Connection Bar'), order=36, tooltip=_('If checked, connection bar will be shown (only on Windows clients)'), tab=gui.DISPLAY_TAB, defvalue=gui.TRUE)

    multimedia = gui.CheckBoxField(label=_('Multimedia sync'), order=40, tooltip=_('If checked. Linux client will use multimedia parameter for xfreerdp'), tab='Linux Client')
    alsa = gui.CheckBoxField(label=_('Use Alsa'), order=41, tooltip=_('If checked, Linux client will try to use ALSA, otherwise Pulse will be used'), tab='Linux Client')
    redirectHome = gui.CheckBoxField(label=_('Redirect home folder'), order=42, tooltip=_('If checked, Linux client will try to redirect /home local folder'), tab='Linux Client', defvalue=gui.FALSE)
    printerString = gui.TextField(label=_('Printer string'), order=43, tooltip=_('If printer is checked, the printer string used with xfreerdp client'), tab='Linux Client')
    smartcardString = gui.TextField(label=_('Smartcard string'), order=44, tooltip=_('If smartcard is checked, the smartcard string used with xfreerdp client'), tab='Linux Client')
    customParameters = gui.TextField(label=_('Custom parameters'), order=45, tooltip=_('If not empty, extra parameter to include for Linux Client (for example /usb:id,dev:054c:0268, or aything compatible with your xfreerdp client)'), tab='Linux Client')

    def isAvailableFor(self, userService: 'models.UserService', ip: str) -> bool:
        """
        Checks if the transport is available for the requested destination ip
        Override this in yours transports
        """
        logger.debug('Checking availability for %s', ip)
        ready = self.cache.get(ip)
        if ready is None:
            # Check again for ready
            if self.testServer(userService, ip, '3389') is True:
                self.cache.put(ip, 'Y', READY_CACHE_TIMEOUT)
                return True
            else:
                self.cache.put(ip, 'N', READY_CACHE_TIMEOUT)
        return ready == 'Y'

    def processedUser(self, userService: 'models.UserService', user: '******') -> str:
        v = self.processUserPassword(userService, user, '')
        return v['username']

    def processUserPassword(self, userService: 'models.UserService', user: '******', password: '******') -> typing.Dict[str, typing.Any]:
        username = user.getUsernameForAuth()

        if self.fixedName.value != '':
            username = self.fixedName.value

        proc = username.split('@')
        if len(proc) > 1:
            domain = proc[1]
        else:
            domain = ''
        username = proc[0]

        if self.fixedPassword.value != '':
            password = self.fixedPassword.value
        if self.fixedDomain.value != '':
            domain = self.fixedDomain.value
        if self.useEmptyCreds.isTrue():
            username, password, domain = '', '', ''

        if self.withoutDomain.isTrue():
            domain = ''

        if domain != '':  # If has domain
            if '.' in domain:  # Dotter domain form
                username = username + '@' + domain
                domain = ''
            else:  # In case of a NETBIOS domain (not recomended), join it so processUserPassword can deal with it
                username = domain + '\\' + username
                domain = ''

        # Temporal "fix" to check if we do something on processUserPassword

        # Fix username/password acording to os manager
        username, password = userService.processUserPassword(username, password)

        # Recover domain name if needed
        if '\\' in username:
            username, domain = username.split('\\')

        return {'protocol': self.protocol, 'username': username, 'password': password, 'domain': domain}

    def getConnectionInfo(
            self,
            userService: typing.Union['models.UserService', 'models.ServicePool'],
            user: '******',
            password: str
        ) -> typing.Dict[str, str]:
        return self.processUserPassword(userService, user, password)

    def getScript(self, scriptNameTemplate: str, osName: str, params: typing.Dict[str, typing.Any]) -> typing.Tuple[str, str, typing.Dict[str, typing.Any]]:
        # Reads script
        scriptNameTemplate = scriptNameTemplate.format(osName)
        with open(os.path.join(os.path.dirname(__file__), scriptNameTemplate)) as f:
            script = f.read()
        # Reads signature
        with open(os.path.join(os.path.dirname(__file__), scriptNameTemplate + '.signature')) as f:
            signature = f.read()
        return script, signature, params
Example #20
0
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 based 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 an user')
    # : If we need to generate a "Level 2" cache for this service (i.e., L1
    # : could be running machines and L2 suspended machines)
    usesCache_L2 = True
    # : Tooltip shown to user when this item is pointed at admin interface, None
    # : also because we don't use it
    cacheTooltip_L2 = _(
        'Number of desired VMs to keep stopped waiting for use')

    # : If the service needs a s.o. manager (managers are related to agents
    # : provided by services itselfs, i.e. virtual machines with actors)
    needsManager = True
    # : If true, the system can't do an automatic assignation of a deployed user
    # : service from this service
    mustAssignManually = False
    canReset = True

    # : 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
    datastore = gui.ChoiceField(label=_("Datastore"),
                                order=100,
                                tooltip=_('Service clones datastore'),
                                required=True)

    template = gui.ChoiceField(label=_("Base Template"),
                               order=110,
                               tooltip=_('Service base template'),
                               tab=_('Machine'),
                               required=True)

    baseName = gui.TextField(
        label=_('Machine Names'),
        rdonly=False,
        order=111,
        tooltip=_('Base name for clones from this machine'),
        tab=_('Machine'),
        required=True)

    lenName = gui.NumericField(
        length=1,
        label=_('Name Length'),
        defvalue=5,
        order=112,
        tooltip=_('Size of numeric part for the names of these machines'),
        tab=_('Machine'),
        required=True)

    def initialize(self, values: 'Module.ValuesType') -> None:
        """
        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 not values:
            return

        self.baseName.value = validators.validateHostname(self.baseName.value,
                                                          maxLength=15 -
                                                          self.lenName.num(),
                                                          asPattern=True)

    def parent(self) -> 'OpenNebulaProvider':
        return typing.cast('OpenNebulaProvider', super().parent())

    def initGui(self) -> None:
        """
        Loads required values inside
        """

        t: 'on.types.TemplateType'
        self.template.setValues([
            gui.choiceItem(t.id, t.name) for t in self.parent().getTemplates()
        ])

        d: 'on.types.StorageType'
        self.datastore.setValues([
            gui.choiceItem(d.id, d.name)
            for d in self.parent().getDatastores()
        ])

    def sanitizeVmName(self, name: str) -> str:
        return self.parent().sanitizeVmName(name)

    def makeTemplate(self, templateName: str) -> str:
        return self.parent().makeTemplate(self.template.value, templateName,
                                          self.datastore.value)

    def checkTemplatePublished(self, templateId: str) -> bool:
        return self.parent().checkTemplatePublished(templateId)

    def deployFromTemplate(self, name: str, templateId: str) -> str:
        """
        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 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
        """
        logger.debug('Deploying from template %s machine %s', templateId, name)
        # self.datastoreHasSpace()
        return self.parent().deployFromTemplate(name, templateId)

    def removeTemplate(self, templateId: str) -> None:
        """
        invokes removeTemplate from parent provider
        """
        self.parent().removeTemplate(templateId)

    def getMachineState(self, machineId: str) -> 'on.types.VmState':
        """
        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 getMachineSubstate(self, machineId: str) -> int:
        """
        On OpenNebula, the machine can be "active" but not "running".
        Any active machine will have a LCM_STATE, that is what we get here
        """
        return self.parent().getMachineSubstate(machineId)

    def startMachine(self, machineId: str) -> None:
        """
        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:
        """
        self.parent().startMachine(machineId)

    def stopMachine(self, machineId: str) -> None:
        """
        Tries to stop a machine. No check is done, it is simply requested to OpenNebula

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.parent().stopMachine(machineId)

    def suspendMachine(self, machineId: str) -> None:
        """
        Tries to suspend machine. No check is done, it is simply requested to OpenNebula

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.parent().suspendMachine(machineId)

    def shutdownMachine(self, machineId: str) -> None:
        """
        Tries to "gracefully" shutdown machine. No check is done, it is simply requested to OpenNebula

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.parent().shutdownMachine(machineId)

    def resetMachine(self, machineId: str) -> None:
        self.parent().resetMachine(machineId)

    def removeMachine(self, machineId: str) -> None:
        """
        Tries to delete a machine. No check is done, it is simply requested to OpenNebula

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.parent().removeMachine(machineId)

    def getNetInfo(
            self,
            machineId: str,
            networkId: typing.Optional[str] = None) -> typing.Tuple[str, str]:
        """
        Changes the mac address of first nic of the machine to the one specified
        """
        return self.parent().getNetInfo(machineId, networkId=None)

    def getBaseName(self) -> str:
        """
        Returns the base name
        """
        return self.baseName.value

    def getLenName(self) -> int:
        """
        Returns the length of numbers part
        """
        return self.lenName.num()

    def getConsoleConnection(self,
                             machineId: str) -> typing.Dict[str, typing.Any]:
        return self.parent().getConsoleConnection(machineId)

    def desktopLogin(self, machineId: str, username: str, password: str,
                     domain: str) -> typing.Dict[str, typing.Any]:
        return self.parent().desktopLogin(machineId, username, password,
                                          domain)
class OVirtLinkedService(Service):
    '''
    oVirt Linked clones service. This is based on creating a template from selected vm, and then use it to
    '''
    # : 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 = _('oVirt/RHEV Linked Clone')
    # : Type used internally to identify this provider
    typeType = 'oVirtLinkedService'
    # : Description shown at administration interface for this provider
    typeDescription = _(
        'oVirt Services based on templates and COW (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 = 'service.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 we need to generate a "Level 2" cache for this service (i.e., L1
    # : could be running machines and L2 suspended machines)
    usesCache_L2 = True
    # : Tooltip shown to user when this item is pointed at admin interface, None
    # : also because we don't use it
    cacheTooltip_L2 = _(
        'Number of desired machines to keep suspended waiting for use')

    # : If the service needs a s.o. manager (managers are related to agents
    # : provided by services itselfs, i.e. virtual machines with actors)
    needsManager = 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 = OVirtPublication
    # : Types of deploys (services in cache and/or assigned to users)
    deployedType = OVirtLinkedDeployment

    # Now the form part
    machine = gui.ChoiceField(label=_("Base Machine"),
                              order=1,
                              tooltip=_('Service base machine'),
                              required=True)
    cluster = gui.ChoiceField(label=_("Cluster"),
                              order=2,
                              fills={
                                  'callbackName': 'ovFillResourcesFromCluster',
                                  'function': oVirtHelpers.getResources,
                                  'parameters': ['cluster', 'ov', 'ev']
                              },
                              tooltip=_("Cluster to contain services"),
                              required=True)

    datastore = gui.ChoiceField(
        label=_("Datastore Domain"),
        rdonly=False,
        order=3,
        tooltip=_('Datastore domain where to publish and put incrementals'),
        required=True)

    minSpaceGB = gui.NumericField(length=3,
                                  label=_('Reserved Space'),
                                  defvalue='32',
                                  order=4,
                                  tooltip=_('Minimal free space in GB'),
                                  required=True)

    memory = gui.NumericField(label=_("Memory (Mb)"),
                              length=4,
                              defvalue=512,
                              rdonly=False,
                              order=5,
                              tooltip=_('Memory assigned to machines'),
                              required=True)

    memoryGuaranteed = gui.NumericField(
        label=_("Memory Guaranteed (Mb)"),
        length=4,
        defvalue=256,
        rdonly=False,
        order=6,
        tooltip=_('Physical memory guaranteed to machines'),
        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)

    display = gui.ChoiceField(
        label=_('Display'),
        rdonly=False,
        order=8,
        tooltip=_('Display type (only for administration purposes)'),
        values=[
            gui.choiceItem('spice', 'Spice'),
            gui.choiceItem('vnc', 'Vnc')
        ],
        defvalue='1'  # Default value is the ID of the choicefield
    )

    ov = gui.HiddenField()
    ev = gui.HiddenField(
    )  # 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:
            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'))
            if int(self.memory.value) < 256 or int(
                    self.memoryGuaranteed.value) < 256:
                raise Service.ValidationException(
                    _('The minimum allowed memory is 256 Mb'))
            if int(self.memoryGuaranteed.value) > int(self.memory.value):
                self.memoryGuaranteed.value = self.memory.value

    def initGui(self):
        '''
        Loads required values inside
        '''

        # Here we have to use "default values", cause values aren't used at form initialization
        # This is that value is always '', so if we want to change something, we have to do it
        # at defValue
        self.ov.defValue = self.parent().serialize()
        self.ev.defValue = self.parent().env().key()

        machines = self.parent().getMachines()
        vals = []
        for m in machines:
            vals.append(gui.choiceItem(m['id'], m['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.machine.setValues(vals)

        clusters = self.parent().getClusters()
        vals = []
        for c in clusters:
            vals.append(gui.choiceItem(c['id'], c['name']))
        self.cluster.setValues(vals)

    def datastoreHasSpace(self):
        # Get storages for that datacenter
        logger.debug('Checking datastore space for {0}'.format(
            self.datastore.value))
        info = self.parent().getStorageInfo(self.datastore.value)
        logger.debug('Datastore Info: {0}'.format(info))
        availableGB = info['available'] / (1024 * 1024 * 1024)
        if availableGB < self.minSpaceGB.num():
            raise Exception(
                'Not enough free space available: (Needs at least {0} GB and there is only {1} GB '
                .format(self.minSpaceGB.num(), availableGB))

    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 makeTemplate(self, name, comments):
        '''
        Invokes makeTemplate from parent provider, completing params

        Args:
            name: Name to assign to template (must be previously "sanitized"
            comments: Comments (UTF-8) to add to template

        Returns:
            template Id of the template created

        Raises an exception if operation fails.
        '''

        # Checks datastore size
        # Get storages for that datacenter

        self.datastoreHasSpace()
        return self.parent().makeTemplate(name, comments, self.machine.value,
                                          self.cluster.value,
                                          self.datastore.value,
                                          self.display.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
Example #22
0
class OVirtProvider(services.ServiceProvider):  # pylint: disable=too-many-public-methods
    """
    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 = [OVirtLinkedService]
    # : 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 = _('oVirt/RHEV Platform Provider')
    # : Type used internally to identify this provider
    typeType = 'oVirtPlatform'
    # : Description shown at administration interface for this provider
    typeDescription = _('oVirt 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"
    ovirtVersion = gui.ChoiceField(
        order=1,
        label=_('oVirt Version'),
        tooltip=_('oVirt Server Version'),
        # In this case, the choice can have none value selected by default
        required=True,
        rdonly=False,
        values=[
            gui.choiceItem('4', '4.x'),
        ],
        defvalue='4'  # Default value is the ID of the choicefield
    )

    host = gui.TextField(length=64,
                         label=_('Host'),
                         order=2,
                         tooltip=_('oVirt Server IP or Hostname'),
                         required=True)
    username = gui.TextField(
        length=32,
        label=_('Username'),
        order=3,
        tooltip=_(
            'User with valid privileges on oVirt, (use "user@domain" form)'),
        required=True,
        defvalue='admin@internal')
    password = gui.PasswordField(lenth=32,
                                 label=_('Password'),
                                 order=4,
                                 tooltip=_('Password of the user of oVirt'),
                                 required=True)

    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 oVirt'),
        required=True,
        tab=gui.ADVANCED_TAB)
    macsRange = gui.TextField(
        length=36,
        label=_('Macs range'),
        defvalue='52:54:00:00:00:00-52:54:00:FF:FF:FF',
        order=91,
        rdonly=True,
        tooltip=_('Range of valid macs for UDS managed machines'),
        required=True,
        tab=gui.ADVANCED_TAB)

    # Own variables
    _api: typing.Optional[client.Client] = None

    # oVirt engine, right now, only permits a connection to one server and only one per instance
    # If we want to connect to more than one server, we need keep locked access to api, change api server, etc..
    # We have implemented an "exclusive access" client that will only connect to one server at a time (using locks)
    # and this way all will be fine
    def __getApi(self) -> client.Client:
        """
        Returns the connection API object for oVirt (using ovirtsdk)
        """
        if self._api is None:
            self._api = client.Client(self.host.value, self.username.value,
                                      self.password.value, self.timeout.value,
                                      self.cache)

        return self._api

    # There is more fields type, but not here the best place to cover it
    def initialize(self, values: 'Module.ValuesType') -> 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)
            logger.debug(self.host.value)

    def testConnection(self) -> bool:
        """
        Test that conection to oVirt server is fine

        Returns

            True if all went fine, false if id didn't
        """

        return self.__getApi().test()

    def testValidVersion(self):
        """
        Checks that this version of ovirt if "fully functional" and does not needs "patchs'
        """
        return list(self.__getApi().isFullyFunctionalVersion())

    def getMachines(
        self,
        force: bool = False
    ) -> typing.List[typing.MutableMapping[str, typing.Any]]:
        """
        Obtains the list of machines inside oVirt.
        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'
        """

        return self.__getApi().getVms(force)

    def getClusters(
        self,
        force: bool = False
    ) -> typing.List[typing.MutableMapping[str, typing.Any]]:
        """
        Obtains the list of clusters inside oVirt.

        Args:
            force: If true, force to update the cache, if false, tries to first
            get data from cache and, if valid, return this.

        Returns
            Filters out clusters not attached to any datacenter
            An array of dictionaries, containing:
                'name'
                'id'
                'datacenter_id'
        """

        return self.__getApi().getClusters(force)

    def getClusterInfo(
            self,
            clusterId: str,
            force: bool = False) -> typing.MutableMapping[str, typing.Any]:
        """
        Obtains the cluster info

        Args:
            datacenterId: Id of the cluster to get information about it
            force: If true, force to update the cache, if false, tries to first
            get data from cache and, if valid, return this.

        Returns

            A dictionary with following values
                'name'
                'id'
                'datacenter_id'
        """
        return self.__getApi().getClusterInfo(clusterId, force)

    def getDatacenterInfo(
            self,
            datacenterId: str,
            force: bool = False) -> typing.MutableMapping[str, typing.Any]:
        """
        Obtains the datacenter info

        Args:
            datacenterId: Id of the datacenter to get information about it
            force: If true, force to update the cache, if false, tries to first
            get data from cache and, if valid, return this.

        Returns

            A dictionary with following values
                'name'
                'id'
                'storage_type' -> ('isisi', 'nfs', ....)
                'storage_format' -> ('v1', v2')
                'description'
                'storage' -> array of dictionaries, with:
                   'id' -> Storage id
                   'name' -> Storage name
                   'type' -> Storage type ('data', 'iso')
                   'available' -> Space available, in bytes
                   'used' -> Space used, in bytes
                   'active' -> True or False

        """
        return self.__getApi().getDatacenterInfo(datacenterId, force)

    def getStorageInfo(
            self,
            storageId: str,
            force: bool = False) -> typing.MutableMapping[str, typing.Any]:
        """
        Obtains the storage info

        Args:
            storageId: Id of the storage to get information about it
            force: If true, force to update the cache, if false, tries to first
            get data from cache and, if valid, return this.

        Returns

            A dictionary with following values
               'id' -> Storage id
               'name' -> Storage name
               'type' -> Storage type ('data', 'iso')
               'available' -> Space available, in bytes
               'used' -> Space used, in bytes
               # 'active' -> True or False --> This is not provided by api?? (api.storagedomains.get)

        """
        return self.__getApi().getStorageInfo(storageId, force)

    def makeTemplate(self, name: str, comments: str, machineId: str,
                     clusterId: str, storageId: str, displayType: str) -> str:
        """
        Publish the machine (makes a template from it so we can create COWs) and returns the template id of
        the creating machine

        Args:
            name: Name of the machine (care, only ascii characters and no spaces!!!)
            machineId: id of the machine to be published
            clusterId: id of the cluster that will hold the machine
            storageId: id of the storage tuat will contain the publication AND linked clones
            displayType: type of display (for oVirt admin interface only)

        Returns
            Raises an exception if operation could not be acomplished, or returns the id of the template being created.
        """
        return self.__getApi().makeTemplate(name, comments, machineId,
                                            clusterId, storageId, displayType)

    def getTemplateState(self, templateId: str) -> str:
        """
        Returns current template state.

        Returned values could be:
            ok
            locked
            removed

        (don't know if ovirt returns something more right now, will test what happens when template can't be published)
        """
        return self.__getApi().getTemplateState(templateId)

    def getMachineState(self, machineId: str) -> str:
        """
        Returns the state of the machine
        This method do not uses cache at all (it always tries to get machine state from oVirt 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 removeTemplate(self, templateId: str) -> None:
        """
        Removes a template from ovirt server

        Returns nothing, and raises an Exception if it fails
        """
        return self.__getApi().removeTemplate(templateId)

    def deployFromTemplate(self, name: str, comments: str, templateId: str,
                           clusterId: str, displayType: str, usbType: str,
                           memoryMB: int, guaranteedMB: int) -> str:
        """
        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 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
        """
        return self.__getApi().deployFromTemplate(name, comments, templateId,
                                                  clusterId, displayType,
                                                  usbType, memoryMB,
                                                  guaranteedMB)

    def startMachine(self, machineId: str) -> None:
        """
        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:
        """
        self.__getApi().startMachine(machineId)

    def stopMachine(self, machineId: str) -> None:
        """
        Tries to start a machine. No check is done, it is simply requested to oVirt

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.__getApi().stopMachine(machineId)

    def suspendMachine(self, machineId: str) -> None:
        """
        Tries to start a machine. No check is done, it is simply requested to oVirt

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.__getApi().suspendMachine(machineId)

    def removeMachine(self, machineId: str) -> None:
        """
        Tries to delete a machine. No check is done, it is simply requested to oVirt

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.__getApi().removeMachine(machineId)

    def updateMachineMac(self, machineId: str, macAddres: str) -> None:
        """
        Changes the mac address of first nic of the machine to the one specified
        """
        self.__getApi().updateMachineMac(machineId, macAddres)

    def fixUsb(self, machineId: str) -> None:
        self.__getApi().fixUsb(machineId)

    def getMacRange(self) -> str:
        return self.macsRange.value

    def getConsoleConnection(
        self, machineId: str
    ) -> typing.Optional[typing.MutableMapping[str, typing.Any]]:
        return self.__getApi().getConsoleConnection(machineId)

    @staticmethod
    def test(env: 'Environment',
             data: 'Module.ValuesType') -> typing.List[typing.Any]:
        """
        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..

        """
        # try:
        #    # We instantiate the provider, but this may fail...
        #    instance = Provider(env, data)
        #    logger.debug('Methuselah has {0} years and is {1} :-)'
        #                 .format(instance.methAge.value, instance.methAlive.value))
        # except ServiceProvider.ValidationException as e:
        #    # If we say that meth is alive, instantiation will
        #    return [False, str(e)]
        # except Exception as e:
        #    logger.exception("Exception caugth!!!")
        #    return [False, str(e)]
        # return [True, _('Nothing tested, but all went fine..')]
        ov = OVirtProvider(env, data)
        if ov.testConnection() is True:
            return ov.testValidVersion()

        return [False, _("Connection failed. Check connection params")]
Example #23
0
class HTML5RDPTransport(transports.Transport):
    """
    Provides access via RDP to service.
    This transport can use an domain. If username processed by authenticator contains '@', it will split it and left-@-part will be username, and right password
    """

    typeName = _('HTML5 RDP')
    typeType = 'HTML5RDPTransport'
    typeDescription = _('RDP protocol using HTML5 client')
    iconFile = 'html5.png'

    ownLink = True
    supportedOss = OsDetector.allOss
    protocol = transports.protocols.RDP
    group = transports.TUNNELED_GROUP

    guacamoleServer = gui.TextField(
        label=_('Tunnel Server'),
        order=1,
        tooltip=
        _('Host of the tunnel server (use http/https & port if needed) as accesible from users'
          ),
        defvalue='https://',
        length=64,
        required=True,
        tab=gui.TUNNEL_TAB,
    )

    useGlyptodonTunnel = gui.CheckBoxField(
        label=_('Use Glyptodon Enterprise tunnel'),
        order=2,
        tooltip=
        _('If checked, UDS will use Glyptodon Enterprise Tunnel for HTML tunneling instead of UDS Tunnel'
          ),
        tab=gui.TUNNEL_TAB,
    )

    useEmptyCreds = gui.CheckBoxField(
        label=_('Empty creds'),
        order=3,
        tooltip=_('If checked, the credentials used to connect will be emtpy'),
        tab=gui.CREDENTIALS_TAB,
    )
    fixedName = gui.TextField(
        label=_('Username'),
        order=4,
        tooltip=_(
            'If not empty, this username will be always used as credential'),
        tab=gui.CREDENTIALS_TAB,
    )
    fixedPassword = gui.PasswordField(
        label=_('Password'),
        order=5,
        tooltip=_(
            'If not empty, this password will be always used as credential'),
        tab=gui.CREDENTIALS_TAB,
    )
    withoutDomain = gui.CheckBoxField(
        label=_('Without Domain'),
        order=6,
        tooltip=
        _('If checked, the domain part will always be emptied (to connecto to xrdp for example is needed)'
          ),
        tab=gui.CREDENTIALS_TAB,
    )
    fixedDomain = gui.TextField(
        label=_('Domain'),
        order=7,
        tooltip=
        _('If not empty, this domain will be always used as credential (used as DOMAIN\\user)'
          ),
        tab=gui.CREDENTIALS_TAB,
    )
    wallpaper = gui.CheckBoxField(
        label=_('Show wallpaper'),
        order=20,
        tooltip=
        _('If checked, the wallpaper and themes will be shown on machine (better user experience, more bandwidth)'
          ),
        tab=gui.PARAMETERS_TAB,
    )
    desktopComp = gui.CheckBoxField(
        label=_('Allow Desk.Comp.'),
        order=22,
        tooltip=_('If checked, desktop composition will be allowed'),
        tab=gui.PARAMETERS_TAB,
    )
    smooth = gui.CheckBoxField(
        label=_('Font Smoothing'),
        order=23,
        tooltip=_(
            'If checked, fonts smoothing will be allowed (windows clients only)'
        ),
        tab=gui.PARAMETERS_TAB,
    )
    enableAudio = gui.CheckBoxField(
        label=_('Enable Audio'),
        order=24,
        tooltip=
        _('If checked, the audio will be redirected to remote session (if client browser supports it)'
          ),
        tab=gui.PARAMETERS_TAB,
        defvalue=gui.TRUE,
    )
    enableAudioInput = gui.CheckBoxField(
        label=_('Enable Microphone'),
        order=24,
        tooltip=
        _('If checked, the microphone will be redirected to remote session (if client browser supports it)'
          ),
        tab=gui.PARAMETERS_TAB,
    )
    enablePrinting = gui.CheckBoxField(
        label=_('Enable Printing'),
        order=25,
        tooltip=
        _('If checked, the printing will be redirected to remote session (if client browser supports it)'
          ),
        tab=gui.PARAMETERS_TAB,
    )
    enableFileSharing = gui.ChoiceField(
        label=_('File Sharing'),
        order=22,
        tooltip=_('File upload/download redirection policy'),
        defvalue='false',
        values=[
            {
                'id': 'false',
                'text': 'Disable file sharing'
            },
            {
                'id': 'down',
                'text': 'Allow download only'
            },
            {
                'id': 'up',
                'text': 'Allow upload only'
            },
            {
                'id': 'true',
                'text': 'Enable file sharing'
            },
        ],
        tab=gui.PARAMETERS_TAB,
    )

    serverLayout = gui.ChoiceField(
        order=26,
        label=_('Layout'),
        tooltip=_('Keyboards Layout of server'),
        required=True,
        values=[
            gui.choiceItem('-', 'default'),
            gui.choiceItem('en-us-qwerty', _('English (US) keyboard')),
            gui.choiceItem('en-gb-qwerty', _('English (GB) keyboard')),
            gui.choiceItem('es-es-qwerty', _('Spanish keyboard')),
            gui.choiceItem('es-latam-qwerty', _('Latin American keyboard')),
            gui.choiceItem('de-de-qwertz', _('German keyboard (qwertz)')),
            gui.choiceItem('fr-fr-azerty', _('French keyboard (azerty)')),
            gui.choiceItem('fr-ch-qwertz',
                           _('Swiss French keyboard (qwertz)')),
            gui.choiceItem('de-ch-qwertz',
                           _('Swiss German keyboard (qwertz)')),
            gui.choiceItem('it-it-qwerty', _('Italian keyboard')),
            gui.choiceItem('sv-se-qwerty', _('Swedish keyboard')),
            gui.choiceItem('ja-jp-qwerty', _('Japanese keyboard')),
            gui.choiceItem('pt-br-qwerty', _('Brazilian keyboard')),
            gui.choiceItem('failsafe', _('Failsafe')),
        ],
        defvalue='-',
        tab=gui.PARAMETERS_TAB,
    )
    security = gui.ChoiceField(
        order=27,
        label=_('Security'),
        tooltip=_('Connection security mode for Guacamole RDP connection'),
        required=True,
        values=[
            gui.choiceItem(
                'any', _('Any (Allow the server to choose the type of auth)')),
            gui.choiceItem(
                'rdp',
                _('RDP (Standard RDP encryption. Should be supported by all servers)'
                  ),
            ),
            gui.choiceItem(
                'nla',
                _('NLA (Network Layer authentication. Requires VALID username&password, or connection will fail)'
                  ),
            ),
            gui.choiceItem(
                'nla-ext',
                _('NLA extended (Network Layer authentication. Requires VALID username&password, or connection will fail)'
                  ),
            ),
            gui.choiceItem('tls',
                           _('TLS (Transport Security Layer encryption)')),
        ],
        defvalue='any',
        tab=gui.PARAMETERS_TAB,
    )

    ticketValidity = gui.NumericField(
        length=3,
        label=_('Ticket Validity'),
        defvalue='60',
        order=90,
        tooltip=
        _('Allowed time, in seconds, for HTML5 client to reload data from UDS Broker. The default value of 60 is recommended.'
          ),
        required=True,
        minValue=60,
        tab=gui.ADVANCED_TAB,
    )

    forceNewWindow = gui.ChoiceField(
        order=91,
        label=_('Force new HTML Window'),
        tooltip=_('Select windows behavior for new connections on HTML5'),
        required=True,
        values=[
            gui.choiceItem(
                gui.FALSE,
                _('Open every connection on the same window, but keeps UDS window.'
                  ),
            ),
            gui.choiceItem(
                gui.TRUE,
                _('Force every connection to be opened on a new window.')),
            gui.choiceItem(
                'overwrite',
                _('Override UDS window and replace it with the connection.'),
            ),
        ],
        defvalue=gui.FALSE,
        tab=gui.ADVANCED_TAB,
    )

    customGEPath = gui.TextField(
        label=_('Glyptodon Enterprise context path'),
        order=92,
        tooltip=
        _('Customized path for Glyptodon Enterprise tunnel. (Only valid for Glyptodon Enterprise Tunnel)'
          ),
        defvalue='/',
        length=128,
        required=False,
        tab=gui.ADVANCED_TAB,
    )

    def initialize(self, values: 'Module.ValuesType'):
        if not values:
            return
        # Strip spaces
        self.guacamoleServer.value = self.guacamoleServer.value.strip()
        if self.guacamoleServer.value[0:4] != 'http':
            raise transports.Transport.ValidationException(
                _('The server must be http or https'))
        if self.useEmptyCreds.isTrue() and self.security.value != 'rdp':
            raise transports.Transport.ValidationException(
                _('Empty credentials (on Credentials tab) is only allowed with Security level (on Parameters tab) set to "RDP"'
                  ))

    # Same check as normal RDP transport
    def isAvailableFor(self, userService: 'models.UserService',
                       ip: str) -> bool:
        """
        Checks if the transport is available for the requested destination ip
        Override this in yours transports
        """
        logger.debug('Checking availability for %s', ip)
        ready = self.cache.get(ip)
        if not ready:
            # Check again for readyness
            if self.testServer(userService, ip, '3389') is True:
                self.cache.put(ip, 'Y', READY_CACHE_TIMEOUT)
                return True
            self.cache.put(ip, 'N', READY_CACHE_TIMEOUT)
        return ready == 'Y'

    def processedUser(self, userService: 'models.UserService',
                      user: '******') -> str:
        v = self.processUserAndPassword(userService, user, '')
        return v['username']

    def processUserAndPassword(self, userService: 'models.UserService',
                               user: '******',
                               password: str) -> typing.Dict[str, str]:
        username: str = user.getUsernameForAuth()

        if self.fixedName.value != '':
            username = self.fixedName.value

        proc = username.split('@')
        domain = proc[1] if len(proc) > 1 else ''
        username = proc[0]

        if self.fixedPassword.value != '':
            password = self.fixedPassword.value

        azureAd = False
        if self.fixedDomain.value != '':
            if self.fixedDomain.value.lower() == 'azuread':
                azureAd = True
            else:
                domain = self.fixedDomain.value

        if self.useEmptyCreds.isTrue():
            username, password, domain = '', '', ''

        # If no domain to be transfered, set it to ''
        if self.withoutDomain.isTrue():
            domain = ''

        if '.' in domain:  # Dotter domain form
            username = username + '@' + domain
            domain = ''

        # If AzureAD, include it on username
        if azureAd:
            username = '******' + username

        # Fix username/password acording to os manager
        username, password = userService.processUserPassword(
            username, password)

        return {
            'protocol': self.protocol,
            'username': username,
            'password': password,
            'domain': domain,
        }

    def getLink(  # pylint: disable=too-many-locals
        self,
        userService: 'models.UserService',
        transport: 'models.Transport',
        ip: str,
        os: typing.Dict[str, str],
        user: '******',
        password: str,
        request: 'HttpRequest',
    ) -> str:
        credsInfo = self.processUserAndPassword(userService, user, password)
        username, password, domain = (
            credsInfo['username'],
            credsInfo['password'],
            credsInfo['domain'],
        )

        scrambler = cryptoManager().randomString(32)
        passwordCrypted = cryptoManager().symCrypt(password, scrambler)

        # Build params dict
        params = {
            'protocol': 'rdp',
            'hostname': ip,
            'username': username,
            'password': passwordCrypted,
            'resize-method': 'display-update',
            'ignore-cert': 'true',
            'security': self.security.value,
            'drive-path': '/share/{}'.format(user.uuid),
            'create-drive-path': 'true',
            'ticket-info': {
                'userService': userService.uuid,
                'user': userService.user.uuid,
            },
        }

        if False:  # Future imp
            sanitize = lambda x: re.sub("[^a-zA-Z0-9_-]", "_", x)
            params['recording-path'] = (
                '/share/recording/' + sanitize(user.manager.name) + '_' +
                sanitize(user.name) + '/' +
                getSqlDatetime().strftime('%Y%m%d-%H%M'))
            params['create-recording-path'] = 'true'

        if domain:
            params['domain'] = domain

        if self.enableFileSharing.value == 'true':
            params['enable-drive'] = 'true'
        elif self.enableFileSharing.value == 'down':
            params['enable-drive'] = 'true'
            params['disable-upload'] = 'true'
        elif self.enableFileSharing.value == 'up':
            params['enable-drive'] = 'true'
            params['disable-download'] = 'true'

        if self.serverLayout.value != '-':
            params['server-layout'] = self.serverLayout.value

        if not self.enableAudio.isTrue():
            params['disable-audio'] = 'true'
        elif self.enableAudioInput.isTrue():
            params['enable-audio-input'] = 'true'

        if self.enablePrinting.isTrue():
            params['enable-printing'] = 'true'
            params['printer-name'] = 'UDS-Printer'

        if self.wallpaper.isTrue():
            params['enable-wallpaper'] = 'true'

        if self.desktopComp.isTrue():
            params['enable-desktop-composition'] = 'true'

        if self.smooth.isTrue():
            params['enable-font-smoothing'] = 'true'

        logger.debug('RDP Params: %s', params)

        ticket = models.TicketStore.create(params,
                                           validity=self.ticketValidity.num())

        onw = ''
        if self.forceNewWindow.value == gui.TRUE:
            onw = '&o_n_w={}'
        elif self.forceNewWindow.value == 'overwrite':
            onw = '&o_s_w=yes'
        onw = onw.format(hash(transport.name))
        path = (self.customGEPath.value
                if self.useGlyptodonTunnel.isTrue() else '/guacamole')
        # Remove trailing /
        if path[-1] == '/':
            path = path[:-1]

        return str("{server}{path}/#/?data={ticket}.{scrambler}{onw}".format(
            server=self.guacamoleServer.value,
            path=path,
            ticket=ticket,
            scrambler=scrambler,
            onw=onw,
        ))