Пример #1
0
class IPSingleMachineService(IPServiceBase):
    # Gui
    ip = gui.TextField(length=64,
                       label=_('Machine IP'),
                       order=1,
                       tooltip=_('Machine IP'),
                       required=True)

    # Description of service
    typeName = _('Static Single IP')
    typeType = 'IPSingleMachineService'
    typeDescription = _(
        'This service provides access to POWERED-ON Machine by IP')
    iconFile = 'machine.png'

    # Characteristics of service
    maxDeployed = -1  # If the service provides more than 1 "provided service" (-1 = no limit, 0 = ???? (do not use it!!!), N = max number to deploy
    usesCache = False  # Cache are running machine awaiting to be assigned
    usesCache_L2 = False  # L2 Cache are running machines in suspended state
    needsManager = False  # If the service needs a s.o. manager (managers are related to agents provided by services itselfs, i.e. virtual machines with agent)
    mustAssignManually = False  # If true, the system can't do an automatic assignation of a deployed user service from this service

    deployedType = IPMachineDeployed

    servicesTypeProvided = (serviceTypes.VDI, )

    def initialize(self, values: 'Module.ValuesType') -> None:
        if values is None:
            return

        if not net.isValidHost(self.ip.value):
            raise IPServiceBase.ValidationException(
                gettext('Invalid server used: "{}"'.format(self.ip.value)))

    def getUnassignedMachine(self) -> typing.Optional[str]:
        ip: typing.Optional[str] = None
        try:
            counter = self.storage.getPickle('counter')
            counter = counter + 1 if counter is not None else 1
            self.storage.putPickle('counter', counter)
            ip = '{}~{}'.format(self.ip.value, counter)
        except Exception:
            ip = None
            logger.exception("Exception at getUnassignedMachine")

        return ip

    def unassignMachine(self, ip: str) -> None:
        pass
Пример #2
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();'
            )
        )
Пример #3
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 = False  # 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'),
        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

    _api: typing.Optional['openstack.Client'] = None

    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:
            tools.checkValidBasename(self.baseName.value, self.lenName.num())

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

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

    def initGui(self):
        """
        Loads required values inside
        """
        api = self.parent().api()

        if not self.parent().legacy and self.parent().region.value:
            regions = [
                gui.choiceItem(self.parent().region.value,
                               self.parent().region.value)
            ]
        else:
            regions = [
                gui.choiceItem(r['id'], r['id']) for r in api.listRegions()
            ]

        self.region.setValues(regions)

        if not self.parent().legacy and self.parent().tenant.value:
            tenants = [
                gui.choiceItem(self.parent().tenant.value,
                               self.parent().tenant.value)
            ]
        else:
            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) -> 'openstack.Client':
        if not self._api:
            self._api = self.parent().api(projectId=self.project.value,
                                          region=self.region.value)

        return self._api

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

    def makeTemplate(self,
                     templateName: str,
                     description: typing.Optional[str] = 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 = description or 'UDS Template snapshot'
        return self.api.createVolumeSnapshot(self.volume.value, templateName,
                                             description)

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

    def deployFromTemplate(self, name: str, snapshotId: str) -> str:
        """
        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 %s machine %s', 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: str) -> None:
        """
        invokes removeTemplate from parent provider
        """
        self.api.deleteSnapshot(templateId)

    def getMachineState(self, machineId: str) -> str:
        """
        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.
                SHUTOFF. The server was powered down by the user, either through the OpenStack Compute API or from within the server. For example, the user issued a shutdown -h command from within the server. If the OpenStack Compute manager detects that the VM was powered down, it transitions the server to the SHUTOFF status.
        """
        server = self.api.getServer(machineId)
        if server['status'] in ('ERROR', 'DELETED'):
            logger.warning('Got server status %s for %s: %s', server['status'],
                           machineId, server.get('fault'))
        return server['status']

    def startMachine(self, machineId: str) -> None:
        """
        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: str) -> None:
        """
        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: str) -> None:
        """
        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: str) -> None:
        """
        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: str) -> None:
        """
        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: str) -> None:
        """
        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: str) -> typing.Tuple[str, str]:
        """
        Gets the mac address of first nic of the machine
        """
        net = self.api.getServer(machineId)['addresses']
        vals = next(
            iter(net.values())
        )[0]  # Returns "any" mac address of any interface. We just need only one interface info
        # vals = six.next(six.itervalues(net))[0]
        return vals['OS-EXT-IPS-MAC:mac_addr'].upper(), vals['addr']

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

    def getLenName(self) -> int:
        """
        Returns the length of numbers part
        """
        return int(self.lenName.value)
Пример #4
0
class TRDPTransport(BaseRDPTransport):
    """
    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 = _('RDP')
    typeType = 'TSRDPTransport'
    typeDescription = _('RDP Protocol. Tunneled connection.')
    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,
    )

    tunnelWait = gui.NumericField(
        length=3,
        label=_('Tunnel wait time'),
        defvalue='60',
        minValue=5,
        maxValue=65536,
        order=2,
        tooltip=_('Maximum time to wait before closing the tunnel listener'),
        required=True,
        tab=gui.TUNNEL_TAB,
    )

    verifyCertificate = gui.CheckBoxField(
        label=_('Force SSL certificate verification'),
        order=23,
        tooltip=
        _('If enabled, the certificate of tunnel server will be verified (recommended).'
          ),
        defvalue=gui.TRUE,
        tab=gui.TUNNEL_TAB,
    )

    useEmptyCreds = BaseRDPTransport.useEmptyCreds
    fixedName = BaseRDPTransport.fixedName
    fixedPassword = BaseRDPTransport.fixedPassword
    withoutDomain = BaseRDPTransport.withoutDomain
    fixedDomain = BaseRDPTransport.fixedDomain
    allowSmartcards = BaseRDPTransport.allowSmartcards
    allowPrinters = BaseRDPTransport.allowPrinters
    allowDrives = BaseRDPTransport.allowDrives
    enforceDrives = BaseRDPTransport.enforceDrives
    allowSerials = BaseRDPTransport.allowSerials
    allowClipboard = BaseRDPTransport.allowClipboard
    allowAudio = BaseRDPTransport.allowAudio
    allowWebcam = BaseRDPTransport.allowWebcam

    wallpaper = BaseRDPTransport.wallpaper
    multimon = BaseRDPTransport.multimon
    aero = BaseRDPTransport.aero
    smooth = BaseRDPTransport.smooth
    showConnectionBar = BaseRDPTransport.showConnectionBar
    credssp = BaseRDPTransport.credssp

    screenSize = BaseRDPTransport.screenSize
    colorDepth = BaseRDPTransport.colorDepth

    alsa = BaseRDPTransport.alsa
    multimedia = BaseRDPTransport.multimedia
    printerString = BaseRDPTransport.printerString
    smartcardString = BaseRDPTransport.smartcardString
    customParameters = BaseRDPTransport.customParameters
    allowMacMSRDC = BaseRDPTransport.allowMacMSRDC
    customParametersMAC = BaseRDPTransport.customParametersMAC

    def initialize(self, values: 'Module.ValuesType'):
        if values:
            if values['tunnelServer'].count(':') != 1:
                raise transports.Transport.ValidationException(
                    _('Must use HOST:PORT in Tunnel Server Field'))

    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.Mapping[str, typing.Any]]:
        # We use helper to keep this clean
        # prefs = user.prefs('rdp')

        ci = self.getConnectionInfo(userService, user, password)
        username, password, domain = ci['username'], ci['password'], ci[
            'domain']

        # escape conflicting chars : Note, on 3.0 this should not be neccesary. Kept until more tests
        # password = password.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")

        # width, height = CommonPrefs.getWidthHeight(prefs)
        # depth = CommonPrefs.getDepth(prefs)
        width, height = self.screenSize.value.split('x')
        depth = self.colorDepth.value

        ticket = TicketStore.create_for_tunnel(
            userService=userService,
            port=3389,
            validity=self.tunnelWait.num() + 60,  # Ticket overtime
        )

        tunHost, tunPort = self.tunnelServer.value.split(':')

        r = RDPFile(width == '-1' or height == '-1',
                    width,
                    height,
                    depth,
                    target=os['OS'])
        r.enablecredsspsupport = ci.get(
            'sso') == 'True' or self.credssp.isTrue()
        r.address = '{address}'
        r.username = username
        r.password = password
        r.domain = domain
        r.redirectPrinters = self.allowPrinters.isTrue()
        r.redirectSmartcards = self.allowSmartcards.isTrue()
        r.redirectDrives = self.allowDrives.value
        r.redirectSerials = self.allowSerials.isTrue()
        r.enableClipboard = self.allowClipboard.isTrue()
        r.redirectAudio = self.allowAudio.isTrue()
        r.redirectWebcam = self.allowWebcam.isTrue()
        r.showWallpaper = self.wallpaper.isTrue()
        r.multimon = self.multimon.isTrue()
        r.desktopComposition = self.aero.isTrue()
        r.smoothFonts = self.smooth.isTrue()
        r.enablecredsspsupport = self.credssp.isTrue()
        r.multimedia = self.multimedia.isTrue()
        r.alsa = self.alsa.isTrue()
        r.smartcardString = self.smartcardString.value
        r.printerString = self.printerString.value
        r.linuxCustomParameters = self.customParameters.value
        r.enforcedShares = self.enforceDrives.value

        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: typing.MutableMapping[str, typing.Any] = {
            'tunHost': tunHost,
            'tunPort': tunPort,
            'tunWait': self.tunnelWait.num(),
            'tunChk': self.verifyCertificate.isTrue(),
            'ticket': ticket,
            'password': password,
            'this_server': request.build_absolute_uri('/'),
        }

        if osName == 'windows':
            if password != '':
                r.password = '******'
            sp.update({
                'as_file': r.as_file,
            })
        elif osName == 'linux':
            sp.update({
                'as_new_xfreerdp_params': r.as_new_xfreerdp_params,
            })
        else:  # Mac
            r.linuxCustomParameters = self.customParametersMAC.value
            sp.update({
                'as_new_xfreerdp_params':
                r.as_new_xfreerdp_params,
                'as_file':
                r.as_file if self.allowMacMSRDC.isTrue() else '',
                'as_rdp_url':
                r.as_rdp_url if self.allowMacMSRDC.isTrue() else '',
            })

        return self.getScript('scripts/{}/tunnel.py', osName, sp)
Пример #5
0
class BaseSpiceTransport(transports.Transport):
    """
    Provides access via SPICE 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 = 'spice.png'
    protocol = protocols.SPICE

    useEmptyCreds = gui.CheckBoxField(
        order=1,
        label=_('Empty credentials'),
        tooltip=_('If checked, the credentials used to connect will be emtpy'),
        tab=gui.CREDENTIALS_TAB)
    fixedName = gui.TextField(
        order=2,
        label=_('Username'),
        tooltip=_(
            'If not empty, this username will be always used as credential'),
        tab=gui.CREDENTIALS_TAB)
    fixedPassword = gui.PasswordField(
        order=3,
        label=_('Password'),
        tooltip=_(
            'If not empty, this password will be always used as credential'),
        tab=gui.CREDENTIALS_TAB)
    serverCertificate = gui.TextField(
        order=4,
        length=4096,
        multiline=4,
        label=_('Certificate'),
        tooltip=
        _('Server certificate (public), can be found on your ovirt engine, probably at /etc/pki/ovirt-engine/certs/ca.der (Use the contents of this file).'
          ),
        required=False)
    fullScreen = gui.CheckBoxField(
        order=5,
        label=_('Fullscreen Mode'),
        tooltip=_('If checked, viewer will be shown on fullscreen mode-'),
        tab=gui.ADVANCED_TAB)
    smartCardRedirect = gui.CheckBoxField(
        order=6,
        label=_('Smartcard Redirect'),
        tooltip=_(
            'If checked, SPICE protocol will allow smartcard redirection.'),
        defvalue=gui.FALSE,
        tab=gui.ADVANCED_TAB)
    usbShare = gui.CheckBoxField(
        order=7,
        label=_('Enable USB'),
        tooltip=_('If checked, USB redirection will be allowed.'),
        defvalue=gui.FALSE,
        tab=gui.ADVANCED_TAB)
    autoNewUsbShare = gui.CheckBoxField(
        order=8,
        label=_('New USB Auto Sharing'),
        tooltip=_('Auto-redirect USB devices when plugged in.'),
        defvalue=gui.FALSE,
        tab=gui.ADVANCED_TAB)

    def isAvailableFor(self, userService: 'models.UserService',
                       ip: str) -> bool:
        """
        Checks if the transport is available for the requested destination ip
        """
        ready = self.cache.get(ip)
        if ready is None:
            userServiceInstance: typing.Any = userService.getInstance(
            )  # Disable mypy checks on this
            con = userServiceInstance.getConsoleConnection()

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

            if con is None:
                return False

            port, secure_port = con['port'] or -1, con['secure_port'] or -1

            # test ANY of the ports
            port_to_test = port if port != -1 else secure_port
            if port_to_test == -1:
                self.cache.put(
                    'cachedMsg', 'Could not find the PORT for connection',
                    120)  # Write a message, that will be used from getCustom
                logger.info('SPICE didn\'t find has any port: %s', con)
                return False

            self.cache.put(
                'cachedMsg',
                'Could not reach server "{}" on port "{}" from broker (prob. causes are name resolution & firewall rules)'
                .format(con['address'], port_to_test), 120)

            if self.testServer(userService, con['address'],
                               port_to_test) is True:
                self.cache.put(ip, 'Y', READY_CACHE_TIMEOUT)
                ready = 'Y'

        return ready == 'Y'

    def getCustomAvailableErrorMsg(self, userService: 'models.UserService',
                                   ip: str) -> str:
        msg = self.cache.get('cachedMsg')
        if msg is None:
            return transports.Transport.getCustomAvailableErrorMsg(
                self, userService, ip)
        return msg

    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

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

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

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

        return {
            'protocol': self.protocol,
            'username': username,
            'password': 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 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
Пример #6
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")]
Пример #7
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 = [XenLinkedService]
    # : 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 = _('Xenserver Platform Provider')
    # : Type used internally to identify this provider
    typeType = 'XenPlatform'
    # : Description shown at administration interface for this provider
    typeDescription = _('XenServer 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=_('XenServer Server IP or Hostname'), required=True)
    username = gui.TextField(length=32, label=_('Username'), order=2, tooltip=_('User with valid privileges on XenServer'), required=True, defvalue='root')
    password = gui.PasswordField(lenth=32, label=_('Password'), order=3, tooltip=_('Password of the user of XenServer'), 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)

    macsRange = gui.TextField(length=36, label=_('Macs range'), defvalue='02:46:00:00:00:00-02:46:00:FF:FF:FF', order=90, rdonly=True,
                              tooltip=_('Range of valid macs for created machines'), required=True, tab=gui.ADVANCED_TAB)
    verifySSL = gui.CheckBoxField(label=_('Verify Certificate'), order=91,
                                  tooltip=_('If selected, certificate will be checked against system valid certificate providers'), required=True, tab=gui.ADVANCED_TAB)

    # XenServer 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, force=False):
        '''
        Returns the connection API object for XenServer (using XenServersdk)
        '''
        logger.debug('API verifySSL: {} {}'.format(self.verifySSL.value, self.verifySSL.isTrue()))
        if self._api is None or force:
            self._api = XenServer(self.host.value, '443', self.username.value, self.password.value, True, self.verifySSL.isTrue())
        return self._api

    # There is more fields type, but not here the best place to cover it
    def initialize(self, values=None):
        '''
        We will use the "autosave" feature for form fields
        '''

        # Just reset _api connection variable
        self._api = None

    def testConnection(self):
        '''
        Test that conection to XenServer server is fine

        Returns

            True if all went fine, false if id didn't
        '''
        self.__getApi().test()

    def checkTaskFinished(self, task):
        '''
        Checks a task state.
        Returns None if task is Finished
        Returns a number indicating % of completion if running
        Raises an exception with status else ('cancelled', 'unknown', 'failure')
        '''
        if task is None or task == '':
            return (True, '')
        ts = self.__getApi().getTaskInfo(task)
        logger.debug('Task status: {0}'.format(ts))
        if ts['status'] == 'running':
            return (False, ts['progress'])
        if ts['status'] == 'success':
            return (True, ts['result'])

        # Any other state, raises an exception
        raise Exception(six.text_type(ts['result']))  # Should be error message

    def getMachines(self, force=False):
        '''
        Obtains the list of machines inside XenServer.
        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'
        '''

        for m in self.__getApi().getVMs():
            if m['name'][:3] == 'UDS':
                continue
            yield m

    def getStorages(self, force=False):
        '''
        Obtains the list of storages inside XenServer.

        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'
                'size'
                'used'
        '''
        return self.__getApi().getSRs()

    def getStorageInfo(self, storageId, force=False):
        '''
        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().getSRInfo(storageId)

    def getNetworks(self, force=False):
        return self.__getApi().getNetworks()

    def cloneForTemplate(self, name, comments, machineId, sr):
        task = self.__getApi().cloneVM(machineId, name, sr)
        logger.debug('Task for cloneForTemplate: {0}'.format(task))
        return task

    def convertToTemplate(self, machineId, shadowMultiplier=4):
        '''
        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 XenServer admin interface only)

        Returns
            Raises an exception if operation could not be acomplished, or returns the id of the template being created.
        '''
        self.__getApi().convertToTemplate(machineId, shadowMultiplier)

    def getMachineState(self, machineId):
        '''
        Returns the state of the machine
        This method do not uses cache at all (it always tries to get machine state from XenServer 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)

        return State.INACTIVE

    def removeTemplate(self, templateId):
        '''
        Removes a template from XenServer server

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

    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
            clusterId: Id of the cluster to deploy to
            displayType: 'vnc' or 'spice'. Display to use ad XenServer 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().cloneTemplate(templateId, name)

    def getVMPowerState(self, machineId):
        '''
        Returns current machine power state
        '''
        return self.__getApi().getVMPowerState(machineId)

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

        This start also "resume" suspended/paused machines

        Args:
            machineId: Id of the machine

        Returns:
        '''
        return self.__getApi().startVM(machineId, async)
Пример #8
0
class TRDPTransport(BaseRDPTransport):
    '''
    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 = _('RDP')
    typeType = 'TSRDPTransport'
    typeDescription = _('RDP Protocol. Tunneled connection.')
    needsJava = True  # If this transport needs java for rendering
    protocol = transports.protocols.RDP
    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)
    # tunnelCheckServer = gui.TextField(label=_('Tunnel host check'), order=2, tooltip=_('If not empty, this server will be used to check if service is running before assigning it to user. (use HOST:PORT format)'), tab=gui.TUNNEL_TAB)

    tunnelWait = gui.NumericField(
        length=3,
        label=_('Tunnel wait time'),
        defvalue='10',
        minValue=1,
        maxValue=65536,
        order=2,
        tooltip=_('Maximum time to wait before closing the tunnel listener'),
        required=True,
        tab=gui.TUNNEL_TAB)

    useEmptyCreds = BaseRDPTransport.useEmptyCreds
    fixedName = BaseRDPTransport.fixedName
    fixedPassword = BaseRDPTransport.fixedPassword
    withoutDomain = BaseRDPTransport.withoutDomain
    fixedDomain = BaseRDPTransport.fixedDomain
    allowSmartcards = BaseRDPTransport.allowSmartcards
    allowPrinters = BaseRDPTransport.allowPrinters
    allowDrives = BaseRDPTransport.allowDrives
    enforceDrives = BaseRDPTransport.enforceDrives
    allowSerials = BaseRDPTransport.allowSerials
    allowClipboard = BaseRDPTransport.allowClipboard
    allowAudio = BaseRDPTransport.allowAudio

    wallpaper = BaseRDPTransport.wallpaper
    multimon = BaseRDPTransport.multimon
    aero = BaseRDPTransport.aero
    smooth = BaseRDPTransport.smooth
    showConnectionBar = BaseRDPTransport.showConnectionBar
    credssp = BaseRDPTransport.credssp

    screenSize = BaseRDPTransport.screenSize
    colorDepth = BaseRDPTransport.colorDepth

    alsa = BaseRDPTransport.alsa
    multimedia = BaseRDPTransport.multimedia
    redirectHome = BaseRDPTransport.redirectHome
    printerString = BaseRDPTransport.printerString
    smartcardString = BaseRDPTransport.smartcardString
    customParameters = BaseRDPTransport.customParameters

    def initialize(self, values: 'Module.ValuesType'):
        if values:
            if values['tunnelServer'].count(':') != 1:
                raise transports.Transport.ValidationException(
                    _('Must use HOST:PORT in Tunnel Server Field'))

    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]]:
        # We use helper to keep this clean
        # prefs = user.prefs('rdp')

        ci = self.getConnectionInfo(userService, user, password)
        username, password, domain = ci['username'], ci['password'], ci[
            'domain']

        # width, height = CommonPrefs.getWidthHeight(prefs)
        # depth = CommonPrefs.getDepth(prefs)
        width, height = self.screenSize.value.split('x')
        depth = self.colorDepth.value

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

        sshHost, sshPort = self.tunnelServer.value.split(':')

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

        r = RDPFile(width == '-1' or height == '-1',
                    width,
                    height,
                    depth,
                    target=os['OS'])
        r.enablecredsspsupport = ci.get(
            'sso') == 'True' or self.credssp.isTrue()
        r.address = '{address}'
        r.username = username
        r.password = password
        r.domain = domain
        r.redirectPrinters = self.allowPrinters.isTrue()
        r.redirectSmartcards = self.allowSmartcards.isTrue()
        r.redirectDrives = self.allowDrives.value
        r.redirectHome = self.redirectHome.isTrue()
        r.redirectSerials = self.allowSerials.isTrue()
        r.enableClipboard = self.allowClipboard.isTrue()
        r.redirectAudio = self.allowAudio.isTrue()
        r.showWallpaper = self.wallpaper.isTrue()
        r.multimon = self.multimon.isTrue()
        r.desktopComposition = self.aero.isTrue()
        r.smoothFonts = self.smooth.isTrue()
        r.enablecredsspsupport = self.credssp.isTrue()
        r.multimedia = self.multimedia.isTrue()
        r.alsa = self.alsa.isTrue()
        r.smartcardString = self.smartcardString.value
        r.printerString = self.printerString.value
        r.linuxCustomParameters = self.customParameters.value
        r.enforcedShares = self.enforceDrives.value

        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 = {
            'tunUser': tunuser,
            'tunPass': tunpass,
            'tunHost': sshHost,
            'tunPort': sshPort,
            'tunWait': self.tunnelWait.num(),
            'ip': ip,
            'password': password,
            'this_server': request.build_absolute_uri('/'),
        }

        if osName == 'windows':
            if password != '':
                r.password = '******'
            sp.update({
                'as_file': r.as_file,
            })
        elif osName == 'linux':
            sp.update({
                'as_new_xfreerdp_params': r.as_new_xfreerdp_params,
                'as_rdesktop_params': r.as_rdesktop_params,
            })
        else:  # Mac
            sp.update({
                'as_file': r.as_file,
                'as_cord_url': r.as_cord_url,
                'as_new_xfreerdp_params': r.as_new_xfreerdp_params,
            })
            if domain != '':
                sp['usernameWithDomain'] = '{}\\\\{}'.format(domain, username)
            else:
                sp['usernameWithDomain'] = username

        return self.getScript('scripts/{}/tunnel.py', osName, sp)
Пример #9
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
Пример #10
0
class SimpleLDAPAuthenticator(auths.Authenticator):

    host = gui.TextField(length=64,
                         label=_('Host'),
                         order=1,
                         tooltip=_('Ldap Server IP or Hostname'),
                         required=True)
    port = gui.NumericField(
        length=5,
        label=_('Port'),
        defvalue='389',
        order=2,
        tooltip=_('Ldap port (usually 389 for non ssl and 636 for ssl)'),
        required=True)
    ssl = gui.CheckBoxField(
        label=_('Use SSL'),
        order=3,
        tooltip=
        _('If checked, the connection will be ssl, using port 636 instead of 389'
          ))
    username = gui.TextField(
        length=64,
        label=_('Ldap User'),
        order=4,
        tooltip=_('Username with read privileges on the base selected'),
        required=True,
        tab=gui.CREDENTIALS_TAB)
    password = gui.PasswordField(lenth=32,
                                 label=_('Password'),
                                 order=5,
                                 tooltip=_('Password of the ldap user'),
                                 required=True,
                                 tab=gui.CREDENTIALS_TAB)
    timeout = gui.NumericField(
        length=3,
        label=_('Timeout'),
        defvalue='10',
        order=6,
        tooltip=_('Timeout in seconds of connection to LDAP'),
        required=True,
        minValue=1)
    ldapBase = gui.TextField(
        length=64,
        label=_('Base'),
        order=7,
        tooltip=_('Common search base (used for "users" and "groups")'),
        required=True,
        tab=_('Ldap info'))
    userClass = gui.TextField(
        length=64,
        label=_('User class'),
        defvalue='posixAccount',
        order=8,
        tooltip=_('Class for LDAP users (normally posixAccount)'),
        required=True,
        tab=_('Ldap info'))
    userIdAttr = gui.TextField(
        length=64,
        label=_('User Id Attr'),
        defvalue='uid',
        order=9,
        tooltip=_('Attribute that contains the user id'),
        required=True,
        tab=_('Ldap info'))
    userNameAttr = gui.TextField(
        length=64,
        label=_('User Name Attr'),
        defvalue='uid',
        order=10,
        tooltip=
        _('Attributes that contains the user name (list of comma separated values)'
          ),
        required=True,
        tab=_('Ldap info'))
    groupClass = gui.TextField(
        length=64,
        label=_('Group class'),
        defvalue='posixGroup',
        order=11,
        tooltip=_('Class for LDAP groups (normally poxisGroup)'),
        required=True,
        tab=_('Ldap info'))
    groupIdAttr = gui.TextField(
        length=64,
        label=_('Group Id Attr'),
        defvalue='cn',
        order=12,
        tooltip=_('Attribute that contains the group id'),
        required=True,
        tab=_('Ldap info'))
    memberAttr = gui.TextField(
        length=64,
        label=_('Group membership attr'),
        defvalue='memberUid',
        order=13,
        tooltip=_(
            'Attribute of the group that contains the users belonging to it'),
        required=True,
        tab=_('Ldap info'))

    typeName = _('SimpleLDAP Authenticator')
    typeType = 'SimpleLdapAuthenticator'
    typeDescription = _('Simple LDAP authenticator')
    iconFile = 'auth.png'

    # If it has and external source where to get "new" users (groups must be declared inside UDS)
    isExternalSource = True
    # If we need to enter the password for this user
    needsPassword = False
    # Label for username field
    userNameLabel = _('Username')
    # Label for group field
    groupNameLabel = _("Group")
    # Label for password field
    passwordLabel = _("Password")

    _connection: typing.Any = None
    _host: str = ''
    _port: str = ''
    _ssl: bool = False
    _username: str = ''
    _password: str = ''
    _timeout: str = ''
    _ldapBase: str = ''
    _userClass: str = ''
    _groupClass: str = ''
    _userIdAttr: str = ''
    _groupIdAttr: str = ''
    _memberAttr: str = ''
    _userNameAttr: str = ''

    def initialize(
            self, values: typing.Optional[typing.Dict[str,
                                                      typing.Any]]) -> None:
        if values:
            self._host = values['host']
            self._port = values['port']
            self._ssl = gui.strToBool(values['ssl'])
            self._username = values['username']
            self._password = values['password']
            self._timeout = values['timeout']
            self._ldapBase = values['ldapBase']
            self._userClass = values['userClass']
            self._groupClass = values['groupClass']
            self._userIdAttr = values['userIdAttr']
            self._groupIdAttr = values['groupIdAttr']
            self._memberAttr = values['memberAttr']
            self._userNameAttr = values['userNameAttr'].replace(
                ' ', '')  # Removes white spaces

    def valuesDict(self) -> gui.ValuesDictType:
        return {
            'host': self._host,
            'port': self._port,
            'ssl': gui.boolToStr(self._ssl),
            'username': self._username,
            'password': self._password,
            'timeout': self._timeout,
            'ldapBase': self._ldapBase,
            'userClass': self._userClass,
            'groupClass': self._groupClass,
            'userIdAttr': self._userIdAttr,
            'groupIdAttr': self._groupIdAttr,
            'memberAttr': self._memberAttr,
            'userNameAttr': self._userNameAttr
        }

    def marshal(self) -> bytes:
        return '\t'.join([
            'v1', self._host, self._port,
            gui.boolToStr(self._ssl), self._username, self._password,
            self._timeout, self._ldapBase, self._userClass, self._groupClass,
            self._userIdAttr, self._groupIdAttr, self._memberAttr,
            self._userNameAttr
        ]).encode('utf8')

    def unmarshal(self, data: bytes):
        vals = data.decode('utf8').split('\t')
        if vals[0] == 'v1':
            logger.debug("Data: %s", vals[1:])
            (self._host, self._port, ssl, self._username, self._password,
             self._timeout, self._ldapBase, self._userClass, self._groupClass,
             self._userIdAttr, self._groupIdAttr, self._memberAttr,
             self._userNameAttr) = vals[1:]
            self._ssl = gui.strToBool(ssl)

    def __connection(self,
                     username: typing.Optional[str] = None,
                     password: typing.Optional[str] = None):
        """
        Tries to connect to ldap. If username is None, it tries to connect using user provided credentials.
        @return: Connection established
        @raise exception: If connection could not be established
        """
        if self._connection is None:  # We want this method also to check credentials
            self._connection = ldaputil.connection(self._username,
                                                   self._password,
                                                   self._host,
                                                   port=int(self._port),
                                                   ssl=self._ssl,
                                                   timeout=int(self._timeout),
                                                   debug=False)

        return self._connection

    def __connectAs(self, username: str, password: str) -> typing.Any:
        return ldaputil.connection(username,
                                   password,
                                   self._host,
                                   ssl=self._ssl,
                                   timeout=int(self._timeout),
                                   debug=False)

    def __getUser(self,
                  username: str) -> typing.Optional[ldaputil.LDAPResultType]:
        """
        Searchs for the username and returns its LDAP entry
        @param username: username to search, using user provided parameters at configuration to map search entries.
        @return: None if username is not found, an dictionary of LDAP entry attributes if found.
        @note: Active directory users contains the groups it belongs to in "memberOf" attribute
        """
        return ldaputil.getFirst(
            con=self.__connection(),
            base=self._ldapBase,
            objectClass=self._userClass,
            field=self._userIdAttr,
            value=username,
            attributes=[
                i for i in self._userNameAttr.split(',') + [self._userIdAttr]
            ],
            sizeLimit=LDAP_RESULT_LIMIT)

    def __getGroup(self,
                   groupName: str) -> typing.Optional[ldaputil.LDAPResultType]:
        """
        Searchs for the groupName and returns its LDAP entry
        @param groupName: group name to search, using user provided parameters at configuration to map search entries.
        @return: None if group name is not found, an dictionary of LDAP entry attributes if found.
        """
        return ldaputil.getFirst(con=self.__connection(),
                                 base=self._ldapBase,
                                 objectClass=self._groupClass,
                                 field=self._groupIdAttr,
                                 value=groupName,
                                 attributes=[self._memberAttr],
                                 sizeLimit=LDAP_RESULT_LIMIT)

    def __getGroups(self, user: ldaputil.LDAPResultType):
        try:
            groups: typing.List[str] = []

            filter_ = '(&(objectClass=%s)(|(%s=%s)(%s=%s)))' % (
                self._groupClass, self._memberAttr, user['_id'],
                self._memberAttr, user['dn'])
            for d in ldaputil.getAsDict(con=self.__connection(),
                                        base=self._ldapBase,
                                        ldapFilter=filter_,
                                        attrList=[self._groupIdAttr],
                                        sizeLimit=10 * LDAP_RESULT_LIMIT):
                if self._groupIdAttr in d:
                    for k in d[self._groupIdAttr]:
                        groups.append(k)

            logger.debug('Groups: %s', groups)
            return groups

        except Exception:
            logger.exception('Exception at __getGroups')
            return []

    def __getUserRealName(self, usr: ldaputil.LDAPResultType) -> str:
        '''
        Tries to extract the real name for this user. Will return all atttributes (joint)
        specified in _userNameAttr (comma separated).
        '''
        return ' '.join([
            ' '.join((str(k) for k in usr.get(id_, ''))) if isinstance(
                usr.get(id_), list) else str(usr.get(id_, ''))
            for id_ in self._userNameAttr.split(',')
        ]).strip()

    def authenticate(self, username: str, credentials: str,
                     groupsManager: 'auths.GroupsManager') -> bool:
        '''
        Must authenticate the user.
        We can have to different situations here:
           1.- The authenticator is external source, what means that users may be unknown to system before callig this
           2.- The authenticator isn't external source, what means that users have been manually added to system and are known before this call
        We receive the username, the credentials used (normally password, but can be a public key or something related to pk) and a group manager.
        The group manager is responsible for letting know the authenticator which groups we currently has active.
        @see: uds.core.auths.groups_manager
        '''
        try:
            # Locate the user at LDAP
            user = self.__getUser(username)

            if user is None:
                return False

            # Let's see first if it credentials are fine
            self.__connectAs(
                user['dn'],
                credentials)  # Will raise an exception if it can't connect

            groupsManager.validate(self.__getGroups(user))

            return True

        except Exception:
            return False

    def createUser(self, usrData: typing.Dict[str, str]) -> None:
        '''
        Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to
        @param usrData: Contains data received from user directly, that is, a dictionary with at least: name, realName, comments, state & password
        @return:  Raises an exception (AuthException) it things didn't went fine
        '''
        res = self.__getUser(usrData['name'])
        if res is None:
            raise auths.AuthenticatorException(_('Username not found'))
        # Fills back realName field
        usrData['real_name'] = self.__getUserRealName(res)

    def getRealName(self, username: str) -> str:
        '''
        Tries to get the real name of an user
        '''
        res = self.__getUser(username)
        if res is None:
            return username
        return self.__getUserRealName(res)

    def modifyUser(self, usrData: typing.Dict[str, str]) -> None:
        '''
        We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..)
        Modify user has no reason on external sources, so it will never be used (probably)
        Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to
        @param usrData: Contains data received from user directly, that is, a dictionary with at least: name, realName, comments, state & password
        @return:  Raises an exception it things don't goes fine
        '''
        return self.createUser(usrData)

    def createGroup(self, groupData: typing.Dict[str, str]) -> None:
        '''
        We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..)
        External sources already has its own groups and, at most, it can check if it exists on external source before accepting it
        Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to
        @params groupData: a dict that has, at least, name, comments and active
        @return:  Raises an exception it things don't goes fine
        '''
        res = self.__getGroup(groupData['name'])
        if res is None:
            raise auths.AuthenticatorException(_('Group not found'))

    def getGroups(self, username: str, groupsManager: 'auths.GroupsManager'):
        '''
        Looks for the real groups to which the specified user belongs
        Updates groups manager with valid groups
        Remember to override it in derived authentication if needed (external auths will need this, for internal authenticators this is never used)
        '''
        user = self.__getUser(username)
        if user is None:
            raise auths.AuthenticatorException(_('Username not found'))
        groupsManager.validate(self.__getGroups(user))

    def searchUsers(self,
                    pattern: str) -> typing.Iterable[typing.Dict[str, str]]:
        try:
            res = []
            for r in ldaputil.getAsDict(
                    con=self.__connection(),
                    base=self._ldapBase,
                    ldapFilter='(&(objectClass=%s)(%s=%s*))' %
                (self._userClass, self._userIdAttr, pattern),
                    attrList=[self._userIdAttr, self._userNameAttr],
                    sizeLimit=LDAP_RESULT_LIMIT):
                res.append({
                    'id': r[self._userIdAttr][0],  # Ignore @...
                    'name': self.__getUserRealName(r)
                })

            return res
        except Exception:
            logger.exception("Exception: ")
            raise auths.AuthenticatorException(
                _('Too many results, be more specific'))

    def searchGroups(self,
                     pattern: str) -> typing.Iterable[typing.Dict[str, str]]:
        try:
            res = []
            for r in ldaputil.getAsDict(
                    con=self.__connection(),
                    base=self._ldapBase,
                    ldapFilter='(&(objectClass=%s)(%s=%s*))' %
                (self._groupClass, self._groupIdAttr, pattern),
                    attrList=[self._groupIdAttr, 'memberOf', 'description'],
                    sizeLimit=LDAP_RESULT_LIMIT):
                res.append({
                    'id': r[self._groupIdAttr][0],
                    'name': r['description'][0]
                })

            return res
        except Exception:
            logger.exception("Exception: ")
            raise auths.AuthenticatorException(
                _('Too many results, be more specific'))

    @staticmethod
    def test(env, data):
        try:
            auth = SimpleLDAPAuthenticator(None, env, data)
            return auth.testConnection()
        except Exception as e:
            logger.error("Exception found testing Simple LDAP auth: %s", e)
            return [False, "Error testing connection"]

    def testConnection(self):  # pylint: disable=too-many-return-statements,too-many-branches
        try:
            con = self.__connection()
        except Exception as e:
            return [False, str(e)]

        try:
            con.search_s(base=self._ldapBase, scope=ldap.SCOPE_BASE)
        except Exception:
            return [False, _('Ldap search base is incorrect')]

        try:
            if len(
                    con.search_ext_s(base=self._ldapBase,
                                     scope=ldap.SCOPE_SUBTREE,
                                     filterstr='(objectClass=%s)' %
                                     self._userClass,
                                     sizelimit=1)) == 1:
                raise Exception()
            return [
                False,
                _('Ldap user class seems to be incorrect (no user found by that class)'
                  )
            ]
        except Exception as e:
            # If found 1 or more, all right
            pass

        try:
            if len(
                    con.search_ext_s(base=self._ldapBase,
                                     scope=ldap.SCOPE_SUBTREE,
                                     filterstr='(objectClass=%s)' %
                                     self._groupClass,
                                     sizelimit=1)) == 1:
                raise Exception()
            return [
                False,
                _('Ldap group class seems to be incorrect (no group found by that class)'
                  )
            ]
        except Exception as e:
            # If found 1 or more, all right
            pass

        try:
            if len(
                    con.search_ext_s(base=self._ldapBase,
                                     scope=ldap.SCOPE_SUBTREE,
                                     filterstr='(%s=*)' % self._userIdAttr,
                                     sizelimit=1)) == 1:
                raise Exception()
            return [
                False,
                _('Ldap user id attribute seems to be incorrect (no user found by that attribute)'
                  )
            ]
        except Exception as e:
            # If found 1 or more, all right
            pass

        try:
            if len(
                    con.search_ext_s(base=self._ldapBase,
                                     scope=ldap.SCOPE_SUBTREE,
                                     filterstr='(%s=*)' % self._groupIdAttr,
                                     sizelimit=1)) == 1:
                raise Exception()
            return [
                False,
                _('Ldap group id attribute seems to be incorrect (no group found by that attribute)'
                  )
            ]
        except Exception as e:
            # If found 1 or more, all right
            pass

        # Now test objectclass and attribute of users
        try:
            if len(
                    con.search_ext_s(base=self._ldapBase,
                                     scope=ldap.SCOPE_SUBTREE,
                                     filterstr='(&(objectClass=%s)(%s=*))' %
                                     (self._userClass, self._userIdAttr),
                                     sizelimit=1)) == 1:
                raise Exception()
            return [
                False,
                _('Ldap user class or user id attr is probably wrong (can\'t find any user with both conditions)'
                  )
            ]
        except Exception as e:
            # If found 1 or more, all right
            pass

        # And group part, with membership
        try:
            res = con.search_ext_s(base=self._ldapBase,
                                   scope=ldap.SCOPE_SUBTREE,
                                   filterstr='(&(objectClass=%s)(%s=*))' %
                                   (self._groupClass, self._groupIdAttr),
                                   attrlist=[self._memberAttr.encode('utf-8')])
            if not res:
                raise Exception(
                    _('Ldap group class or group id attr is probably wrong (can\'t find any group with both conditions)'
                      ))
            ok = False
            for r in res:
                if self._memberAttr in r[1]:
                    ok = True
                    break
            if ok is False:
                raise Exception(
                    _('Can\'t locate any group with the membership attribute specified'
                      ))
        except Exception as e:
            return [False, str(e)]

        return [
            True,
            _("Connection params seem correct, test was succesfully executed")
        ]

    def __str__(self):
        return "Ldap Auth: {0}:{1}@{2}:{3}, base = {4}, userClass = {5}, groupClass = {6}, userIdAttr = {7}, groupIdAttr = {8}, memberAttr = {9}, userName attr = {10}".format(
            self._username, self._password, self._host, self._port,
            self._ldapBase, self._userClass, self._groupClass,
            self._userIdAttr, self._groupIdAttr, self._memberAttr,
            self._userNameAttr)
Пример #11
0
class TX2GOTransport(BaseX2GOTransport):
    """
    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
    """

    typeName = _('X2Go')
    typeType = 'TX2GOTransport'
    typeDescription = _('X2Go access (Experimental). Tunneled connection.')
    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,
    )

    tunnelWait = gui.NumericField(
        length=3,
        label=_('Tunnel wait time'),
        defvalue='30',
        minValue=5,
        maxValue=65536,
        order=2,
        tooltip=_('Maximum time to wait before closing the tunnel listener'),
        required=True,
        tab=gui.TUNNEL_TAB,
    )

    verifyCertificate = gui.CheckBoxField(
        label=_('Force SSL certificate verification'),
        order=23,
        tooltip=
        _('If enabled, the certificate of tunnel server will be verified (recommended).'
          ),
        defvalue=gui.TRUE,
        tab=gui.TUNNEL_TAB,
    )

    fixedName = BaseX2GOTransport.fixedName
    screenSize = BaseX2GOTransport.screenSize
    desktopType = BaseX2GOTransport.desktopType
    customCmd = BaseX2GOTransport.customCmd
    sound = BaseX2GOTransport.sound
    exports = BaseX2GOTransport.exports
    speed = BaseX2GOTransport.speed

    soundType = BaseX2GOTransport.soundType
    keyboardLayout = BaseX2GOTransport.keyboardLayout
    pack = BaseX2GOTransport.pack
    quality = BaseX2GOTransport.quality

    def initialize(self, values: 'Module.ValuesType'):
        if values:
            if values['tunnelServer'].count(':') != 1:
                raise BaseX2GOTransport.ValidationException(
                    _('Must use HOST:PORT in Tunnel Server Field'))

    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.Mapping[str, typing.Any]]:

        ci = self.getConnectionInfo(userService, user, password)
        username = ci['username']

        priv, pub = self.getAndPushKey(username, userService)

        width, height = self.getScreenSize()

        rootless = False
        desktop = self.desktopType.value
        if desktop == "UDSVAPP":
            desktop = "/usr/bin/udsvapp " + self.customCmd.value
            rootless = True

        xf = x2go_file.getTemplate(
            speed=self.speed.value,
            pack=self.pack.value,
            quality=self.quality.value,
            sound=self.sound.isTrue(),
            soundSystem=self.sound.value,
            windowManager=desktop,
            exports=self.exports.isTrue(),
            rootless=rootless,
            width=width,
            height=height,
            user=username,
        )

        ticket = TicketStore.create_for_tunnel(
            userService=userService,
            port=22,
            validity=self.tunnelWait.num() + 60,  # Ticket overtime
        )

        tunHost, tunPort = self.tunnelServer.value.split(':')

        # data
        data = {
            'os': os['OS'],
            'ip': ip,
            'port': 22,
            'key': priv,
            'width': width,
            'height': height,
            'printers': True,
            'drives': self.exports.isTrue(),
            'fullScreen': width == -1 or height == -1,
            'this_server': request.build_absolute_uri('/'),
            'xf': xf,
        }

        m = tools.DictAsObj(data)

        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 = {
            'tunHost': tunHost,
            'tunPort': tunPort,
            'tunWait': self.tunnelWait.num(),
            'tunChk': self.verifyCertificate.isTrue(),
            'ticket': ticket,
            'key': priv,
            'xf': xf,
        }

        return self.getScript('scripts/{}/tunnel.py', osName, sp)
Пример #12
0
class IPMachinesService(IPServiceBase):
    # Gui
    # Gui
    token = gui.TextField(
        order=1,
        label=_('Service Token'),
        length=16,
        tooltip=_('Service token that will be used by actors to communicate with service. Leave empty for persistent assignation.'),
        defvalue='',
        required=False,
        rdonly=False
    )

    ipList = gui.EditableList(label=_('List of servers'), tooltip=_('List of servers available for this service'))

    port = gui.NumericField(length=5, label=_('Check Port'), defvalue='0', order=2, tooltip=_('If non zero, only Machines responding to connection on that port will be used'), required=True, tab=gui.ADVANCED_TAB)

    # Description of service
    typeName = _('Static Multiple IP')
    typeType = 'IPMachinesService'
    typeDescription = _('This service provides access to POWERED-ON Machines by IP')
    iconFile = 'machines.png'

    # Characteristics of service
    maxDeployed = -1  # If the service provides more than 1 "provided service" (-1 = no limit, 0 = ???? (do not use it!!!), N = max number to deploy
    usesCache = False  # Cache are running machine awaiting to be assigned
    usesCache_L2 = False  # L2 Cache are running machines in suspended state
    needsManager = False  # If the service needs a s.o. manager (managers are related to agents provided by services itselfs, i.e. virtual machines with agent)
    mustAssignManually = False  # If true, the system can't do an automatic assignation of a deployed user service from this service

    deployedType = IPMachineDeployed

    servicesTypeProvided = (serviceTypes.VDI,)

    _ips: typing.List[str] = []
    _token: str = ''
    _port: int = 0

    def initialize(self, values: 'Module.ValuesType') -> None:
        if values is None:
            return

        if values.get('ipList', None) is None:
            self._ips = []
        else:
            self._ips = ['{}~{}'.format(str(ip).strip(), i) for i, ip in enumerate(values['ipList']) if str(ip).strip()]  # Allow duplicates right now
            # self._ips.sort()

        self._token = self.token.value.strip()
        self._port = self.port.value

    def getToken(self):
        return self._token or None

    def valuesDict(self) -> gui.ValuesDictType:
        ips = (i.split('~')[0] for i in self._ips)

        return {'ipList': gui.convertToList(ips), 'token': self._token, 'port': str(self._port)}

    def marshal(self) -> bytes:
        self.storage.saveData('ips', pickle.dumps(self._ips))
        return b'\0'.join([b'v3', self._token.encode(), str(self._port).encode()])

    def unmarshal(self, data: bytes) -> None:
        values: typing.List[bytes] = data.split(b'\0')
        d = self.storage.readData('ips')
        if isinstance(d, bytes):
            self._ips = pickle.loads(d)
        elif isinstance(d, str):  # "legacy" saved elements
            self._ips = pickle.loads(d.encode('utf8'))
            self.marshal()  # Ensure now is bytes..
        else:
            self._ips = []
        if values[0] != b'v1':
            self._token = values[1].decode()
            if values[0] == b'v3':
                self._port = int(values[2].decode())

    def getUnassignedMachine(self) -> typing.Optional[str]:
        # Search first unassigned machine
        try:
            for ip in self._ips:
                theIP = ip.split('~')[0]
                if self.storage.readData(theIP) is None:
                    self.storage.saveData(theIP, theIP)
                    # Now, check if it is available on port, if required...
                    if self._port > 0:
                        if connection.testServer(theIP, self._port, timeOut=0.5) is False:
                            # Log into logs of provider, so it can be "shown" on services logs
                            self.parent().doLog(log.WARN, 'Host {} not accesible on port {}'.format(theIP, self._port))
                            self.storage.remove(theIP)  # Return Machine to pool
                            continue
                    return theIP
            return None
        except Exception:
            logger.exception("Exception at getUnassignedMachine")
            return None

    def unassignMachine(self, ip: str) -> None:
        try:
            self.storage.remove(ip)
        except Exception:
            logger.exception("Exception at getUnassignedMachine")

    def listAssignables(self):
        return [(ip, ip.split('~')[0]) for ip in self._ips if self.storage.readData(ip) is None]

    def assignFromAssignables(self, assignableId: str, user: '******', userDeployment: 'services.UserDeployment') -> str:
        userServiceInstance: IPMachineDeployed = typing.cast(IPMachineDeployed, userDeployment)
        theIP = assignableId.split('~')[0]
        if self.storage.readData(theIP) is None:
            self.storage.saveData(theIP, theIP)
            return userServiceInstance.assign(theIP)
        return userServiceInstance.error('IP already assigned')
Пример #13
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 = _('OpenNebula Platform Provider')
    # : Type used internally to identify this provider
    typeType = 'openNebulaPlatform'
    # : Description shown at administration interface for this provider
    typeDescription = _('OpenNebula platform service provider')
    # : Icon file used as icon for this provider. This string will be translated
    # : BEFORE sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    iconFile = 'provider.png'

    # now comes the form fields
    # There is always two fields that are requested to the admin, that are:
    # Service Name, that is a name that the admin uses to name this provider
    # Description, that is a short description that the admin gives to this provider
    # Now we are going to add a few fields that we need to use this provider
    # Remember that these are "dummy" fields, that in fact are not required
    # but used for sample purposes
    # If we don't indicate an order, the output order of fields will be
    # "random"
    host = gui.TextField(length=64,
                         label=_('Host'),
                         order=1,
                         tooltip=_('OpenNebula Host'),
                         required=True)
    port = gui.NumericField(
        length=5,
        label=_('Port'),
        defvalue='2633',
        order=2,
        tooltip=_('OpenNebula Port (default is 2633 for non ssl connection)'),
        required=True)
    ssl = gui.CheckBoxField(
        label=_('Use SSL'),
        order=3,
        tooltip=
        _('If checked, the connection will be forced to be ssl (will not work if server is not providing ssl)'
          ))
    username = gui.TextField(
        length=32,
        label=_('Username'),
        order=4,
        tooltip=_('User with valid privileges on OpenNebula'),
        required=True,
        defvalue='oneadmin')
    password = gui.PasswordField(
        lenth=32,
        label=_('Password'),
        order=5,
        tooltip=_('Password of the user of OpenNebula'),
        required=True)

    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 OpenNebula'),
        required=True,
        tab=gui.ADVANCED_TAB)

    # Own variables
    _api = None

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

        # Just reset _api connection variable
        self._api = None

        if values is not None:
            self.timeout.value = validators.validateTimeout(
                self.timeout.value, returnAsInteger=False)
            logger.debug('Endpoint: {}'.format(self.endpoint))

    @property
    def endpoint(self):
        return 'http{}://{}:{}/RPC2'.format('s' if self.ssl.isTrue() else '',
                                            self.host.value, self.port.value)

    @property
    def api(self):
        if self._api is None:
            self._api = on.OpenNebulaClient(self.username.value,
                                            self.password.value, self.endpoint)

        logger.debug('Api: {}'.format(self._api))
        return self._api

    def resetApi(self):
        self._api = None

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

    def testConnection(self):
        '''
        Test that conection to OpenNebula server is fine

        Returns

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

        try:
            if self.api.version[0] < '4':
                return [
                    False,
                    'OpenNebula version is not supported (required version 4.1 or newer)'
                ]
        except Exception as e:
            return [False, '{}'.format(e)]

        return [True, _('Opennebula test connection passed')]

    def getDatastores(self, datastoreType=0):
        return on.storage.enumerateDatastores(self.api, datastoreType)

    def getTemplates(self, force=False):
        return on.template.getTemplates(self.api, force)

    def makeTemplate(self, fromTemplateId, name, toDataStore):
        return on.template.create(self.api, fromTemplateId, name, toDataStore)

    def checkTemplatePublished(self, templateId):
        return on.template.checkPublished(self.api, templateId)

    def removeTemplate(self, templateId):
        return on.template.remove(self.api, templateId)

    def deployFromTemplate(self, name, templateId):
        return on.template.deployFrom(self.api, templateId, name)

    def getMachineState(self, machineId):
        '''
        Returns the state of the machine
        This method do not uses cache at all (it always tries to get machine state from OpenNebula server)

        Args:
            machineId: Id of the machine to get state

        Returns:
            one of the on.VmState Values
        '''
        return on.vm.getMachineState(self.api, machineId)

    def getMachineSubState(self, machineId):
        '''
        Returns the  LCM_STATE of a machine (must be ready)
        '''
        return on.vm.getMachineSubstate(self.api, 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:
        '''
        on.vm.startMachine(self.api, 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:
        '''
        on.vm.stopMachine(self.api, 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:
        '''
        on.vm.suspendMachine(self.api, 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:
        '''
        on.vm.removeMachine(self.api, machineId)

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

    def getConsoleConnection(self, machineId):
        display = on.vm.getDisplayConnection(self.api, machineId)

        return {
            'type': display['type'],
            'address': display['host'],
            'port': display['port'],
            'secure_port': -1,
            'monitors': 1,
            'cert_subject': '',
            'ticket': {
                'value': display['passwd'],
                'expiry': ''
            }
        }

    @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()
Пример #14
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
Пример #15
0
class ProxmoxProvider(services.ServiceProvider):  # pylint: disable=too-many-public-methods
    offers = [ProxmoxLinkedService]
    typeName = _('Proxmox Platform Provider')
    typeType = 'ProxmoxPlatform'
    typeDescription = _('Proxmox platform service provider')
    iconFile = 'provider.png'

    host = gui.TextField(length=64,
                         label=_('Host'),
                         order=1,
                         tooltip=_('Proxmox Server IP or Hostname'),
                         required=True)
    port = gui.NumericField(lengh=5,
                            label=_('Port'),
                            order=2,
                            tooltip=_('Proxmox API port (default is 8006)'),
                            required=True,
                            defvalue='8006')

    username = gui.TextField(
        length=32,
        label=_('Username'),
        order=3,
        tooltip=
        _('User with valid privileges on Proxmox, (use "user@authenticator" form)'
          ),
        required=True,
        defvalue='root@pam')
    password = gui.PasswordField(lenth=32,
                                 label=_('Password'),
                                 order=4,
                                 tooltip=_('Password of the user of Proxmox'),
                                 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='20',
        order=90,
        tooltip=_('Timeout in seconds of connection to Proxmox'),
        required=True,
        tab=gui.ADVANCED_TAB)

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

    def __getApi(self) -> client.ProxmoxClient:
        """
        Returns the connection API object
        """
        if self._api is None:
            self._api = client.ProxmoxClient(self.host.value, self.port.num(),
                                             self.username.value,
                                             self.password.value,
                                             self.timeout.num(), False,
                                             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.timeout.value = validators.validateTimeout(self.timeout.value)
            logger.debug(self.host.value)

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

        Returns

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

        return self.__getApi().test()

    def listMachines(self) -> typing.List[client.types.VMInfo]:
        return self.__getApi().listVms()

    def getMachineInfo(self, vmId: int) -> client.types.VMInfo:
        return self.__getApi().getVmInfo(vmId, force=True)

    def getMachineConfiguration(self,
                                vmId: int) -> client.types.VMConfiguration:
        return self.__getApi().getVmConfiguration(vmId, force=True)

    def getStorageInfo(self, storageId: str,
                       node: str) -> client.types.StorageInfo:
        return self.__getApi().getStorage(storageId, node)

    def listStorages(
            self, node: typing.Optional[str]
    ) -> typing.List[client.types.StorageInfo]:
        return self.__getApi().listStorages(node=node, content='images')

    def listPools(self) -> typing.List[client.types.PoolInfo]:
        return self.__getApi().listPools()

    def makeTemplate(self, vmId: int) -> None:
        return self.__getApi().convertToTemplate(vmId)

    def cloneMachine(self,
                     vmId: int,
                     name: str,
                     description: typing.Optional[str],
                     linkedClone: bool,
                     toNode: typing.Optional[str] = None,
                     toStorage: typing.Optional[str] = None,
                     toPool: typing.Optional[str] = None,
                     memory: int = 0) -> client.types.VmCreationResult:
        return self.__getApi().cloneVm(vmId, name, description, linkedClone,
                                       toNode, toStorage, toPool, memory)

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

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

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

    def removeMachine(self, vmId: int) -> client.types.UPID:
        return self.__getApi().deleteVm(vmId)

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

    def enableHA(self,
                 vmId: int,
                 started: bool = False,
                 group: typing.Optional[str] = None) -> None:
        self.__getApi().enableVmHA(vmId, started, group)

    def disableHA(self, vmId: int) -> None:
        self.__getApi().disableVmHA(vmId)

    def listHaGroups(self) -> typing.List[str]:
        return self.__getApi().listHAGroups()

    def getConsoleConnection(
        self, machineId: str
    ) -> typing.Optional[typing.MutableMapping[str, typing.Any]]:
        # TODO: maybe proxmox also supports "spice"? for future release...
        return None

    @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..')]
        prox = ProxmoxProvider(env, data)
        if prox.testConnection() is True:
            return [True, 'Test successfully passed']

        return [False, _("Connection failed. Check connection params")]
Пример #16
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
Пример #17
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)
Пример #18
0
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
Пример #19
0
class WinDomainOsManager(WindowsOsManager):
    typeName = _('Windows Domain OS Manager')
    typeType = 'WinDomainManager'
    typeDescription = _('Os Manager to control windows machines with domain.')
    iconFile = 'wosmanager.png'

    # Apart form data from windows os manager, we need also domain and credentials
    domain = gui.TextField(length=64, label=_('Domain'), order=1, tooltip=_('Domain to join machines to (use FQDN form, Netbios name not supported for most operations)'), required=True)
    account = gui.TextField(length=64, label=_('Account'), order=2, tooltip=_('Account with rights to add machines to domain'), required=True)
    password = gui.PasswordField(length=64, label=_('Password'), order=3, tooltip=_('Password of the account'), required=True)
    ou = gui.TextField(length=128, label=_('OU'), order=4, tooltip=_('Organizational unit where to add machines in domain (check it before using it). i.e.: ou=My Machines,dc=mydomain,dc=local'))
    grp = gui.TextField(length=64, label=_('Machine Group'), order=7, tooltip=_('Group to which add machines on creation. If empty, no group will be used. (experimental)'), tab=_('Advanced'))
    removeOnExit = gui.CheckBoxField(label=_('Machine clean'), order=8, tooltip=_('If checked, UDS will try to remove the machine from the domain USING the provided credentials'), tab=_('Advanced'), defvalue=gui.TRUE)
    serverHint = gui.TextField(length=64, label=_('Server Hint'), order=9, tooltip=_('In case of several AD servers, which one is preferred'), tab=_('Advanced'))
    ssl = gui.CheckBoxField(label=_('Use SSL'), order=10, tooltip=_('If checked,  a ssl connection to Active Directory will be used'), tab=_('Advanced'), defvalue=gui.TRUE)

    # Inherits base "onLogout"
    onLogout = WindowsOsManager.onLogout
    idle = WindowsOsManager.idle

    _domain: str
    _ou: str
    _account: str
    _pasword: str
    _group: str
    _serverHint: str
    _removeOnExit: str
    _ssl: str

    def __init__(self, environment: 'Environment', values: 'Module.ValuesType'):
        super().__init__(environment, values)
        if values:
            if values['domain'] == '':
                raise osmanagers.OSManager.ValidationException(_('Must provide a domain!'))
            # if values['domain'].find('.') == -1:
            #    raise osmanagers.OSManager.ValidationException(_('Must provide domain in FQDN'))
            if values['account'] == '':
                raise osmanagers.OSManager.ValidationException(_('Must provide an account to add machines to domain!'))
            if values['account'].find('\\') != -1:
                raise osmanagers.OSManager.ValidationException(_('DOM\\USER form is not allowed!'))
            if values['password'] == '':
                raise osmanagers.OSManager.ValidationException(_('Must provide a password for the account!'))
            self._domain = values['domain']
            self._ou = values['ou'].strip()
            self._account = values['account']
            self._password = values['password']
            self._group = values['grp'].strip()
            self._serverHint = values['serverHint'].strip()
            self._ssl = 'y' if values['ssl'] else 'n'
            self._removeOnExit = 'y' if values['removeOnExit'] else 'n'
        else:
            self._domain = ""
            self._ou = ""
            self._account = ""
            self._password = ""
            self._group = ""
            self._serverHint = ""
            self._removeOnExit = 'n'
            self._ssl = 'n'

        # self._ou = self._ou.replace(' ', ''), do not remove spaces
        if self._domain != '' and self._ou != '':
            lpath = 'dc=' + ',dc='.join((s.lower() for s in self._domain.split('.')))
            if self._ou.lower().find(lpath) == -1:
                self._ou += ',' + lpath

    def __getServerList(self) -> typing.Iterable[typing.Tuple[str, int]]:
        if self._serverHint != '':
            yield (self._serverHint, 389)

        for server in reversed(sorted(dns.resolver.query('_ldap._tcp.' + self._domain, 'SRV'), key=lambda i: i.priority * 10000 + i.weight)):
            yield (str(server.target)[:-1], server.port)

    def __connectLdap(self, servers: typing.Optional[typing.Iterable[typing.Tuple[str, int]]] = None) -> typing.Any:
        """
        Tries to connect to LDAP
        Raises an exception if not found:
            dns.resolver.NXDOMAIN
            ldaputil.LDAPError
        """
        if servers is None:
            servers = self.__getServerList()

        account = self._account
        if account.find('@') == -1:
            account += '@' + self._domain

        _str = "No servers found"
        # And if not possible, try using NON-SSL
        for server in servers:
            ssl = self._ssl == 'y'
            port = server[1] if not ssl else -1
            try:
                return ldaputil.connection(account, self._password, server[0], port, ssl=ssl, timeout=10, debug=False)
            except Exception as e:
                _str = 'Error: {}'.format(e)

        raise ldaputil.LDAPError(_str)

    def __getGroup(self, ldapConnection: typing.Any) -> typing.Optional[str]:
        base = ','.join(['DC=' + i for i in self._domain.split('.')])
        group = ldaputil.escape(self._group)
        obj: typing.Optional[typing.MutableMapping[str, typing.Any]]
        try:
            obj = next(ldaputil.getAsDict(ldapConnection, base, "(&(objectClass=group)(|(cn={0})(sAMAccountName={0})))".format(group), ['dn'], sizeLimit=50))
        except StopIteration:
            obj = None

        if obj is None:
            return None

        return obj['dn']  # Returns the DN

    def __getMachine(self, ldapConnection, machineName: str) -> typing.Optional[str]:
        # if self._ou:
        #     base = self._ou
        # else:
        base = ','.join(['DC=' + i for i in self._domain.split('.')])

        fltr = '(&(objectClass=computer)(sAMAccountName={}$))'.format(ldaputil.escape(machineName))
        obj: typing.Optional[typing.MutableMapping[str, typing.Any]]
        try:
            obj = next(ldaputil.getAsDict(ldapConnection, base, fltr, ['dn'], sizeLimit=50))
        except StopIteration:
            obj = None

        if obj is None:
            return None

        return obj['dn']  # Returns the DN

    def readyNotified(self, userService: 'UserService') -> None:
        # No group to add
        if self._group == '':
            return

        if '.' not in self._domain:
            logger.info('Adding to a group for a non FQDN domain is not supported')
            return

        # The machine is on a AD for sure, and maybe they are not already sync
        error: typing.Optional[str] = None
        for s in self.__getServerList():
            try:
                ldapConnection = self.__connectLdap(servers=(s,))

                machine = self.__getMachine(ldapConnection, userService.friendly_name)
                group = self.__getGroup(ldapConnection)
                # #
                # Direct LDAP operation "modify", maybe this need to be added to ldaputil? :)
                # #
                ldapConnection.modify_s(group, ((ldap.MOD_ADD, 'member', machine),))  # @UndefinedVariable
                error = None
                break
            except dns.resolver.NXDOMAIN:  # No domain found, log it and pass
                logger.warning('Could not find _ldap._tcp.%s', self._domain)
                log.doLog(userService, log.WARN, "Could not remove machine from domain (_ldap._tcp.{0} not found)".format(self._domain), log.OSMANAGER)
            except ldap.ALREADY_EXISTS:  # @UndefinedVariable
                # Already added this machine to this group, pass
                error = None
                break
            except ldaputil.LDAPError:
                logger.exception('Ldap Exception caught')
                error = "Could not add machine (invalid credentials? for {0})".format(self._account)
            except Exception as e:
                error = "Could not add machine {} to group {}: {}".format(userService.friendly_name, self._group, e)
                # logger.exception('Ldap Exception caught')

        if error:
            log.doLog(userService, log.WARN, error, log.OSMANAGER)
            logger.error(error)

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

        # If no removal requested, just return
        if self._removeOnExit != 'y':
            return

        if '.' not in self._domain:
            # logger.info('Releasing from a not FQDN domain is not supported')
            log.doLog(userService, log.INFO, "Removing a domain machine form a non FQDN domain is not supported.", log.OSMANAGER)
            return

        try:
            ldapConnection = self.__connectLdap()
        except dns.resolver.NXDOMAIN:  # No domain found, log it and pass
            logger.warning('Could not find _ldap._tcp.%s', self._domain)
            log.doLog(userService, log.WARN, "Could not remove machine from domain (_ldap._tcp.{} not found)".format(self._domain), log.OSMANAGER)
            return
        except ldaputil.LDAPError as e:
            # logger.exception('Ldap Exception caught')
            log.doLog(userService, log.WARN, "Could not remove machine from domain ({})".format(e), log.OSMANAGER)
            return
        except Exception as e:
            # logger.exception('Exception caught')
            log.doLog(userService, log.WARN, "Could not remove machine from domain ({})".format(e), log.OSMANAGER)
            return

        try:
            res = self.__getMachine(ldapConnection, userService.friendly_name)
            if res is None:
                raise Exception('Machine {} not found on AD (permissions?)'.format(userService.friendly_name))
            ldaputil.recursive_delete(ldapConnection, res)
        except IndexError:
            logger.error('Error deleting %s from BASE %s', userService.friendly_name, self._ou)
        except Exception:
            logger.exception('Deleting from AD: ')

    def check(self) -> str:
        try:
            ldapConnection = self.__connectLdap()
        except ldaputil.LDAPError as e:
            return _('Check error: {}').format(e)
        except dns.resolver.NXDOMAIN:
            return _('Could not find server parameters (_ldap._tcp.{0} can\'t be resolved)').format(self._domain)
        except Exception as e:
            logger.exception('Exception ')
            return str(e)

        try:
            ldapConnection.search_st(self._ou, ldap.SCOPE_BASE)  # @UndefinedVariable
        except ldaputil.LDAPError as e:
            return _('Check error: {}').format(e)

        # Group
        if self._group != '':
            if self.__getGroup(ldapConnection) is None:
                return _('Check Error: group "{}" not found (using "cn" to locate it)').format(self._group)

        return _('Server check was successful')

    # pylint: disable=protected-access
    @staticmethod
    def test(env: 'Environment', data: typing.Dict[str, str]) -> typing.List[typing.Any]:
        logger.debug('Test invoked')
        try:
            wd: WinDomainOsManager = WinDomainOsManager(env, data)
            logger.debug(wd)
            try:
                ldapConnection = wd.__connectLdap()
            except ldaputil.LDAPError as e:
                return [False, _('Could not access AD using LDAP ({0})').format(e)]

            ou = wd._ou
            if ou == '':
                ou = 'cn=Computers,dc=' + ',dc='.join(wd._domain.split('.'))

            logger.info('Checking %s with ou %s', wd._domain, ou)
            r = ldapConnection.search_st(ou, ldap.SCOPE_BASE)  # @UndefinedVariable
            logger.info('Result of search: %s', r)

        except ldaputil.LDAPError:
            if not wd._ou:
                return [False, _('The default path {0} for computers was not found!!!').format(wd._ou)]
            return [False, _('The ou path {0} was not found!!!').format(wd._ou)]
        except dns.resolver.NXDOMAIN:
            return [True, _('Could not check parameters (_ldap._tcp.{0} can\'r be resolved)').format(wd._domain)]
        except Exception as e:
            logger.exception('Exception ')
            return [False, str(e)]

        return [True, _("All parameters seem to work fine.")]

    def actorData(self, userService: 'UserService') -> typing.MutableMapping[str, typing.Any]:
        return {
            'action': 'rename_ad',
            'name': userService.getName(),
            'ad': self._domain,
            'ou': self._ou,
            'username': self._account,
            'password': self._password,
        }

    def infoVal(self, userService: 'UserService') -> str:
        return 'domain:{0}\t{1}\t{2}\t{3}\t{4}'.format(self.getName(userService), self._domain, self._ou, self._account, self._password)

    def infoValue(self, userService: 'UserService') -> str:
        return 'domain\r{0}\t{1}\t{2}\t{3}\t{4}'.format(self.getName(userService), self._domain, self._ou, self._account, self._password)

    def marshal(self) -> bytes:
        '''
        Serializes the os manager data so we can store it in database
        '''
        base = typing.cast(str, encoders.encode(super().marshal(), 'hex', asText=True))
        return '\t'.join([
            'v4',
            self._domain, self._ou, self._account,
            cryptoManager().encrypt(self._password),
            base,
            self._group, self._serverHint, self._ssl, self._removeOnExit
        ]).encode('utf8')

    def unmarshal(self, data: bytes) -> None:
        values = data.decode('utf8').split('\t')
        if values[0] in ('v1', 'v2', 'v3', 'v4'):
            self._domain = values[1]
            self._ou = values[2]
            self._account = values[3]
            self._password = cryptoManager().decrypt(values[4])

        if values[0] in ('v2', 'v3', 'v4'):
            self._group = values[6]
        else:
            self._group = ''

        if values[0] in ('v3', 'v4'):
            self._serverHint = values[7]
        else:
            self._serverHint = ''

        if values[0] == 'v4':
            self._ssl = values[8]
            self._removeOnExit = values[9]
        else:
            self._ssl = 'n'
            self._removeOnExit = 'y'

        super().unmarshal(typing.cast(bytes, encoders.decode(values[5], 'hex')))

    def valuesDict(self) -> gui.ValuesDictType:
        dct = super().valuesDict()
        dct['domain'] = self._domain
        dct['ou'] = self._ou
        dct['account'] = self._account
        dct['password'] = self._password
        dct['grp'] = self._group
        dct['serverHint'] = self._serverHint
        dct['ssl'] = self._ssl == 'y'
        dct['removeOnExit'] = self._removeOnExit == 'y'
        return dct
Пример #20
0
class IPMachinesService(IPServiceBase):
    # Gui
    token = gui.TextField(
        order=1,
        label=_('Service Token'),
        length=16,
        tooltip=
        _('Service token that will be used by actors to communicate with service. Leave empty for persistent assignation.'
          ),
        defvalue='',
        required=False,
        rdonly=False,
    )

    ipList = gui.EditableList(
        label=_('List of servers'),
        tooltip=_('List of servers available for this service'),
    )

    port = gui.NumericField(
        length=5,
        label=_('Check Port'),
        defvalue='0',
        order=2,
        tooltip=
        _('If non zero, only hosts responding to connection on that port will be served.'
          ),
        required=True,
        tab=gui.ADVANCED_TAB,
    )
    skipTimeOnFailure = gui.NumericField(
        length=6,
        label=_('Skip time'),
        defvalue='0',
        order=2,
        tooltip=_(
            'If a host fails to check, skip it for this time (in minutes).'),
        minValue=0,
        required=True,
        tab=gui.ADVANCED_TAB,
    )

    # Description of service
    typeName = _('Static Multiple IP')
    typeType = 'IPMachinesService'
    typeDescription = _(
        'This service provides access to POWERED-ON Machines by IP')
    iconFile = 'machines.png'

    # Characteristics of service
    maxDeployed = (
        -1
    )  # If the service provides more than 1 "provided service" (-1 = no limit, 0 = ???? (do not use it!!!), N = max number to deploy
    usesCache = False  # Cache are running machine awaiting to be assigned
    usesCache_L2 = False  # L2 Cache are running machines in suspended state
    needsManager = False  # If the service needs a s.o. manager (managers are related to agents provided by services itselfs, i.e. virtual machines with agent)
    mustAssignManually = False  # If true, the system can't do an automatic assignation of a deployed user service from this service

    deployedType = IPMachineDeployed

    servicesTypeProvided = (serviceTypes.VDI, )

    _ips: typing.List[str] = []
    _token: str = ''
    _port: int = 0
    _skipTimeOnFailure: int = 0

    def initialize(self, values: 'Module.ValuesType') -> None:
        if values is None:
            return

        if values.get('ipList', None) is None:
            self._ips = []
        else:
            # Check that ips are valid
            for v in values['ipList']:
                if not net.isValidHost(v):
                    raise IPServiceBase.ValidationException(
                        gettext('Invalid value detected on servers list: "{}"'
                                ).format(v))
            self._ips = [
                '{}~{}'.format(str(ip).strip(), i)
                for i, ip in enumerate(values['ipList']) if str(ip).strip()
            ]  # Allow duplicates right now
            # Current stored data, if it exists
            d = self.storage.readData('ips')
            old_ips = pickle.loads(d) if d and isinstance(d, bytes) else []
            # dissapeared ones
            dissapeared = set(i.split('~')[0] for i in old_ips) - set(
                i.split('~')[0] for i in self._ips)
            with transaction.atomic():
                for removable in dissapeared:
                    self.storage.remove(removable)

        self._token = self.token.value.strip()
        self._port = self.port.value
        self._skipTimeOnFailure = self.skipTimeOnFailure.num()

    def getToken(self):
        return self._token or None

    def valuesDict(self) -> gui.ValuesDictType:
        ips = (i.split('~')[0] for i in self._ips)

        return {
            'ipList': gui.convertToList(ips),
            'token': self._token,
            'port': str(self._port),
            'skipTimeOnFailure': str(self._skipTimeOnFailure),
        }

    def marshal(self) -> bytes:
        self.storage.saveData('ips', pickle.dumps(self._ips))
        return b'\0'.join([
            b'v4',
            self._token.encode(),
            str(self._port).encode(),
            str(self._skipTimeOnFailure).encode(),
        ])

    def unmarshal(self, data: bytes) -> None:
        values: typing.List[bytes] = data.split(b'\0')
        d = self.storage.readData('ips')
        if isinstance(d, bytes):
            self._ips = pickle.loads(d)
        elif isinstance(d, str):  # "legacy" saved elements
            self._ips = pickle.loads(d.encode('utf8'))
            self.marshal()  # Ensure now is bytes..
        else:
            self._ips = []
        if values[0] != b'v1':
            self._token = values[1].decode()
            if values[0] in (b'v3', b'v4'):
                self._port = int(values[2].decode())
            if values[0] == b'v4':
                self._skipTimeOnFailure = int(values[3].decode())

        # Sets maximum services for this
        self.maxDeployed = len(self._ips)

    def getUnassignedMachine(self) -> typing.Optional[str]:
        # Search first unassigned machine
        try:
            now = getSqlDatetimeAsUnix()
            consideredFreeTime = now - config.GlobalConfig.SESSION_EXPIRE_TIME.getInt(
                force=False) * 3600
            for ip in self._ips:
                theIP = IPServiceBase.getIp(ip)
                theMAC = IPServiceBase.getMac(ip)
                locked = self.storage.getPickle(theIP)
                if not locked or locked < consideredFreeTime:
                    if self._port > 0 and self._skipTimeOnFailure > 0 and self.cache.get(
                            'port{}'.format(theIP)):
                        continue  # The check failed not so long ago, skip it...
                    self.storage.putPickle(theIP, now)
                    # Is WOL enabled?
                    wolENABLED = bool(self.parent().wolURL(theIP, theMAC))
                    # Now, check if it is available on port, if required...
                    if self._port > 0 and not wolENABLED:  # If configured WOL, check is a nonsense
                        if (connection.testServer(
                                theIP, self._port, timeOut=0.5) is False):
                            # Log into logs of provider, so it can be "shown" on services logs
                            self.parent().doLog(
                                log.WARN,
                                'Host {} not accesible on port {}'.format(
                                    theIP, self._port),
                            )
                            logger.warning(
                                'Static Machine check on %s:%s failed. Will be ignored for %s minutes.',
                                theIP,
                                self._port,
                                self._skipTimeOnFailure,
                            )
                            self.storage.remove(
                                theIP)  # Return Machine to pool
                            if self._skipTimeOnFailure > 0:
                                self.cache.put(
                                    'port{}'.format(theIP),
                                    '1',
                                    validity=self._skipTimeOnFailure * 60,
                                )
                            continue
                    if theMAC:
                        return theIP + ';' + theMAC
                    return theIP
            return None
        except Exception:
            logger.exception("Exception at getUnassignedMachine")
            return None

    def unassignMachine(self, ip: str) -> None:
        try:
            if ';' in ip:
                ip = ip.split(';')[0]  # ; means that HAS an attached MAC
            self.storage.remove(ip)
        except Exception:
            logger.exception("Exception at getUnassignedMachine")

    def listAssignables(self):
        return [(ip, ip.split('~')[0]) for ip in self._ips
                if self.storage.readData(ip) is None]

    def assignFromAssignables(
        self,
        assignableId: str,
        user: '******',
        userDeployment: 'services.UserDeployment',
    ) -> str:
        userServiceInstance: IPMachineDeployed = typing.cast(
            IPMachineDeployed, userDeployment)
        theIP = IPServiceBase.getIp(assignableId)
        if self.storage.readData(theIP) is None:
            self.storage.saveData(theIP, theIP)
            return userServiceInstance.assign(theIP)
        return userServiceInstance.error('IP already assigned')
Пример #21
0
class OGProvider(ServiceProvider):
    '''
    This class represents the sample services provider

    In this class we provide:
       * The Provider functionality
       * The basic configuration parameters for the provider
       * The form fields needed by administrators to configure this provider

       :note: At class level, the translation must be simply marked as so
       using ugettext_noop. This is so cause we will translate the string when
       sent to the administration client.

    For this class to get visible at administration client as a provider type,
    we MUST register it at package __init__.

    '''
    # : What kind of services we offer, this are classes inherited from Service
    offers = [OGService]
    # : Name to show the administrator. This string will be translated BEFORE
    # : sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    typeName = _('OpenGnsys Platform Provider')
    # : Type used internally to identify this provider
    typeType = 'openGnsysPlatform'
    # : Description shown at administration interface for this provider
    typeDescription = _('OpenGnsys platform service provider (experimental)')
    # : Icon file used as icon for this provider. This string will be translated
    # : BEFORE sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    iconFile = 'provider.png'

    # now comes the form fields
    # There is always two fields that are requested to the admin, that are:
    # Service Name, that is a name that the admin uses to name this provider
    # Description, that is a short description that the admin gives to this provider
    # Now we are going to add a few fields that we need to use this provider
    # Remember that these are "dummy" fields, that in fact are not required
    # but used for sample purposes
    # If we don't indicate an order, the output order of fields will be
    # "random"
    host = gui.TextField(length=64, label=_('Host'), order=1, tooltip=_('OpenGnsys Host'), required=True)
    port = gui.NumericField(length=5, label=_('Port'), defvalue='443', order=2, tooltip=_('OpenGnsys Port (default is 443, and only ssl connection is allowed)'), required=True)
    checkCert = gui.CheckBoxField(label=_('Check Cert.'), order=3, tooltip=_('If checked, ssl certificate of OpenGnsys server must be valid (not self signed)'))
    username = gui.TextField(length=32, label=_('Username'), order=4, tooltip=_('User with valid privileges on OpenGnsys'), required=True)
    password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the user of OpenGnsys'), required=True)
    udsServerAccessUrl = gui.TextField(length=32, label=_('UDS Server URL'), order=6, tooltip=_('URL used by OpenGnsys to access UDS. If empty, UDS will guess it.'), required=False, tab=gui.PARAMETERS_TAB)

    maxPreparingServices = gui.NumericField(length=3, label=_('Creation concurrency'), defvalue='10', minValue=1, maxValue=65536, order=50, tooltip=_('Maximum number of concurrently creating VMs'), required=True, tab=gui.ADVANCED_TAB)
    maxRemovingServices = gui.NumericField(length=3, label=_('Removal concurrency'), defvalue='5', minValue=1, maxValue=65536, order=51, tooltip=_('Maximum number of concurrently removing VMs'), required=True, tab=gui.ADVANCED_TAB)

    timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=90, tooltip=_('Timeout in seconds of connection to OpenGnsys'), required=True, tab=gui.ADVANCED_TAB)

    # Own variables
    _api = None

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

        # Just reset _api connection variable
        self._api = None

        if values is not None:
            self.timeout.value = validators.validateTimeout(self.timeout.value, returnAsInteger=False)
            logger.debug('Endpoint: {}'.format(self.endpoint))

            try:
                request = values['_request']

                if self.udsServerAccessUrl.value.strip() == '':
                    self.udsServerAccessUrl.value = request.build_absolute_uri('/')

                if self.udsServerAccessUrl.value[-1] != '/':
                    self.udsServerAccessUrl.value += '/'
            except Exception:
                pass

    @property
    def endpoint(self):
        return 'https://{}:{}/opengnsys/rest'.format(self.host.value, self.port.value)

    @property
    def api(self):
        if self._api is None:
            self._api = og.OpenGnsysClient(self.username.value, self.password.value, self.endpoint, self.cache, self.checkCert.isTrue())

        logger.debug('Api: {}'.format(self._api))
        return self._api

    def resetApi(self):
        self._api = None

    def testConnection(self):
        '''
        Test that conection to OpenGnsys server is fine

        Returns

            True if all went fine, false if id didn't
        '''
        try:
            if self.api.version[0:5] < '1.1.0':
                return [False, 'OpenGnsys version is not supported (required version 1.1.0 or newer and found {})'.format(self.api.version)]
        except Exception as e:
            logger.exception('Error')
            return [False, '{}'.format(e)]

        return [True, _('OpenGnsys test connection passed')]

    @staticmethod
    def test(env, data):
        '''
        Test ovirt Connectivity

        Args:
            env: environment passed for testing (temporal environment passed)

            data: data passed for testing (data obtained from the form
            definition)

        Returns:
            Array of two elements, first is True of False, depending on test
            (True is all right, false is error),
            second is an String with error, preferably i18n..

        '''
        return OGProvider(env, data).testConnection()

    def getUDSServerAccessUrl(self):
        return self.udsServerAccessUrl.value

    def reserve(self, ou, image, lab=0, maxtime=0):
        return self.api.reserve(ou, image, lab, maxtime)

    def unreserve(self, machineId):
        return self.api.unreserve(machineId)

    def notifyEvents(self, machineId, loginURL, logoutURL):
        return self.api.notifyURLs(machineId, loginURL, logoutURL)

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

    def status(self, machineId):
        return self.api.status(machineId)
Пример #22
0
class TSPICETransport(BaseSpiceTransport):
    """
    Provides access via SPICE 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 = _('SPICE')
    typeType = 'TSSPICETransport'
    typeDescription = _('SPICE Protocol. Tunneled connection.')
    protocol = transports.protocols.SPICE
    group: typing.ClassVar[str] = 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)

    serverCertificate = BaseSpiceTransport.serverCertificate
    fullScreen = BaseSpiceTransport.fullScreen
    usbShare = BaseSpiceTransport.usbShare
    autoNewUsbShare = BaseSpiceTransport.autoNewUsbShare
    smartCardRedirect = BaseSpiceTransport.smartCardRedirect

    def initialize(self, values: 'Module.ValuesType'):
        if values:
            if values['tunnelServer'].count(':') != 1:
                raise transports.Transport.ValidationException(
                    _('Must use HOST:PORT in Tunnel Server Field'))

    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]]:
        userServiceInstance: typing.Any = userService.getInstance()

        # Spice connection
        con = userServiceInstance.getConsoleConnection()
        port: str = con['port'] or '-1'
        secure_port: str = con['secure_port'] or '-1'

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

        sshHost, sshPort = self.tunnelServer.value.split(':')

        r = RemoteViewerFile('127.0.0.1',
                             '{port}',
                             '{secure_port}',
                             con['ticket']['value'],
                             self.serverCertificate.value.strip(),
                             con['cert_subject'],
                             fullscreen=self.fullScreen.isTrue())
        r.usb_auto_share = self.usbShare.isTrue()
        r.new_usb_auto_share = self.autoNewUsbShare.isTrue()
        r.smartcard = self.smartCardRedirect.isTrue()

        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)

        # if sso:  # If SSO requested, and when supported by platform
        #     userServiceInstance.desktopLogin(user, password, '')

        sp = {
            'as_file': r.as_file,
            'as_file_ns': r.as_file_ns,
            'tunUser': tunuser,
            'tunPass': tunpass,
            'tunHost': sshHost,
            'tunPort': sshPort,
            'ip': con['address'],
            'port': port,
            'secure_port': secure_port
        }

        return self.getScript('scripts/{}/tunnel.py', osName, sp)
Пример #23
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 = [ServiceOne, ServiceTwo]
    # : 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 Provider')
    # : Type used internally to identify this provider
    typeType = 'SampleProvider'
    # : Description shown at administration interface for this provider
    typeDescription = _('Sample (and dummy) 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"

    # : Remote host. Here core will translate label and tooltip, remember to
    # : mark them as _ using ugettext_noop.
    remoteHost = gui.TextField(
        oder=1,
        length=64,
        label=_('Remote host'),
        tooltip=_('This fields contains a remote host'),
        required=True,
    )
    # : Name of your pet (sample, not really needed :-) )
    petName = gui.TextField(
        order=2,
        length=32,
        label=_('Your pet\'s name'),
        tooltip=_('If you like, write the name of your pet'),
        requred=False,
        defvalue='Tux'  # : This will not get translated
    )
    # : Age of Methuselah (matusalén in spanish)
    # : in Spain there is a well-known to say that something is very old,
    # : "Tiene mas años que matusalén"(is older than Methuselah)
    methAge = gui.NumericField(
        order=3,
        length=4,  # That is, max allowed value is 9999
        label=_('Age of Methuselah'),
        tooltip=_('If you know it, please, tell me!!!'),
        required=
        True,  # : Numeric fields have always a value, so this not really needed
        defvalue='4500')

    # : Is Methuselah istill alive?
    methAlive = gui.CheckBoxField(
        order=4,
        label=_('Is Methuselah still alive?'),
        tooltip=_('If you fail, this will not get saved :-)'),
        required=True,  # : Also means nothing. Check boxes has always a value
        defvalue=gui.TRUE  # : By default, at new item, check this
    )

    methText = gui.TextField(
        order=5,
        length=512,
        multiline=5,
        label=_('Text area'),
        tooltip=_('This is a text area'),
        requred=False,
        defvalue='Write\nsomething'  # : This will not get translated
    )

    # There is more fields type, but not here the best place to cover it
    def initialize(self, values=None):
        """
        We will use the "autosave" feature for form fields, that is more than
        enought for most providers. (We simply need to store data provided by user
        and, maybe, initialize some kind of connection with this values).

        Normally provider values are rally used at sevice level, cause we never
        instantiate nothing except a service from a provider.
        """

        # If you say meth is alive, you are wrong!!! (i guess..)
        # values are only passed from administration client. Internals
        # instantiations are always empty.
        if values is not None and self.methAlive.isTrue():
            raise ServiceProvider.ValidationException(
                _('Methuselah is not alive!!! :-)'))

    # Marshal and unmarshal are defaults ones, also enought

    # As we use "autosave" fields feature, dictValues is also provided by
    # base class so we don't have to mess with all those things...

    @staticmethod
    def test(env, data):
        """
        Create your test method here so the admin can push the "check" button
        and this gets executed.
        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..

        In this case, wi well do nothing more that use the provider params

        Note also that this is an static method, that will be invoked using
        the admin user provided data via administration client, and a temporary
        environment that will be erased after invoking this method
        """
        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..')]

    # Congratulations!!!, the needed part of your first simple provider is done!
    # Now you can go to administration panel, and check it
    #
    # From now onwards, we implement our own methods, that will be used by,
    # for example, services derived from this provider
    def host(self):
        """
        Sample method, in fact in this we just return
        the value of host field, that is an string
        """
        return self.remoteHost.value

    def methYears(self):
        """
        Another sample return, it will in fact return the Methuselah years
        """
        return self.methAge.value()
Пример #24
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()
Пример #25
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
    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 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)
Пример #26
0
class XenProvider(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 = [XenLinkedService]
    # : 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 = _('Xenserver/XCP-NG Platforms Provider')
    # : Type used internally to identify this provider
    typeType = 'XenPlatform'
    # : Description shown at administration interface for this provider
    typeDescription = _('XenServer and XCP-NG platforms 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=_('XenServer Server IP or Hostname'),
                         required=True)
    username = gui.TextField(
        length=32,
        label=_('Username'),
        order=2,
        tooltip=_('User with valid privileges on XenServer'),
        required=True,
        defvalue='root')
    password = gui.PasswordField(
        lenth=32,
        label=_('Password'),
        order=3,
        tooltip=_('Password of the user of XenServer'),
        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)

    macsRange = gui.TextField(
        length=36,
        label=_('Macs range'),
        defvalue='02:46:00:00:00:00-02:46:00:FF:FF:FF',
        order=90,
        rdonly=True,
        tooltip=_('Range of valid macs for created machines'),
        required=True,
        tab=gui.ADVANCED_TAB)
    verifySSL = gui.CheckBoxField(
        label=_('Verify Certificate'),
        order=91,
        tooltip=
        _('If selected, certificate will be checked against system valid certificate providers'
          ),
        required=True,
        tab=gui.ADVANCED_TAB,
        defvalue=gui.FALSE)

    _api: typing.Optional[XenServer]

    # XenServer 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, force: bool = False) -> XenServer:
        """
        Returns the connection API object for XenServer (using XenServersdk)
        """
        if not self._api or force:
            self._api = XenServer(self.host.value, 443, self.username.value,
                                  self.password.value, True,
                                  self.verifySSL.isTrue())

        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

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

        Returns

            True if all went fine, false if id didn't
        """
        self.__getApi().test()

    def checkTaskFinished(
            self, task: typing.Optional[str]) -> typing.Tuple[bool, str]:
        """
        Checks a task state.
        Returns None if task is Finished
        Returns a number indicating % of completion if running
        Raises an exception with status else ('cancelled', 'unknown', 'failure')
        """
        if not task:
            return True, ''

        ts = self.__getApi().getTaskInfo(task)
        logger.debug('Task status: %s', ts)
        if ts['status'] == 'running':
            return False, ts['progress']
        if ts['status'] == 'success':
            return True, ts['result']

        # Any other state, raises an exception
        raise Exception(str(ts['result']))  # Should be error message

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

        for m in self.__getApi().getVMs():
            if m['name'][:3] == 'UDS':
                continue
            yield m

    def getStorages(
        self,
        force: bool = False
    ) -> typing.Iterable[typing.MutableMapping[str, typing.Any]]:
        """
        Obtains the list of storages inside XenServer.

        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'
                'size'
                'used'
        """
        return self.__getApi().getSRs()

    def getStorageInfo(self,
                       storageId: str,
                       force=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().getSRInfo(storageId)

    def getNetworks(
        self,
        force: bool = False
    ) -> typing.Iterable[typing.MutableMapping[str, typing.Any]]:
        return self.__getApi().getNetworks()

    def cloneForTemplate(self, name: str, comments: str, machineId: str,
                         sr: str):
        task = self.__getApi().cloneVM(machineId, name, sr)
        logger.debug('Task for cloneForTemplate: %s', task)
        return task

    def convertToTemplate(self,
                          machineId: str,
                          shadowMultiplier: int = 4) -> None:
        """
        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 XenServer admin interface only)

        Returns
            Raises an exception if operation could not be acomplished, or returns the id of the template being created.
        """
        self.__getApi().convertToTemplate(machineId, shadowMultiplier)

    def removeTemplate(self, templateId: str) -> None:
        """
        Removes a template from XenServer server

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

    def startDeployFromTemplate(self, name: str, comments: 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
            clusterId: Id of the cluster to deploy to
            displayType: 'vnc' or 'spice'. Display to use ad XenServer 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().cloneTemplate(templateId, name)

    def getVMPowerState(self, machineId: str) -> str:
        """
        Returns current machine power state
        """
        return self.__getApi().getVMPowerState(machineId)

    def startVM(self,
                machineId: str,
                asnc: bool = True) -> typing.Optional[str]:
        """
        Tries to start a machine. No check is done, it is simply requested to XenServer.

        This start also "resume" suspended/paused machines

        Args:
            machineId: Id of the machine

        Returns:
        """
        return self.__getApi().startVM(machineId, asnc)

    def stopVM(self,
               machineId: str,
               asnc: bool = True) -> typing.Optional[str]:
        """
        Tries to start a machine. No check is done, it is simply requested to XenServer

        Args:
            machineId: Id of the machine

        Returns:
        """
        return self.__getApi().stopVM(machineId, asnc)

    def resetVM(self,
                machineId: str,
                asnc: bool = True) -> typing.Optional[str]:
        """
        Tries to start a machine. No check is done, it is simply requested to XenServer

        Args:
            machineId: Id of the machine

        Returns:
        """
        return self.__getApi().resetVM(machineId, asnc)

    def canSuspendVM(self, machineId: str) -> bool:
        """
        The machine can be suspended only when "suspend" is in their operations list (mush have xentools installed)

        Args:
            machineId: Id of the machine

        Returns:
            True if the machien can be suspended
        """
        return self.__getApi().canSuspendVM(machineId)

    def suspendVM(self,
                  machineId: str,
                  asnc: bool = True) -> typing.Optional[str]:
        """
        Tries to start a machine. No check is done, it is simply requested to XenServer

        Args:
            machineId: Id of the machine

        Returns:
        """
        return self.__getApi().suspendVM(machineId, asnc)

    def resumeVM(self,
                 machineId: str,
                 asnc: bool = True) -> typing.Optional[str]:
        """
        Tries to start a machine. No check is done, it is simply requested to XenServer

        Args:
            machineId: Id of the machine

        Returns:
        """
        return self.__getApi().resumeVM(machineId, asnc)

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

        Args:
            machineId: Id of the machine

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

    def configureVM(self, machineId: str, netId: str, mac: str,
                    memory: int) -> None:
        self.__getApi().configureVM(machineId,
                                    mac={
                                        'network': netId,
                                        'mac': mac
                                    },
                                    memory=memory)

    def provisionVM(self, machineId: str, asnc: bool = True) -> str:
        return self.__getApi().provisionVM(machineId, asnc=asnc)

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

    @staticmethod
    def test(env: 'Environment',
             data: 'Module.ValuesType') -> typing.List[typing.Any]:
        """
        Test XenServer 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..')]
        xe = XenProvider(env, data)
        try:
            xe.testConnection()
            return [True, _('Connection test successful')]
        except Exception as e:
            return [False, _("Connection failed: {}").format(str(e))]
Пример #27
0
class OpenNebulaProvider(ServiceProvider):  # pylint: disable=too-many-public-methods
    # : What kind of services we offer, this are classes inherited from Service
    offers = [LiveService]
    # : Name to show the administrator. This string will be translated BEFORE
    # : sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    typeName = _('OpenNebula Platform Provider')
    # : Type used internally to identify this provider
    typeType = 'openNebulaPlatform'
    # : Description shown at administration interface for this provider
    typeDescription = _('OpenNebula platform service provider')
    # : Icon file used as icon for this provider. This string will be translated
    # : BEFORE sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    iconFile = 'provider.png'

    # now comes the form fields
    # There is always two fields that are requested to the admin, that are:
    # Service Name, that is a name that the admin uses to name this provider
    # Description, that is a short description that the admin gives to this provider
    # Now we are going to add a few fields that we need to use this provider
    # Remember that these are "dummy" fields, that in fact are not required
    # but used for sample purposes
    # If we don't indicate an order, the output order of fields will be
    # "random"
    host = gui.TextField(length=64,
                         label=_('Host'),
                         order=1,
                         tooltip=_('OpenNebula Host'),
                         required=True)
    port = gui.NumericField(
        length=5,
        label=_('Port'),
        defvalue='2633',
        order=2,
        tooltip=_('OpenNebula Port (default is 2633 for non ssl connection)'),
        required=True)
    ssl = gui.CheckBoxField(
        label=_('Use SSL'),
        order=3,
        tooltip=
        _('If checked, the connection will be forced to be ssl (will not work if server is not providing ssl)'
          ))
    username = gui.TextField(
        length=32,
        label=_('Username'),
        order=4,
        tooltip=_('User with valid privileges on OpenNebula'),
        required=True,
        defvalue='oneadmin')
    password = gui.PasswordField(
        lenth=32,
        label=_('Password'),
        order=5,
        tooltip=_('Password of the user of OpenNebula'),
        required=True)

    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 OpenNebula'),
        required=True,
        tab=gui.ADVANCED_TAB)

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

    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:
            self.timeout.value = validators.validateTimeout(self.timeout.value)
            logger.debug('Endpoint: %s', self.endpoint)

    @property
    def endpoint(self) -> str:
        return 'http{}://{}:{}/RPC2'.format('s' if self.ssl.isTrue() else '',
                                            self.host.value, self.port.value)

    @property
    def api(self) -> on.client.OpenNebulaClient:
        if self._api is None:
            self._api = on.client.OpenNebulaClient(self.username.value,
                                                   self.password.value,
                                                   self.endpoint)

        return self._api

    def resetApi(self) -> None:
        self._api = None

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

    def testConnection(self) -> typing.List[typing.Any]:
        '''
        Test that conection to OpenNebula server is fine

        Returns

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

        try:
            if self.api.version[0] < '4':
                return [
                    False,
                    'OpenNebula version is not supported (required version 4.1 or newer)'
                ]
        except Exception as e:
            return [False, '{}'.format(e)]

        return [True, _('Opennebula test connection passed')]

    def getDatastores(
            self,
            datastoreType: int = 0) -> typing.Iterable[on.types.StorageType]:
        yield from on.storage.enumerateDatastores(self.api, datastoreType)

    def getTemplates(
            self,
            force: bool = False) -> typing.Iterable[on.types.TemplateType]:
        yield from on.template.getTemplates(self.api, force)

    def makeTemplate(self, fromTemplateId: str, name, toDataStore: str) -> str:
        return on.template.create(self.api, fromTemplateId, name, toDataStore)

    def checkTemplatePublished(self, templateId: str) -> bool:
        return on.template.checkPublished(self.api, templateId)

    def removeTemplate(self, templateId: str) -> None:
        on.template.remove(self.api, templateId)

    def deployFromTemplate(self, name: str, templateId: str) -> str:
        return on.template.deployFrom(self.api, templateId, name)

    def getMachineState(self, machineId: str) -> on.types.VmState:
        '''
        Returns the state of the machine
        This method do not uses cache at all (it always tries to get machine state from OpenNebula server)

        Args:
            machineId: Id of the machine to get state

        Returns:
            one of the on.VmState Values
        '''
        return on.vm.getMachineState(self.api, machineId)

    def getMachineSubstate(self, machineId: str) -> int:
        '''
        Returns the  LCM_STATE of a machine (STATE must be ready or this will return -1)
        '''
        return on.vm.getMachineSubstate(self.api, 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:
        '''
        on.vm.startMachine(self.api, 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:
        '''
        on.vm.stopMachine(self.api, machineId)

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

        Args:
            machineId: Id of the machine

        Returns:
        '''
        on.vm.suspendMachine(self.api, machineId)

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

        Args:
            machineId: Id of the machine

        Returns:
        '''
        on.vm.shutdownMachine(self.api, machineId)

    def resetMachine(self, machineId: str) -> None:
        '''
        Resets a machine (hard-reboot)
        '''
        on.vm.resetMachine(self.api, 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:
        '''
        on.vm.removeMachine(self.api, 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 on.vm.getNetInfo(self.api, machineId, networkId)

    def getConsoleConnection(self,
                             machineId: str) -> typing.Dict[str, typing.Any]:
        display = on.vm.getDisplayConnection(self.api, machineId)

        if display is None:
            raise Exception('Invalid console connection on OpenNebula!!!')

        return {
            'type': display['type'],
            'address': display['host'],
            'port': display['port'],
            'secure_port': -1,
            'monitors': 1,
            'cert_subject': '',
            'ticket': {
                'value': display['passwd'],
                'expiry': ''
            }
        }

    def desktopLogin(self, machineId: str, username: str, password: str,
                     domain: str):
        '''
        Not provided by OpenNebula API right now
        '''
        return

    @staticmethod
    def test(env: 'Environment',
             data: 'Module.ValuesType') -> typing.List[typing.Any]:
        return OpenNebulaProvider(env, data).testConnection()
Пример #28
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)
Пример #29
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 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)
Пример #30
0
class TX2GOTransport(BaseX2GOTransport):
    """
    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
    """
    typeName = _('X2Go')
    typeType = 'TX2GOTransport'
    typeDescription = _('X2Go access (Experimental). Tunneled connection.')
    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)

    fixedName = BaseX2GOTransport.fixedName
    screenSize = BaseX2GOTransport.screenSize
    desktopType = BaseX2GOTransport.desktopType
    customCmd = BaseX2GOTransport.customCmd
    sound = BaseX2GOTransport.sound
    exports = BaseX2GOTransport.exports
    speed = BaseX2GOTransport.speed

    soundType = BaseX2GOTransport.soundType
    keyboardLayout = BaseX2GOTransport.keyboardLayout
    pack = BaseX2GOTransport.pack
    quality = BaseX2GOTransport.quality

    def initialize(self, values: 'Module.ValuesType'):
        if values:
            if values['tunnelServer'].count(':') != 1:
                raise BaseX2GOTransport.ValidationException(
                    _('Must use HOST:PORT in Tunnel Server Field'))

    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]]:

        ci = self.getConnectionInfo(userService, user, password)
        username = ci['username']

        priv, pub = self.getAndPushKey(username, userService)

        width, height = self.getScreenSize()

        rootless = False
        desktop = self.desktopType.value
        if desktop == "UDSVAPP":
            desktop = "/usr/bin/udsvapp " + self.customCmd.value
            rootless = True

        xf = x2go_file.getTemplate(speed=self.speed.value,
                                   pack=self.pack.value,
                                   quality=self.quality.value,
                                   sound=self.sound.isTrue(),
                                   soundSystem=self.sound.value,
                                   windowManager=desktop,
                                   exports=self.exports.isTrue(),
                                   rootless=rootless,
                                   width=width,
                                   height=height,
                                   user=username)

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

        sshHost, sshPort = self.tunnelServer.value.split(':')

        # data
        data = {
            'os': os['OS'],
            'ip': ip,
            'port': 22,
            'key': priv,
            'width': width,
            'height': height,
            'printers': True,
            'drives': self.exports.isTrue(),
            'fullScreen': width == -1 or height == -1,
            'this_server': request.build_absolute_uri('/'),
            'xf': xf
        }

        m = tools.DictAsObj(data)

        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 = {
            'tunUser': tunuser,
            'tunPass': tunpass,
            'tunHost': sshHost,
            'tunPort': sshPort,
            'ip': ip,
            'port': '22',
            'key': priv,
            'xf': xf
        }

        return self.getScript('scripts/{}/direct.py', osName, sp)