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 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 initGui(self): logger.debug('Initializing gui') vals = [gui.choiceItem('0-0-0-0', ugettext('ALL POOLS'))] + [ gui.choiceItem(v.uuid, v.name) for v in ServicePool.objects.all().order_by('name') ] self.pool.setValues(vals)
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 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 getResources(parameters): ''' This helper is designed as a callback for Project Selector ''' from .Provider import OGProvider from uds.core.Environment import Environment logger.debug( 'Parameters received by getResources Helper: {0}'.format(parameters)) env = Environment(parameters['ev']) provider = OGProvider(env) provider.unserialize(parameters['ov']) api = provider.api labs = [gui.choiceItem('0', _('All Labs'))] + [ gui.choiceItem(l['id'], l['name']) for l in api.getLabs(ou=parameters['ou']) ] images = [ gui.choiceItem(z['id'], z['name']) for z in api.getImages(ou=parameters['ou']) ] data = [ { 'name': 'lab', 'values': labs }, { 'name': 'image', 'values': images }, ] logger.debug('Return data: {}'.format(data)) return data
def getResources( parameters: typing.Any) -> typing.List[typing.Dict[str, typing.Any]]: from .provider import OGProvider logger.debug('Parameters received by getResources Helper: %s', parameters) env = Environment(parameters['ev']) provider = OGProvider(env) provider.unserialize(parameters['ov']) api = provider.api labs = [gui.choiceItem('0', _('All Labs'))] + [ gui.choiceItem(l['id'], l['name']) for l in api.getLabs(ou=parameters['ou']) ] images = [ gui.choiceItem(z['id'], z['name']) for z in api.getImages(ou=parameters['ou']) ] data = [ { 'name': 'lab', 'values': labs }, { 'name': 'image', 'values': images }, ] logger.debug('Return data: %s', data) return data
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')
def getGui(self, parent: 'Provider', forType: str) -> typing.Iterable[typing.Any]: try: logger.debug('getGui parameters: %s, %s', parent, forType) parentInstance = parent.getInstance() serviceType = parentInstance.getServiceByType(forType) if not serviceType: raise self.invalidItemException( 'Gui for {} not found'.format(forType)) service = serviceType( Environment.getTempEnv(), parentInstance ) # Instantiate it so it has the opportunity to alter gui description based on parent localGui = self.addDefaultFields(service.guiDescription(service), ['name', 'comments', 'tags']) if GlobalConfig.EXPERIMENTAL_FEATURES.getBool(): self.addField( localGui, { 'name': 'proxy_id', 'values': [gui.choiceItem(-1, '')] + gui.sortedChoices([ gui.choiceItem(v.uuid, v.name) for v in models.Proxy.objects.all() ]), 'label': _('Proxy'), 'tooltip': _('Proxy for services behind a firewall'), 'type': gui.InputField.CHOICE_TYPE, 'tab': _('Advanced'), 'order': 132, }, ) else: self.addField( localGui, { 'name': 'proxy_id', 'value': '-1', 'type': gui.InputField.HIDDEN_TYPE, }, ) return localGui except Exception as e: logger.exception('getGui') raise ResponseError(str(e))
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 getVolumes(parameters): """ This helper is designed as a callback for Zone Selector """ from .Provider import Provider from uds.core.Environment import Environment logger.debug( 'Parameters received by getVolumes Helper: {0}'.format(parameters)) env = Environment(parameters['ev']) provider = Provider(env) provider.unserialize(parameters['ov']) api = provider.api(parameters['project'], parameters['region']) volumes = [ gui.choiceItem(v['id'], v['name']) for v in api.listVolumes() if v['name'] != '' and v['availability_zone'] == parameters['availabilityZone'] ] data = [ { 'name': 'volume', 'values': volumes }, ] logger.debug('Return data: {}'.format(data)) return data
def initGui(self): ''' Loads required values inside ''' api = self.parent().api() regions = [gui.choiceItem(r['id'], r['id']) for r in api.listRegions()] self.region.setValues(regions) tenants = [gui.choiceItem(t['id'], t['name']) for t in api.listProjects()] self.project.setValues(tenants) # So we can instantiate parent to get API logger.debug(self.parent().serialize()) self.ov.setDefValue(self.parent().serialize()) self.ev.setDefValue(self.parent().env.key)
def initGui(self): logger.debug('Initializing gui') vals = [ gui.choiceItem(v.uuid, v.name) for v in ServicePool.objects.all().order_by('name') ] self.pools.setValues(vals)
def initGui(self): logger.debug('Initializing gui') vals = [ gui.choiceItem(v.uuid, v.name) for v in Authenticator.objects.all() ] self.authenticator.setValues(vals)
def initGui(self): """ Loads required values inside """ api = self.parent().api() regions = [gui.choiceItem(r['id'], r['id']) for r in api.listRegions()] self.region.setValues(regions) tenants = [ gui.choiceItem(t['id'], t['name']) for t in api.listProjects() ] self.project.setValues(tenants) # So we can instantiate parent to get API logger.debug(self.parent().serialize()) self.ov.setDefValue(self.parent().serialize()) self.ev.setDefValue(self.parent().env.key)
def initGui(self): """ Loads required values inside """ ous = [gui.choiceItem(r['id'], r['name']) for r in self.parent().api.getOus()] self.ou.setValues(ous) self.ov.setDefValue(self.parent().serialize()) self.ev.setDefValue(self.parent().env.key)
def initGui(self): ''' Loads required values inside ''' ous = [gui.choiceItem(r['id'], r['name']) for r in self.parent().api.getOus()] self.ou.setValues(ous) self.ov.setDefValue(self.parent().serialize()) self.ev.setDefValue(self.parent().env.key)
def initGui(self): """ Loads required values inside """ templates = self.parent().getTemplates() vals = [] for t in templates: vals.append(gui.choiceItem(t[0], t[1])) # This is not the same case, values is not the "value" of the field, but # the list of values shown because this is a "ChoiceField" self.template.setValues(vals) datastores = self.parent().getDatastores() vals = [] for d in datastores: vals.append(gui.choiceItem(d[0], d[1])) self.datastore.setValues(vals)
def initGui(self): ''' Loads required values inside ''' templates = self.parent().getTemplates() vals = [] for t in templates: vals.append(gui.choiceItem(t[0], t[1])) # This is not the same case, values is not the "value" of the field, but # the list of values shown because this is a "ChoiceField" self.template.setValues(vals) datastores = self.parent().getDatastores() vals = [] for d in datastores: vals.append(gui.choiceItem(d[0], d[1])) self.datastore.setValues(vals)
def 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 getResources(parameters): """ This helper is designed as a callback for Project Selector """ from .Provider import Provider from uds.core.Environment import Environment logger.debug('Parameters received by getResources Helper: {0}'.format(parameters)) env = Environment(parameters['ev']) provider = Provider(env) provider.unserialize(parameters['ov']) api = provider.api(parameters['project'], parameters['region']) zones = [gui.choiceItem(z, z) for z in api.listAvailabilityZones()] networks = [gui.choiceItem(z['id'], z['name']) for z in api.listNetworks()] flavors = [gui.choiceItem(z['id'], z['name']) for z in api.listFlavors()] securityGroups = [gui.choiceItem(z['id'], z['name']) for z in api.listSecurityGroups()] volumeTypes = [gui.choiceItem('-', _('None'))] + [gui.choiceItem(t['id'], t['name']) for t in api.listVolumeTypes()] data = [ {'name': 'availabilityZone', 'values': zones }, {'name': 'network', 'values': networks }, {'name': 'flavor', 'values': flavors }, {'name': 'securityGroups', 'values': securityGroups }, {'name': 'volumeType', 'values': volumeTypes }, ] logger.debug('Return data: {}'.format(data)) return data
def getResources(parameters): ''' This helper is designed as a callback for Project Selector ''' if parameters['legacy'] == 'true': from .ProviderLegacy import ProviderLegacy as Provider else: from .Provider import Provider from uds.core.Environment import Environment logger.debug('Parameters received by getResources Helper: {0}'.format(parameters)) env = Environment(parameters['ev']) provider = Provider(env) provider.unserialize(parameters['ov']) api = provider.api(parameters['project'], parameters['region']) zones = [gui.choiceItem(z, z) for z in api.listAvailabilityZones()] networks = [gui.choiceItem(z['id'], z['name']) for z in api.listNetworks()] flavors = [gui.choiceItem(z['id'], z['name']) for z in api.listFlavors()] securityGroups = [gui.choiceItem(z['id'], z['name']) for z in api.listSecurityGroups()] volumeTypes = [gui.choiceItem('-', _('None'))] + [gui.choiceItem(t['id'], t['name']) for t in api.listVolumeTypes()] data = [ {'name': 'availabilityZone', 'values': zones }, {'name': 'network', 'values': networks }, {'name': 'flavor', 'values': flavors }, {'name': 'securityGroups', 'values': securityGroups }, {'name': 'volumeType', 'values': volumeTypes }, ] logger.debug('Return data: {}'.format(data)) return data
def getResources(parameters): """ This helper is designed as a callback for Project Selector """ from .Provider import OGProvider from uds.core.Environment import Environment logger.debug('Parameters received by getResources Helper: {0}'.format(parameters)) env = Environment(parameters['ev']) provider = OGProvider(env) provider.unserialize(parameters['ov']) api = provider.api labs = [gui.choiceItem('0', _('All Labs'))] + [gui.choiceItem(l['id'], l['name']) for l in api.getLabs(ou=parameters['ou'])] images = [gui.choiceItem(z['id'], z['name']) for z in api.getImages(ou=parameters['ou'])] data = [ {'name': 'lab', 'values': labs }, {'name': 'image', 'values': images }, ] logger.debug('Return data: {}'.format(data)) return data
def getVolumes(parameters: typing.Dict[str, str]) -> typing.List[typing.Dict[str, typing.Any]]: ''' This helper is designed as a callback for Zone Selector ''' api = getApi(parameters) # Source volumes are all available for us # volumes = [gui.choiceItem(v['id'], v['name']) for v in api.listVolumes() if v['name'] != '' and v['availability_zone'] == parameters['availabilityZone']] volumes = [gui.choiceItem(v['id'], v['name']) for v in api.listVolumes() if v['name'] != ''] data = [ {'name': 'volume', 'values': volumes}, ] logger.debug('Return data: %s', data) return data
def getGui(self, type_: str) -> typing.List[typing.Any]: localGUI = self.addDefaultFields([], ['name', 'short_name', 'comments', 'tags']) for field in [{ 'name': 'policy', 'values': [gui.choiceItem(k, str(v)) for k, v in MetaPool.TYPES.items()], 'label': ugettext('Policy'), 'tooltip': ugettext('Service pool policy'), 'type': gui.InputField.CHOICE_TYPE, 'order': 100, }, { 'name': 'image_id', 'values': [gui.choiceImage(-1, '--------', DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in Image.objects.all()]), 'label': ugettext('Associated Image'), 'tooltip': ugettext('Image assocciated with this service'), 'type': gui.InputField.IMAGECHOICE_TYPE, 'order': 120, 'tab': ugettext('Display'), }, { 'name': 'servicesPoolGroup_id', 'values': [gui.choiceImage(-1, _('Default'), DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in ServicePoolGroup.objects.all()]), 'label': ugettext('Pool group'), 'tooltip': ugettext('Pool group for this pool (for pool classify on display)'), 'type': gui.InputField.IMAGECHOICE_TYPE, 'order': 121, 'tab': ugettext('Display'), }, { 'name': 'visible', 'value': True, 'label': ugettext('Visible'), 'tooltip': ugettext('If active, metapool will be visible for users'), 'type': gui.InputField.CHECKBOX_TYPE, 'order': 123, 'tab': ugettext('Display'), }, { 'name': 'calendar_message', 'value': '', 'label': ugettext('Calendar access denied text'), 'tooltip': ugettext('Custom message to be shown to users if access is limited by calendar rules.'), 'type': gui.InputField.TEXT_TYPE, 'order': 124, 'tab': ugettext('Display'), }]: self.addField(localGUI, field) return localGUI
def getVolumes(parameters): """ This helper is designed as a callback for Zone Selector """ from .Provider import Provider from uds.core.Environment import Environment logger.debug('Parameters received by getVolumes Helper: {0}'.format(parameters)) env = Environment(parameters['ev']) provider = Provider(env) provider.unserialize(parameters['ov']) api = provider.api(parameters['project'], parameters['region']) volumes = [gui.choiceItem(v['id'], v['name']) for v in api.listVolumes() if v['name'] != '' and v['availability_zone'] == parameters['availabilityZone']] data = [ {'name': 'volume', 'values': volumes }, ] logger.debug('Return data: {}'.format(data)) return data
def getResources( parameters: typing.Dict[str, str] ) -> typing.List[typing.Dict[str, typing.Any]]: ''' This helper is designed as a callback for Project Selector ''' api, nameFromSubnets = getApi(parameters) zones = [gui.choiceItem(z, z) for z in api.listAvailabilityZones()] networks = [ gui.choiceItem(z['id'], z['name']) for z in api.listNetworks(nameFromSubnets=nameFromSubnets) ] flavors = [gui.choiceItem(z['id'], z['name']) for z in api.listFlavors()] securityGroups = [ gui.choiceItem(z['id'], z['name']) for z in api.listSecurityGroups() ] volumeTypes = [gui.choiceItem('-', _('None'))] + [ gui.choiceItem(t['id'], t['name']) for t in api.listVolumeTypes() ] data = [ { 'name': 'availabilityZone', 'values': zones }, { 'name': 'network', 'values': networks }, { 'name': 'flavor', 'values': flavors }, { 'name': 'securityGroups', 'values': securityGroups }, { 'name': 'volumeType', 'values': volumeTypes }, ] logger.debug('Return data: %s', data) return data
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
from django.utils.translation import ugettext_noop as _ from uds.core.services import ServiceProvider from uds.core.ui import gui from uds.core.util import validators from .LiveService import LiveService from . import openStack import logging __updated__ = '2018-10-22' logger = logging.getLogger(__name__) INTERFACE_VALUES = [ gui.choiceItem('public', 'public'), gui.choiceItem('private', 'private'), gui.choiceItem('admin', 'admin'), ] class ProviderLegacy(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
class HTML5VNCTransport(transports.Transport): """ Provides access via VNC to service. This transport can use an domain. If username processed by authenticator contains '@', it will split it and left-@-part will be username, and right password """ typeName = _('HTML5 VNC Experimental') typeType = 'HTML5VNCTransport' typeDescription = _('VNC protocol using HTML5 client (EXPERIMENTAL)') iconFile = 'html5vnc.png' ownLink = True supportedOss = OsDetector.allOss protocol = transports.protocols.VNC group = transports.TUNNELED_GROUP guacamoleServer = gui.TextField( label=_('Tunnel Server'), order=1, tooltip= _('Host of the tunnel server (use http/https & port if needed) as accesible from users' ), defvalue='https://', length=64, required=True, tab=gui.TUNNEL_TAB) username = gui.TextField( label=_('Username'), order=20, tooltip=_('Username for VNC connection authentication.'), tab=gui.PARAMETERS_TAB) password = gui.PasswordField( label=_('Password'), order=21, tooltip=_('Password for VNC connection authentication'), tab=gui.PARAMETERS_TAB) vncPort = gui.NumericField(length=22, label=_('VNC Server port'), defvalue='5900', order=2, tooltip=_('Port of the VNC server.'), required=True, tab=gui.PARAMETERS_TAB) colorDepth = gui.ChoiceField( order=26, label=_('Color depth'), tooltip=_( 'Color depth for VNC connection. Use this to control bandwidth.'), required=True, values=[ gui.choiceItem('-', 'default'), gui.choiceItem('8', '8 bits'), gui.choiceItem('16', '16 bits'), gui.choiceItem('24', '24 bits'), gui.choiceItem('32', '33 bits'), ], defvalue='-', tab=gui.PARAMETERS_TAB) swapRedBlue = gui.CheckBoxField( label=_('Swap red/blue'), order=27, tooltip= _('Use this if your colours seems incorrect (blue appears red, ..) to swap them.' ), tab=gui.PARAMETERS_TAB) cursor = gui.CheckBoxField( label=_('Remote cursor'), order=28, tooltip=_('If set, force to show remote cursor'), tab=gui.PARAMETERS_TAB) readOnly = gui.CheckBoxField( label=_('Read only'), order=29, tooltip=_('If set, the connection will be read only'), tab=gui.PARAMETERS_TAB) ticketValidity = gui.NumericField( length=3, label=_('Ticket Validity'), defvalue='60', order=90, tooltip= _('Allowed time, in seconds, for HTML5 client to reload data from UDS Broker. The default value of 60 is recommended.' ), required=True, minValue=60, tab=gui.ADVANCED_TAB) forceNewWindow = gui.CheckBoxField( label=_('Force new HTML Window'), order=91, tooltip= _('If checked, every connection will try to open its own window instead of reusing the "global" one.' ), defvalue=gui.FALSE, tab=gui.ADVANCED_TAB) def initialize(self, values: 'Module.ValuesType'): if not values: return # Strip spaces self.guacamoleServer.value = self.guacamoleServer.value.strip() if self.guacamoleServer.value[0:4] != 'http': raise transports.Transport.ValidationException( _('The server must be http or https')) def isAvailableFor(self, userService: 'models.UserService', ip: str) -> bool: """ Checks if the transport is available for the requested destination ip Override this in yours transports """ logger.debug('Checking availability for %s', ip) ready = self.cache.get(ip) if not ready: # Check again for readyness if self.testServer(userService, ip, self.vncPort.value) is True: self.cache.put(ip, 'Y', READY_CACHE_TIMEOUT) return True self.cache.put(ip, 'N', READY_CACHE_TIMEOUT) return ready == 'Y' def getLink( # pylint: disable=too-many-locals self, userService: 'models.UserService', transport: 'models.Transport', ip: str, os: typing.Dict[str, str], user: '******', password: str, request: 'HttpRequest') -> str: # Build params dict params = { 'protocol': 'vnc', 'hostname': ip, 'port': str(self.vncPort.num()), } if self.username.value.strip(): params['username'] = self.username.value.strip() if self.password.value.strip(): params['password'] = self.password.value.strip() if self.colorDepth.value != '-': params['color-depth'] = self.colorDepth.value if self.swapRedBlue.isTrue(): params['swap-red-blue'] = 'true' if self.cursor.isTrue(): params['cursor'] = 'remote' if self.readOnly.isTrue(): params['read-only'] = 'true' logger.debug('VNC Params: %s', params) scrambler = cryptoManager().randomString(32) ticket = models.TicketStore.create(params, validity=self.ticketValidity.num()) onw = 'o_n_w={};'.format(hash( transport.name)) if self.forceNewWindow.isTrue() else '' return str("{}/transport/?{}.{}&{}".format(self.guacamoleServer.value, ticket, scrambler, onw))
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")]
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();' ) )
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
from django.utils.translation import ugettext_noop as _ from uds.core.services import ServiceProvider from uds.core.ui import gui from uds.core.util import validators from .LiveService import LiveService from . import openStack import logging __updated__ = '2016-04-25' logger = logging.getLogger(__name__) INTERFACE_VALUES = [ gui.choiceItem('public', 'public'), gui.choiceItem('private', 'private'), gui.choiceItem('admin', 'admin'), ] 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
def listAssignables(self, item: ServicePool) -> typing.Any: service = item.service.getInstance() return [gui.choiceItem(i[0], i[1]) for i in service.listAssignables()]
def getGui(self, type_: str) -> typing.List[typing.Any]: # if OSManager.objects.count() < 1: # No os managers, can't create db # raise ResponseError(ugettext('Create at least one OS Manager before creating a new service pool')) if Service.objects.count() < 1: raise ResponseError( ugettext( 'Create at least a service before creating a new service pool' )) g = self.addDefaultFields([], ['name', 'short_name', 'comments', 'tags']) for f in [ { 'name': 'service_id', 'values': [gui.choiceItem('', '')] + gui.sortedChoices([ gui.choiceItem(v.uuid, v.provider.name + '\\' + v.name) for v in Service.objects.all() ]), 'label': ugettext('Base service'), 'tooltip': ugettext('Service used as base of this service pool'), 'type': gui.InputField.CHOICE_TYPE, 'rdonly': True, 'order': 100, # Ensueres is At end }, { 'name': 'osmanager_id', 'values': [gui.choiceItem(-1, '')] + gui.sortedChoices([ gui.choiceItem(v.uuid, v.name) for v in OSManager.objects.all() ]), 'label': ugettext('OS Manager'), 'tooltip': ugettext('OS Manager used as base of this service pool'), 'type': gui.InputField.CHOICE_TYPE, 'rdonly': True, 'order': 101, }, { 'name': 'allow_users_remove', 'value': False, 'label': ugettext('Allow removal by users'), 'tooltip': ugettext( 'If active, the user will be allowed to remove the service "manually". Be careful with this, because the user will have the "power" to delete it\'s own service' ), 'type': gui.InputField.CHECKBOX_TYPE, 'order': 111, 'tab': ugettext('Advanced'), }, { 'name': 'allow_users_reset', 'value': False, 'label': ugettext('Allow reset by users'), 'tooltip': ugettext( 'If active, the user will be allowed to reset the service' ), 'type': gui.InputField.CHECKBOX_TYPE, 'order': 112, 'tab': ugettext('Advanced'), }, { 'name': 'ignores_unused', 'value': False, 'label': ugettext('Ignores unused'), 'tooltip': ugettext( 'If the option is enabled, UDS will not attempt to detect and remove the user services assigned but not in use.' ), 'type': gui.InputField.CHECKBOX_TYPE, 'order': 113, 'tab': ugettext('Advanced'), }, { 'name': 'image_id', 'values': [gui.choiceImage(-1, '--------', DEFAULT_THUMB_BASE64)] + gui.sortedChoices([ gui.choiceImage(v.uuid, v.name, v.thumb64) for v in Image.objects.all() ]), 'label': ugettext('Associated Image'), 'tooltip': ugettext('Image assocciated with this service'), 'type': gui.InputField.IMAGECHOICE_TYPE, 'order': 120, 'tab': ugettext('Display'), }, { 'name': 'pool_group_id', 'values': [gui.choiceImage(-1, _('Default'), DEFAULT_THUMB_BASE64)] + gui.sortedChoices([ gui.choiceImage(v.uuid, v.name, v.thumb64) for v in ServicePoolGroup.objects.all() ]), 'label': ugettext('Pool group'), 'tooltip': ugettext( 'Pool group for this pool (for pool classify on display)'), 'type': gui.InputField.IMAGECHOICE_TYPE, 'order': 121, 'tab': ugettext('Display'), }, { 'name': 'visible', 'value': True, 'label': ugettext('Visible'), 'tooltip': ugettext('If active, transport will be visible for users'), 'type': gui.InputField.CHECKBOX_TYPE, 'order': 107, 'tab': ugettext('Display'), }, { 'name': 'initial_srvs', 'value': '0', 'minValue': '0', 'label': ugettext('Initial available services'), 'tooltip': ugettext('Services created initially for this service pool'), 'type': gui.InputField.NUMERIC_TYPE, 'order': 130, 'tab': ugettext('Availability'), }, { 'name': 'cache_l1_srvs', 'value': '0', 'minValue': '0', 'label': ugettext('Services to keep in cache'), 'tooltip': ugettext( 'Services kept in cache for improved user service assignation' ), 'type': gui.InputField.NUMERIC_TYPE, 'order': 131, 'tab': ugettext('Availability'), }, { 'name': 'cache_l2_srvs', 'value': '0', 'minValue': '0', 'label': ugettext('Services to keep in L2 cache'), 'tooltip': ugettext( 'Services kept in cache of level2 for improved service generation' ), 'type': gui.InputField.NUMERIC_TYPE, 'order': 132, 'tab': ugettext('Availability'), }, { 'name': 'max_srvs', 'value': '0', 'minValue': '1', 'label': ugettext('Maximum number of services to provide'), 'tooltip': ugettext( 'Maximum number of service (assigned and L1 cache) that can be created for this service' ), 'type': gui.InputField.NUMERIC_TYPE, 'order': 133, 'tab': ugettext('Availability'), }, { 'name': 'show_transports', 'value': True, 'label': ugettext('Show transports'), 'tooltip': ugettext( 'If active, alternative transports for user will be shown' ), 'type': gui.InputField.CHECKBOX_TYPE, 'tab': ugettext('Advanced'), 'order': 130, }, { 'name': 'account_id', 'values': [gui.choiceItem(-1, '')] + gui.sortedChoices([ gui.choiceItem(v.uuid, v.name) for v in Account.objects.all() ]), 'label': ugettext('Accounting'), 'tooltip': ugettext('Account associated to this service pool'), 'type': gui.InputField.CHOICE_TYPE, 'tab': ugettext('Advanced'), 'order': 131, } ]: self.addField(g, f) return g
class HTML5RDPTransport(transports.Transport): """ Provides access via RDP to service. This transport can use an domain. If username processed by authenticator contains '@', it will split it and left-@-part will be username, and right password """ typeName = _('HTML5 RDP') typeType = 'HTML5RDPTransport' typeDescription = _('RDP protocol using HTML5 client') iconFile = 'html5.png' ownLink = True supportedOss = OsDetector.allOss protocol = transports.protocols.RDP group = transports.TUNNELED_GROUP guacamoleServer = gui.TextField( label=_('Tunnel Server'), order=1, tooltip= _('Host of the tunnel server (use http/https & port if needed) as accesible from users' ), defvalue='https://', length=64, required=True, tab=gui.TUNNEL_TAB, ) useGlyptodonTunnel = gui.CheckBoxField( label=_('Use Glyptodon Enterprise tunnel'), order=2, tooltip= _('If checked, UDS will use Glyptodon Enterprise Tunnel for HTML tunneling instead of UDS Tunnel' ), tab=gui.TUNNEL_TAB, ) useEmptyCreds = gui.CheckBoxField( label=_('Empty creds'), order=3, tooltip=_('If checked, the credentials used to connect will be emtpy'), tab=gui.CREDENTIALS_TAB, ) fixedName = gui.TextField( label=_('Username'), order=4, tooltip=_( 'If not empty, this username will be always used as credential'), tab=gui.CREDENTIALS_TAB, ) fixedPassword = gui.PasswordField( label=_('Password'), order=5, tooltip=_( 'If not empty, this password will be always used as credential'), tab=gui.CREDENTIALS_TAB, ) withoutDomain = gui.CheckBoxField( label=_('Without Domain'), order=6, tooltip= _('If checked, the domain part will always be emptied (to connecto to xrdp for example is needed)' ), tab=gui.CREDENTIALS_TAB, ) fixedDomain = gui.TextField( label=_('Domain'), order=7, tooltip= _('If not empty, this domain will be always used as credential (used as DOMAIN\\user)' ), tab=gui.CREDENTIALS_TAB, ) wallpaper = gui.CheckBoxField( label=_('Show wallpaper'), order=20, tooltip= _('If checked, the wallpaper and themes will be shown on machine (better user experience, more bandwidth)' ), tab=gui.PARAMETERS_TAB, ) desktopComp = gui.CheckBoxField( label=_('Allow Desk.Comp.'), order=22, tooltip=_('If checked, desktop composition will be allowed'), tab=gui.PARAMETERS_TAB, ) smooth = gui.CheckBoxField( label=_('Font Smoothing'), order=23, tooltip=_( 'If checked, fonts smoothing will be allowed (windows clients only)' ), tab=gui.PARAMETERS_TAB, ) enableAudio = gui.CheckBoxField( label=_('Enable Audio'), order=24, tooltip= _('If checked, the audio will be redirected to remote session (if client browser supports it)' ), tab=gui.PARAMETERS_TAB, defvalue=gui.TRUE, ) enableAudioInput = gui.CheckBoxField( label=_('Enable Microphone'), order=24, tooltip= _('If checked, the microphone will be redirected to remote session (if client browser supports it)' ), tab=gui.PARAMETERS_TAB, ) enablePrinting = gui.CheckBoxField( label=_('Enable Printing'), order=25, tooltip= _('If checked, the printing will be redirected to remote session (if client browser supports it)' ), tab=gui.PARAMETERS_TAB, ) enableFileSharing = gui.ChoiceField( label=_('File Sharing'), order=22, tooltip=_('File upload/download redirection policy'), defvalue='false', values=[ { 'id': 'false', 'text': 'Disable file sharing' }, { 'id': 'down', 'text': 'Allow download only' }, { 'id': 'up', 'text': 'Allow upload only' }, { 'id': 'true', 'text': 'Enable file sharing' }, ], tab=gui.PARAMETERS_TAB, ) serverLayout = gui.ChoiceField( order=26, label=_('Layout'), tooltip=_('Keyboards Layout of server'), required=True, values=[ gui.choiceItem('-', 'default'), gui.choiceItem('en-us-qwerty', _('English (US) keyboard')), gui.choiceItem('en-gb-qwerty', _('English (GB) keyboard')), gui.choiceItem('es-es-qwerty', _('Spanish keyboard')), gui.choiceItem('es-latam-qwerty', _('Latin American keyboard')), gui.choiceItem('de-de-qwertz', _('German keyboard (qwertz)')), gui.choiceItem('fr-fr-azerty', _('French keyboard (azerty)')), gui.choiceItem('fr-ch-qwertz', _('Swiss French keyboard (qwertz)')), gui.choiceItem('de-ch-qwertz', _('Swiss German keyboard (qwertz)')), gui.choiceItem('it-it-qwerty', _('Italian keyboard')), gui.choiceItem('sv-se-qwerty', _('Swedish keyboard')), gui.choiceItem('ja-jp-qwerty', _('Japanese keyboard')), gui.choiceItem('pt-br-qwerty', _('Brazilian keyboard')), gui.choiceItem('failsafe', _('Failsafe')), ], defvalue='-', tab=gui.PARAMETERS_TAB, ) security = gui.ChoiceField( order=27, label=_('Security'), tooltip=_('Connection security mode for Guacamole RDP connection'), required=True, values=[ gui.choiceItem( 'any', _('Any (Allow the server to choose the type of auth)')), gui.choiceItem( 'rdp', _('RDP (Standard RDP encryption. Should be supported by all servers)' ), ), gui.choiceItem( 'nla', _('NLA (Network Layer authentication. Requires VALID username&password, or connection will fail)' ), ), gui.choiceItem( 'nla-ext', _('NLA extended (Network Layer authentication. Requires VALID username&password, or connection will fail)' ), ), gui.choiceItem('tls', _('TLS (Transport Security Layer encryption)')), ], defvalue='any', tab=gui.PARAMETERS_TAB, ) ticketValidity = gui.NumericField( length=3, label=_('Ticket Validity'), defvalue='60', order=90, tooltip= _('Allowed time, in seconds, for HTML5 client to reload data from UDS Broker. The default value of 60 is recommended.' ), required=True, minValue=60, tab=gui.ADVANCED_TAB, ) forceNewWindow = gui.ChoiceField( order=91, label=_('Force new HTML Window'), tooltip=_('Select windows behavior for new connections on HTML5'), required=True, values=[ gui.choiceItem( gui.FALSE, _('Open every connection on the same window, but keeps UDS window.' ), ), gui.choiceItem( gui.TRUE, _('Force every connection to be opened on a new window.')), gui.choiceItem( 'overwrite', _('Override UDS window and replace it with the connection.'), ), ], defvalue=gui.FALSE, tab=gui.ADVANCED_TAB, ) customGEPath = gui.TextField( label=_('Glyptodon Enterprise context path'), order=92, tooltip= _('Customized path for Glyptodon Enterprise tunnel. (Only valid for Glyptodon Enterprise Tunnel)' ), defvalue='/', length=128, required=False, tab=gui.ADVANCED_TAB, ) def initialize(self, values: 'Module.ValuesType'): if not values: return # Strip spaces self.guacamoleServer.value = self.guacamoleServer.value.strip() if self.guacamoleServer.value[0:4] != 'http': raise transports.Transport.ValidationException( _('The server must be http or https')) if self.useEmptyCreds.isTrue() and self.security.value != 'rdp': raise transports.Transport.ValidationException( _('Empty credentials (on Credentials tab) is only allowed with Security level (on Parameters tab) set to "RDP"' )) # Same check as normal RDP transport def isAvailableFor(self, userService: 'models.UserService', ip: str) -> bool: """ Checks if the transport is available for the requested destination ip Override this in yours transports """ logger.debug('Checking availability for %s', ip) ready = self.cache.get(ip) if not ready: # Check again for readyness if self.testServer(userService, ip, '3389') is True: self.cache.put(ip, 'Y', READY_CACHE_TIMEOUT) return True self.cache.put(ip, 'N', READY_CACHE_TIMEOUT) return ready == 'Y' def processedUser(self, userService: 'models.UserService', user: '******') -> str: v = self.processUserAndPassword(userService, user, '') return v['username'] def processUserAndPassword(self, userService: 'models.UserService', user: '******', password: str) -> typing.Dict[str, str]: username: str = user.getUsernameForAuth() if self.fixedName.value != '': username = self.fixedName.value proc = username.split('@') domain = proc[1] if len(proc) > 1 else '' username = proc[0] if self.fixedPassword.value != '': password = self.fixedPassword.value azureAd = False if self.fixedDomain.value != '': if self.fixedDomain.value.lower() == 'azuread': azureAd = True else: domain = self.fixedDomain.value if self.useEmptyCreds.isTrue(): username, password, domain = '', '', '' # If no domain to be transfered, set it to '' if self.withoutDomain.isTrue(): domain = '' if '.' in domain: # Dotter domain form username = username + '@' + domain domain = '' # If AzureAD, include it on username if azureAd: username = '******' + username # Fix username/password acording to os manager username, password = userService.processUserPassword( username, password) return { 'protocol': self.protocol, 'username': username, 'password': password, 'domain': domain, } def getLink( # pylint: disable=too-many-locals self, userService: 'models.UserService', transport: 'models.Transport', ip: str, os: typing.Dict[str, str], user: '******', password: str, request: 'HttpRequest', ) -> str: credsInfo = self.processUserAndPassword(userService, user, password) username, password, domain = ( credsInfo['username'], credsInfo['password'], credsInfo['domain'], ) scrambler = cryptoManager().randomString(32) passwordCrypted = cryptoManager().symCrypt(password, scrambler) # Build params dict params = { 'protocol': 'rdp', 'hostname': ip, 'username': username, 'password': passwordCrypted, 'resize-method': 'display-update', 'ignore-cert': 'true', 'security': self.security.value, 'drive-path': '/share/{}'.format(user.uuid), 'create-drive-path': 'true', 'ticket-info': { 'userService': userService.uuid, 'user': userService.user.uuid, }, } if False: # Future imp sanitize = lambda x: re.sub("[^a-zA-Z0-9_-]", "_", x) params['recording-path'] = ( '/share/recording/' + sanitize(user.manager.name) + '_' + sanitize(user.name) + '/' + getSqlDatetime().strftime('%Y%m%d-%H%M')) params['create-recording-path'] = 'true' if domain: params['domain'] = domain if self.enableFileSharing.value == 'true': params['enable-drive'] = 'true' elif self.enableFileSharing.value == 'down': params['enable-drive'] = 'true' params['disable-upload'] = 'true' elif self.enableFileSharing.value == 'up': params['enable-drive'] = 'true' params['disable-download'] = 'true' if self.serverLayout.value != '-': params['server-layout'] = self.serverLayout.value if not self.enableAudio.isTrue(): params['disable-audio'] = 'true' elif self.enableAudioInput.isTrue(): params['enable-audio-input'] = 'true' if self.enablePrinting.isTrue(): params['enable-printing'] = 'true' params['printer-name'] = 'UDS-Printer' if self.wallpaper.isTrue(): params['enable-wallpaper'] = 'true' if self.desktopComp.isTrue(): params['enable-desktop-composition'] = 'true' if self.smooth.isTrue(): params['enable-font-smoothing'] = 'true' logger.debug('RDP Params: %s', params) ticket = models.TicketStore.create(params, validity=self.ticketValidity.num()) onw = '' if self.forceNewWindow.value == gui.TRUE: onw = '&o_n_w={}' elif self.forceNewWindow.value == 'overwrite': onw = '&o_s_w=yes' onw = onw.format(hash(transport.name)) path = (self.customGEPath.value if self.useGlyptodonTunnel.isTrue() else '/guacamole') # Remove trailing / if path[-1] == '/': path = path[:-1] return str("{server}{path}/#/?data={ticket}.{scrambler}{onw}".format( server=self.guacamoleServer.value, path=path, ticket=ticket, scrambler=scrambler, onw=onw, ))