コード例 #1
0
    def get(self, request, *args, **kwargs):
        agave = request.user.agave_oauth.client
        app_id = request.GET.get('app_id')
        if app_id:
            METRICS.debug("user:{} is requesting app id:{}".format(
                request.user.username, app_id))
            data = _get_app(app_id, request.user)

            if settings.PORTAL_DATA_DEPOT_LOCAL_STORAGE_SYSTEMS:
                # check if default system needs keys pushed
                default_sys = UserSystemsManager(
                    request.user,
                    settings.PORTAL_DATA_DEPOT_LOCAL_STORAGE_SYSTEM_DEFAULT)
                storage_sys = StorageSystem(agave, default_sys.get_system_id())
                success, result = storage_sys.test()
                data['systemHasKeys'] = success
                data['pushKeysSystem'] = storage_sys.to_dict()
        else:
            METRICS.debug("user:{} is requesting all public apps".format(
                request.user.username))
            public_only = request.GET.get('publicOnly')
            name = request.GET.get('name', None)
            list_kwargs = {}
            if public_only == 'true':
                list_kwargs['publicOnly'] = 'true'
            else:
                list_kwargs['privateOnly'] = True
            if name:
                list_kwargs['query'] = {"name": name}
            data = {'appListing': agave.apps.list(**list_kwargs)}

        return JsonResponse({"response": data})
コード例 #2
0
def setup(username, system):
    """Fires necessary steps for setup

    Called asynchronously from portal.apps.auth.tasks.setup_user

    As of 03/2018 a new account setup means creating a home directory
    (optional), creating an Agave system for that home directory
    and saving the newly created keys in the database.
    The private key will be encrypted using AES.

    :param str username: Account's username to setup

    :return: home_dir, home_sys

    .. note::
        The django setting `PORTAL_USER_ACCOUNT_SETUP_STEPS` can be used to
        add any additional steps after the default setup.
    """

    user = check_user(username)
    mgr = UserSystemsManager(user, system)
    home_dir = mgr.get_private_directory(user)
    systemId = mgr.get_system_id()
    system = StorageSystem(user.agave_oauth.client, id=systemId)
    success, result = system.test()
    if success:
        logger.info(
            "{username} has valid configuration for {systemId}, skipping creation"
            .format(username=username, systemId=systemId))
        return home_dir
    home_sys = mgr.setup_private_system(user)

    return home_dir
コード例 #3
0
    def get(self, request):
        portal_systems = settings.PORTAL_DATAFILES_STORAGE_SYSTEMS
        local_systems = settings.PORTAL_DATA_DEPOT_LOCAL_STORAGE_SYSTEMS

        # compare available storage systems to the systems a user can access
        response = {'system_list': []}
        if request.user.is_authenticated:
            if local_systems:
                user_systems = get_user_storage_systems(
                    request.user.username, local_systems)
                for system_name, details in user_systems.items():
                    response['system_list'].append({
                        'name':
                        details['name'],
                        'system':
                        UserSystemsManager(
                            request.user,
                            system_name=system_name).get_system_id(),
                        'scheme':
                        'private',
                        'api':
                        'tapis',
                        'icon':
                        details['icon']
                    })
                default_system = user_systems[
                    settings.PORTAL_DATA_DEPOT_LOCAL_STORAGE_SYSTEM_DEFAULT]
                response['default_host'] = default_system['host']
        if portal_systems:
            response['system_list'] += portal_systems
        return JsonResponse(response)
コード例 #4
0
ファイル: views.py プロジェクト: TACC/protx
 def getLocalStorageSystems(self, user):
     result = []
     for system in settings.PORTAL_DATA_DEPOT_LOCAL_STORAGE_SYSTEMS.keys():
         try:
             sys_def = UserSystemsManager(user, system_name=system)
             result.append({
                 "path":
                 sys_def.get_sys_tas_user_dir(),
                 "mountPath":
                 "/{namespace}/{name}".format(
                     namespace=settings.PORTAL_NAMESPACE,
                     name=sys_def.get_name()),
                 "pems":
                 "rw"
             })
         except Exception:
             logger.exception("Could not retrieve system {}".format(system))
     return result
コード例 #5
0
ファイル: user_systems_unit_test.py プロジェクト: TACC/protx
def test_init(tas_mock, test_manager, regular_user_with_underscore):
    # Assert that the default system will be loaded
    assert test_manager.system == settings.PORTAL_DATA_DEPOT_LOCAL_STORAGE_SYSTEMS[
        'frontera']
    assert test_manager.tas_user[
        'username'] == regular_user_with_underscore.username

    # Assert that it loads a system by name
    mgr = UserSystemsManager(regular_user_with_underscore,
                             system_name='longhorn')
    assert mgr.system == settings.PORTAL_DATA_DEPOT_LOCAL_STORAGE_SYSTEMS[
        'longhorn']
コード例 #6
0
ファイル: user_systems_unit_test.py プロジェクト: TACC/protx
def test_manager(tas_mock, regular_user_with_underscore):
    yield UserSystemsManager(regular_user_with_underscore)
コード例 #7
0
    def post(self, request, *args, **kwargs):
        agave = request.user.agave_oauth.client
        job_post = json.loads(request.body)
        job_id = job_post.get('job_id')
        job_action = job_post.get('action')

        # cancel job / stop job
        if job_id and job_action:
            METRICS.info("user:{} is canceling/stopping job id:{}".format(
                request.user.username, job_id))
            data = agave.jobs.manage(jobId=job_id, body={"action": job_action})
            return JsonResponse({"response": data})
        # submit job
        elif job_post:
            METRICS.info("user:{} is submitting job:{}".format(
                request.user.username, job_post))
            default_sys = UserSystemsManager(
                request.user,
                settings.PORTAL_DATA_DEPOT_LOCAL_STORAGE_SYSTEM_DEFAULT)

            # cleaning archive path value
            if job_post.get('archivePath'):
                parsed = urlparse(job_post['archivePath'])
                if parsed.path.startswith('/') and len(parsed.path) > 1:
                    # strip leading '/'
                    archive_path = parsed.path[1:]
                elif parsed.path == '':
                    # if path is blank, set to root of system
                    archive_path = '/'
                else:
                    archive_path = parsed.path

                job_post['archivePath'] = archive_path

                if parsed.netloc:
                    job_post['archiveSystem'] = parsed.netloc
                else:
                    job_post['archiveSystem'] = default_sys.get_system_id()
            else:
                job_post['archivePath'] = \
                    'archive/jobs/{}/${{JOB_NAME}}-${{JOB_ID}}'.format(
                        timezone.now().strftime('%Y-%m-%d'))
                job_post['archiveSystem'] = default_sys.get_system_id()

            # check for running licensed apps
            lic_type = _app_license_type(job_post['appId'])
            if lic_type is not None:
                _, license_models = get_license_info()
                license_model = [
                    x for x in license_models if x.license_type == lic_type
                ][0]
                lic = license_model.objects.filter(user=request.user).first()
                if not lic:
                    raise ApiException(
                        "You are missing the required license for this application."
                    )
                job_post['parameters']['_license'] = lic.license_as_str()

            # url encode inputs
            if job_post['inputs']:
                job_post = url_parse_inputs(job_post)

            # Get or create application based on allocation and execution system
            apps_mgr = UserApplicationsManager(request.user)
            app = apps_mgr.get_or_create_app(job_post['appId'],
                                             job_post['allocation'])

            if app.exec_sys:
                return JsonResponse(
                    {"response": {
                        "execSys": app.exec_sys.to_dict()
                    }})

            job_post['appId'] = app.id
            del job_post['allocation']

            if settings.DEBUG:
                wh_base_url = settings.WH_BASE_URL + '/webhooks/'
                jobs_wh_url = settings.WH_BASE_URL + reverse(
                    'webhooks:jobs_wh_handler')
            else:
                wh_base_url = request.build_absolute_uri('/webhooks/')
                jobs_wh_url = request.build_absolute_uri(
                    reverse('webhooks:jobs_wh_handler'))

            job_post['parameters']['_webhook_base_url'] = wh_base_url
            job_post['notifications'] = [{
                'url': jobs_wh_url,
                'event': e
            } for e in settings.PORTAL_JOB_NOTIFICATION_STATES]

            # Remove any params from job_post that are not in appDef
            job_post['parameters'] = {
                param: job_post['parameters'][param]
                for param in job_post['parameters']
                if param in [p['id'] for p in app.parameters]
            }

            response = agave.jobs.submit(body=job_post)

            if "id" in response:
                job = JobSubmission.objects.create(user=request.user,
                                                   jobId=response["id"])
                job.save()

            return JsonResponse({"response": response})
コード例 #8
0
 def __init__(self, *args, **kwargs):
     super(UserApplicationsManager, self).__init__(*args, **kwargs)
     self.user_systems_mgr = UserSystemsManager(self.user)
コード例 #9
0
class UserApplicationsManager(AbstractApplicationsManager):
    """User Applications Manager

    Class that provides workflows to clone apps and execution systems for a user.

    """
    def __init__(self, *args, **kwargs):
        super(UserApplicationsManager, self).__init__(*args, **kwargs)
        self.user_systems_mgr = UserSystemsManager(self.user)

    def get_clone_system_id(self):
        """Gets system id to deploy cloned app materials to.

        *System Id* is a string, unique id for each system.
        This function returns the system id for a user's home system.

        :returns: System unique id
        :rtype: str
        """

        sys_id = self.user_systems_mgr.get_system_id()
        return sys_id

    def get_application(self, appId):
        """Gets an application

        :param str appId: Unique id of the application

        :returns: Application instance
        :rtype: class Application
        """

        app = Application(self.client, id=appId)
        return app

    def check_app_for_updates(self,
                              cloned_app,
                              host_app_id=None,
                              host_app=None):
        """Checks cloned app for updates against host app by comparing the revision
        of the host app to the 'cloneRevision' tag inserted into the cloned apps tags.

        :param cloned_app: Application instance of the cloned application
        :param host_app_id: Agave id of the host application
        :param host_app: Application instance of the host application

        :returns: update_required
        :rtype: bool
        """
        update_required = False

        # compare cloned app revision number to original app revision number
        if not host_app:
            host_app = self.get_application(host_app_id)

        logger.debug(
            'Looking for revision match in tags for app def: {}'.format(
                cloned_app.to_dict()))
        # find revision number in tags
        tag_match = [s for s in cloned_app.tags if 'cloneRevision' in s]
        if not tag_match:
            logger.error(
                'No cloneRevision in tags, app should be updated to ensure consistency.'
            )
            update_required = True
        else:
            try:
                clone_rev = int(tag_match[0].split(':')[1])
                if clone_rev != host_app.revision:
                    logger.warning(
                        'Cloned app revision does not match host: {} != {}'.
                        format(clone_rev, host_app.revision))
                    update_required = True
            except ValueError as exc:
                logger.exception(
                    'cloneRevision in tags cannot be converted to integer, app should be updated to ensure consistency. %s',
                    exc)
                update_required = True

        return update_required

    def get_or_create_cloned_app_exec_system(self, host_exec_id, allocation):
        host_exec = ExecutionSystem(self.client, host_exec_id)
        host_exec_user_role = host_exec.roles.for_user(
            username=self.user.username)
        if host_exec_user_role and host_exec_user_role.role == 'OWNER':
            cloned_exec_sys = host_exec
            logger.debug('Using current execution system {}'.format(
                cloned_exec_sys.id))
        else:
            cloned_exec_id = '{username}.{allocation}.exec.{resource}.{execType}.{revision}'.format(
                username=self.user.username.replace('_', '-'),
                allocation=allocation,
                resource=host_exec.login.host.replace('.tacc.utexas.edu', ''),
                execType=host_exec.execution_type,
                revision=host_exec.revision)
            logger.debug(
                'Getting cloned execution system: {}'.format(cloned_exec_id))
            cloned_exec_sys = self.get_or_create_exec_system(
                cloned_exec_id, host_exec.id, allocation)
        return cloned_exec_sys

    def clone_application(self,
                          allocation,
                          cloned_app_name,
                          host_app_id=None,
                          host_app=None):
        """Clones an application given a host app, allocation, and target name.

        ..note: checks if cloned Execution System already exists for user,
        and creates it if not.

        :param str allocation: Project allocation
        :param str cloned_app_name: Name of application clone
        :param str host_app_id: Agave id of host app
        :param host_app: Application instance of host app

        :returns: Application instance
        :rtype: class Application
        """
        if not host_app:
            host_app = self.get_application(host_app_id)

        logger.debug(
            'Starting process to clone new application for user with id: {}-{}.0'
            .format(cloned_app_name, host_app.revision))

        cloned_exec_sys = self.get_or_create_cloned_app_exec_system(
            host_app.execution_system, allocation)

        cloned_depl_path = '.APPDATA/{appName}-{rev}.0'.format(
            username=self.user.username,
            appName=cloned_app_name,
            rev=host_app.revision)

        logger.debug(
            'Cloning app id {}-{} with exec sys {} at path {} on deployment sys {}'
            .format(
                cloned_app_name,
                host_app.revision,
                cloned_exec_sys.id,
                cloned_depl_path,
                self.get_clone_system_id(),
            ))
        cloned_app = host_app.clone(self.client,
                                    depl_path=cloned_depl_path,
                                    exec_sys=cloned_exec_sys.id,
                                    depl_sys=self.get_clone_system_id(),
                                    name=cloned_app_name,
                                    ver='{}.0'.format(host_app.revision))

        # add host revision number to cloned app's tags
        cloned_app.tags.append('cloneRevision:{}'.format(host_app.revision))
        cloned_app.update()

        # if system needs keys, pass system along with app object to instantiate push keys modal
        if hasattr(cloned_exec_sys, 'needs_keys'):
            cloned_app.exec_sys = cloned_exec_sys

        return cloned_app

    def get_or_create_cloned_app(self, host_app, allocation,
                                 cloned_execution_system):
        """Gets or creates a cloned app for the user.

        Generates a cloned app id and tries to fetch that app.
        If the app exists, check for updates.

        If app does not exist, clone the host app to cloned app id.

        :param host_app: Application instance of host app
        :param str allocation: Project allocation for app to be run on
        :param ExecutionSystem cloned_execution_system: Cloned execution system

        :returns: Application instance
        :rtype: class Application
        """

        # cloned_app_name is of the form 'prtl.clone.sal.PT2050-DataX.compress-0.1u1'
        # NOTE: host revision # will be appended to cloned_app_id, e.g. prtl.clone.sal.PT2050-DataX.compress-0.1u1-2.0
        cloned_app_name = 'prtl.clone.{username}.{allocation}.{appId}'.format(
            username=self.user.username,
            allocation=allocation,
            appId=host_app.id)

        cloned_app_id = '{appId}-{rev}.0'.format(appId=cloned_app_name,
                                                 rev=host_app.revision)
        try:
            cloned_app = self.get_application(cloned_app_id)

            logger.debug('Cloned app {} found. Checking for updates...'.format(
                cloned_app_id))

            if cloned_app.execution_system != cloned_execution_system.id:
                logger.info(
                    "Cloned app {} has outdated execution system ('{}' != '{}'). Recreating..."
                    .format(cloned_app_id, cloned_app.execution_system,
                            cloned_execution_system.id))
                cloned_app.delete()
                cloned_app = self.clone_application(allocation,
                                                    cloned_app_name,
                                                    host_app=host_app)
                return cloned_app

            if not cloned_app.available:
                logger.info(
                    'Cloned app {} is unavailable. Recreating...'.format(
                        cloned_app_id))
                cloned_app.delete()
                cloned_app = self.clone_application(allocation,
                                                    cloned_app_name,
                                                    host_app=host_app)
                return cloned_app

            if not host_app.is_public:
                update_required = self.check_app_for_updates(cloned_app,
                                                             host_app=host_app)
                if update_required:
                    # Need to update cloned app by deleting and re-cloning
                    logger.warning(
                        'Cloned app is being updated (i.e. deleted and re-cloned)'
                    )
                    cloned_app.delete()
                    cloned_app = self.clone_application(allocation,
                                                        cloned_app_name,
                                                        host_app=host_app)
                else:
                    logger.debug('Cloned app is current with host.')

            return cloned_app

        except HTTPError as exc:
            if exc.response.status_code == 404:
                logger.debug('No app found with id {}. Cloning app...'.format(
                    cloned_app_id))
                cloned_app = self.clone_application(allocation,
                                                    cloned_app_name,
                                                    host_app=host_app)
                return cloned_app
            else:
                raise

    def get_or_create_app(self, appId, allocation):
        """Gets or creates application for user.

        If application selected is owned by user, return the app,
        else clone the app to the same exec system with the
        specified allocation.

        ..note: Entry point.

        :param str appId: Agave id of application selected to run
        :param str allocation: Project alloction for app to run on

        :returns: Application instance
        :rtype: class Application
        """

        host_app = self.get_application(appId)

        # if app is owned by user, no need to clone
        if host_app.owner == self.user.username:
            logger.info(
                'User is app owner, no need to clone. Returning original app.')
            app = host_app
            exec_sys = ExecutionSystem(self.client,
                                       app.execution_system,
                                       ignore_error=None)
        else:
            exec_sys = self.get_or_create_cloned_app_exec_system(
                host_app.execution_system, allocation)
            app = self.get_or_create_cloned_app(host_app, allocation, exec_sys)

        # Check if app's execution system needs keys reset and pushed
        if not app.exec_sys:
            sys_ok, res = exec_sys.test()
            if not sys_ok and (exec_sys.owner == self.user.username):
                logger.debug(res)
                logger.info('System {} needs new keys.'.format(exec_sys.id))
                app.exec_sys = exec_sys

        return app

    def clone_execution_system(self, host_system_id, new_system_id, alloc):
        """Clone execution system for user.

        :param str host_system_id: Agave id of host execution system
        :param str new_system_id: id for system clone
        :param str alloc: Project allocation for system's custom directives

        :returns: ExecutionSystem instance
        :rtype: ExecutionSystem
        """

        clone_body = {'action': 'CLONE', 'id': new_system_id}

        cloned_sys = self.client.systems.manage(body=clone_body,
                                                systemId=host_system_id)

        sys = self.validate_exec_system(cloned_sys['id'], alloc)

        return sys

    def set_system_definition(self, system_id, allocation):  # pylint:disable=arguments-differ
        """Initializes Agave execution system

        :param class system_id: ExecutionSystem ID
        :param str allocation: Project allocation for customDirectives

        :returns: ExecutionSystem instance
        :rtype: class ExecutionSystem
        """
        system = self.get_exec_system(system_id)

        if not system.available:
            system.enable()

        storage_settings = {}
        exec_settings = {}
        for host, val in settings.PORTAL_EXEC_SYSTEMS.items():
            if host in system.storage.host:
                storage_settings = val
            if host in system.login.host:
                exec_settings = val

        system.site = settings.PORTAL_DOMAIN
        system.name = "Execution system for user {}".format(self.user.username)
        system.storage.home_dir = storage_settings['home_dir'].format(
            self.user_systems_mgr.get_private_directory(
            )) if 'home_dir' in storage_settings else ''
        system.storage.port = system.login.port
        system.storage.root_dir = '/'
        system.storage.auth.username = self.user.username
        system.storage.auth.type = system.AUTH_TYPES.SSHKEYS
        system.login.auth.username = self.user.username
        system.login.auth.type = system.AUTH_TYPES.SSHKEYS
        system.work_dir = '/work/{}'.format(
            self.user_systems_mgr.get_private_directory())
        system.scratch_dir = exec_settings['scratch_dir'].format(
            self.user_systems_mgr.get_private_directory(
            )) if 'scratch_dir' in exec_settings else ''

        if system.scheduler == 'SLURM':
            for queue in system.queues.all():
                if queue.custom_directives:
                    queue.custom_directives = '-A {}'.format(allocation)
        return system

    def validate_exec_system(self, system_id, alloc, *args, **kwargs):
        """Validate execution system and generate keys for it

        :param system_id: Agave system id
        :param alloc: Project allocation for system

        :returns: ExecutionSystsem instance
        :rtype: class ExecutionSystem
        """

        system = self.set_system_definition(system_id, alloc)

        # NOTE: Check if host keys already exist for user for both login and storage hosts
        for auth_block in [system.login, system.storage]:
            try:
                keys = self.user.ssh_keys.for_hostname(
                    hostname=auth_block.host)
                priv_key_str = keys.private_key()
                publ_key_str = keys.public
                auth_block.auth.public_key = publ_key_str
                auth_block.auth.private_key = priv_key_str
            except ObjectDoesNotExist:
                system.needs_keys = True
                auth_block.auth.public_key = 'public_key'
                auth_block.auth.private_key = 'private_key'

        system.update()

        return system

    def get_exec_system(self, systemId, *args, **kwargs):
        """Gets an execution system

        :param systemId: Agave Execution system id

        :returns: ExecutionSystem instance
        :rtype: class ExecutionSystem
        """

        exec_sys = ExecutionSystem(self.client, systemId, ignore_error=None)
        return exec_sys

    def get_or_create_exec_system(self, clonedSystemId, hostSystemId, alloc,
                                  *args, **kwargs):
        """Gets or creates user's execution system

        :param str clonedSystemId: Agave id of new system to be created
        :param str hostSystemId: Agave id of host system to clone from
        :param str alloc: Project allocation for system

        :returns: Agave response for the system
        """
        try:
            exec_sys = self.get_exec_system(clonedSystemId)
            if not exec_sys.available:
                exec_sys = self.validate_exec_system(exec_sys.id, alloc)
            logger.debug('Execution system found')
            return exec_sys
        except HTTPError as exc:
            if exc.response.status_code == 404:
                logger.debug('No execution system found, cloning system')
                exec_sys = self.clone_execution_system(hostSystemId,
                                                       clonedSystemId, alloc)
                return exec_sys