Esempio n. 1
0
def test_initializer_sets_http_client(jupyterhub_api_environ):
    """
    Does initializer set a httpclient instance?
    """
    sut = JupyterHubAPI()
    assert sut.client is not None
    assert sut.client != ''
Esempio n. 2
0
def test_initializer_raises_error_with_jupyterhub_api_token_env_as_missing(
        monkeypatch, jupyterhub_api_environ):
    """
    Does initializer raise an error when 'JUPYTERHUB_API_TOKEN' is an empty string?
    """
    monkeypatch.setenv('JUPYTERHUB_API_TOKEN', '')
    with pytest.raises(EnvironmentError):
        JupyterHubAPI()
Esempio n. 3
0
def test_initializer_raises_error_with_jupyterhub_api_url_env_as_missing(
        monkeypatch, jupyterhub_api_environ):
    """
    Does initializer raise an error when 'JUPYTERHUB_API_URL' is an empty None?
    """
    monkeypatch.setenv("JUPYTERHUB_API_URL", "")
    with pytest.raises(EnvironmentError):
        JupyterHubAPI()
Esempio n. 4
0
def test_initializer_sets_headers_to_make_request(jupyterhub_api_environ):
    """
    Does initializer set the api url?
    """
    sut = JupyterHubAPI()
    assert sut.default_headers is not None
    assert type(sut.default_headers) is dict
    assert sut.default_headers[
        'Authorization'] == f'token {os.environ.get("JUPYTERHUB_API_TOKEN")}'
Esempio n. 5
0
async def test_get_group_uses_request_helper_method_with_correct_values(
        mock_request, jupyterhub_api_environ):
    """
    Does get_group method use the helper method and pass the correct values?
    """
    sut = JupyterHubAPI()
    await sut.get_group('some-value')
    assert mock_request.called
    mock_request.assert_called_with('groups/some-value')
Esempio n. 6
0
async def test_create_user_uses_request_helper_method_with_correct_values(
        mock_request, jupyterhub_api_environ):
    """
    Does create_user method use the helper method and pass the correct values?
    """
    sut = JupyterHubAPI()
    await sut.create_user('new_user')
    assert mock_request.called
    body_usernames = {'usernames': ['new_user']}
    mock_request.assert_called_with('users/new_user', method='POST', body='')
Esempio n. 7
0
async def test_create_user_uses_request_helper_method_with_correct_values(
        mock_request, jupyterhub_api_environ):
    """
    Does create_user method use the helper method and pass the correct values?
    """
    sut = JupyterHubAPI()
    await sut.create_user("new_user")
    assert mock_request.called
    body_usernames = {"usernames": ["new_user"]}
    mock_request.assert_called_with("users/new_user", method="POST", body="")
Esempio n. 8
0
async def test_create_group_uses_request_helper_method_with_correct_values(
        mock_request, jupyterhub_api_environ):
    """
    Does create_group method use the helper method and pass the correct values?
    """
    sut = JupyterHubAPI()
    await sut.create_group("some-value")
    assert mock_request.called
    mock_request.assert_called_with("groups/some-value",
                                    body="",
                                    method="POST")
Esempio n. 9
0
async def test_add_group_member_uses_request_helper_method_with_correct_values(
        mock_request, jupyterhub_api_environ):
    """
    Does add_group_member method use the helper method and pass the correct values?
    """
    sut = JupyterHubAPI()
    await sut.add_group_member('to_group', 'a_user')
    assert mock_request.called
    body_usernames = {'users': ['a_user']}
    mock_request.assert_called_with('groups/to_group/users',
                                    method='POST',
                                    body=json.dumps(body_usernames))
Esempio n. 10
0
async def test_add_group_member_uses_request_helper_method_with_correct_values(
        mock_request, jupyterhub_api_environ):
    """
    Does add_group_member method use the helper method and pass the correct values?
    """
    sut = JupyterHubAPI()
    await sut.add_group_member("to_group", "a_user")
    assert mock_request.called
    body_usernames = {"users": ["a_user"]}
    mock_request.assert_called_with("groups/to_group/users",
                                    method="POST",
                                    body=json.dumps(body_usernames))
Esempio n. 11
0
async def setup_course_hook(
    authenticator: Authenticator, handler: RequestHandler, authentication: Dict[str, str]
) -> Dict[str, str]:
    """
    Calls the microservice to setup up a new course in case it does not exist.
    The data needed is received from auth_state within authentication object. This
    function assumes that the required k/v's in the auth_state dictionary are available,
    since the Authenticator(s) validates the data beforehand.

    This function requires `Authenticator.enable_auth_state = True` and is intended
    to be used as a post_auth_hook.

    Args:
        authenticator: the JupyterHub Authenticator object
        handler: the JupyterHub handler object
        authentication: the authentication object returned by the
          authenticator class

    Returns:
        authentication (Required): updated authentication object
    """
    lti_utils = LTIUtils()
    jupyterhub_api = JupyterHubAPI()

    # normalize the name and course_id strings in authentication dictionary
    course_id = lti_utils.normalize_string(authentication['auth_state']['course_id'])
    nb_service = NbGraderServiceHelper(course_id)
    username = lti_utils.normalize_string(authentication['name'])
    lms_user_id = authentication['auth_state']['lms_user_id']
    user_role = authentication['auth_state']['user_role']
    # register the user (it doesn't matter if it is a student or instructor) with her/his lms_user_id in nbgrader
    nb_service.add_user_to_nbgrader_gradebook(username, lms_user_id)
    # TODO: verify the logic to simplify groups creation and membership
    if user_is_a_student(user_role):
        # assign the user to 'nbgrader-<course_id>' group in jupyterhub and gradebook
        await jupyterhub_api.add_student_to_jupyterhub_group(course_id, username)
    elif user_is_an_instructor(user_role):
        # assign the user in 'formgrade-<course_id>' group
        await jupyterhub_api.add_instructor_to_jupyterhub_group(course_id, username)
    # launch the new (?) grader-notebook as a service
    setup_response = await register_new_service(org_name=ORG_NAME, course_id=course_id)

    return authentication
Esempio n. 12
0
    def __init__(self, org: str, course_id: str, domain: str):
        self.org = org
        self.course_id = course_id
        self.domain = domain

        self.exchange_root = Path(os.environ.get('MNT_ROOT'), self.org,
                                  'exchange')
        self.grader_name = f'grader-{course_id}'
        self.grader_root = Path(
            os.environ.get('MNT_ROOT'),
            org,
            'home',
            self.grader_name,
        )
        self.course_root = self.grader_root / course_id
        self.token = token_hex(32)
        self.client = docker.from_env()

        self.uid = int(os.environ.get('NB_UID'))
        self.gid = int(os.environ.get('NB_GID'))
        self._is_new_setup = False
        self.jupyterhub_api = JupyterHubAPI()
Esempio n. 13
0
def test_initializer_sets_api_url_from_env_var(jupyterhub_api_environ):
    """
    Does initializer set the api url correctly?
    """
    sut = JupyterHubAPI()
    assert sut.api_root_url == os.environ.get('JUPYTERHUB_API_URL')
Esempio n. 14
0
def test_initializer_sets_api_token_from_env_var(jupyterhub_api_environ):
    """
    Does initializer set the api token correctly?
    """
    sut = JupyterHubAPI()
    assert sut.token == os.environ.get('JUPYTERHUB_API_TOKEN')
Esempio n. 15
0
async def setup_course_hook_lti11(
    handler: RequestHandler,
    authentication: Dict[str, str],
) -> Dict[str, str]:
    """
    Calls the microservice to setup up a new course in case it does not exist when receiving
    LTI 1.1 launch requests. The data needed is received from auth_state within authentication object.
    This function assumes that the required k/v's in the auth_state dictionary are available,
    since the Authenticator(s) validates the data beforehand.

    This function requires `Authenticator.enable_auth_state = True` and is intended
    to be used as a post_auth_hook.

    Args:
        handler: the JupyterHub handler object
        authentication: the authentication object returned by the
            authenticator class

    Returns:
        authentication (Required): updated authentication object
    """
    lti_utils = LTIUtils()
    jupyterhub_api = JupyterHubAPI()

    # normalize the name and course_id strings in authentication dictionary
    username = authentication["name"]
    lms_user_id = authentication["auth_state"]["user_id"]
    user_role = authentication["auth_state"]["roles"].split(",")[0]
    course_id = lti_utils.normalize_string(
        authentication["auth_state"]["context_label"])
    nb_service = NbGraderServiceHelper(course_id, True)

    # register the user (it doesn't matter if it is a student or instructor) with her/his lms_user_id in nbgrader
    nb_service.add_user_to_nbgrader_gradebook(username, lms_user_id)
    # TODO: verify the logic to simplify groups creation and membership
    if user_is_a_student(user_role):
        try:
            # assign the user to 'nbgrader-<course_id>' group in jupyterhub and gradebook
            await jupyterhub_api.add_student_to_jupyterhub_group(
                course_id, username)
        except AddJupyterHubUserException as e:
            logger.error(
                "An error when adding student username: %s to course_id: %s with exception %s",
                (username, course_id, e),
            )
    elif user_is_an_instructor(user_role):
        try:
            # assign the user in 'formgrade-<course_id>' group
            await jupyterhub_api.add_instructor_to_jupyterhub_group(
                course_id, username)
        except AddJupyterHubUserException as e:
            logger.error(
                "An error when adding instructor username: %s to course_id: %s with exception %s",
                (username, course_id, e),
            )

    # launch the new grader-notebook as a service
    try:
        _ = await register_new_service(org_name=ORG_NAME, course_id=course_id)
    except Exception as e:
        logger.error(
            "Unable to launch the shared grader notebook with exception %s", e)

    return authentication
Esempio n. 16
0
async def setup_course_hook(authenticator: Authenticator,
                            handler: RequestHandler,
                            authentication: Dict[str, str]) -> Dict[str, str]:
    """
    Calls the microservice to setup up a new course in case it does not exist.
    The data needed is received from auth_state within authentication object. This
    function assumes that the required k/v's in the auth_state dictionary are available,
    since the Authenticator(s) validates the data beforehand.

    This function requires `Authenticator.enable_auth_state = True` and is intended
    to be used as a post_auth_hook.

    Args:
        authenticator: the JupyterHub Authenticator object
        handler: the JupyterHub handler object
        authentication: the authentication object returned by the
          authenticator class

    Returns:
        authentication (Required): updated authentication object
    """
    lti_utils = LTIUtils()
    jupyterhub_api = JupyterHubAPI()

    announcement_port = os.environ.get('ANNOUNCEMENT_SERVICE_PORT') or '8889'
    org = os.environ.get('ORGANIZATION_NAME')
    if not org:
        raise EnvironmentError('ORGANIZATION_NAME env-var is not set')
    # normalize the name and course_id strings in authentication dictionary
    course_id = lti_utils.normalize_string(
        authentication['auth_state']['course_id'])
    username = lti_utils.normalize_string(authentication['name'])
    lms_user_id = authentication['auth_state']['lms_user_id']
    user_role = authentication['auth_state']['user_role']
    # TODO: verify the logic to simplify groups creation and membership
    if user_role == 'Student' or user_role == 'Learner':
        # assign the user to 'nbgrader-<course_id>' group in jupyterhub and gradebook
        await jupyterhub_api.add_student_to_jupyterhub_group(
            course_id, username)
        await jupyterhub_api.add_user_to_nbgrader_gradebook(
            course_id, username, lms_user_id)
    elif user_role == 'Instructor':
        # assign the user in 'formgrade-<course_id>' group
        await jupyterhub_api.add_instructor_to_jupyterhub_group(
            course_id, username)
    client = AsyncHTTPClient()
    data = {
        'org': org,
        'course_id': course_id,
        'domain': handler.request.host,
    }
    service_name = os.environ.get(
        'DOCKER_SETUP_COURSE_SERVICE_NAME') or 'setup-course'
    port = os.environ.get('DOCKER_SETUP_COURSE_PORT') or '8000'
    url = f'http://{service_name}:{port}'
    headers = {'Content-Type': 'application/json'}
    response = await client.fetch(url,
                                  headers=headers,
                                  body=json.dumps(data),
                                  method='POST')
    if not response.body:
        raise JSONDecodeError('The setup course response body is empty', '', 0)
    resp_json = json.loads(response.body)
    logger.debug(f'Setup-Course service response: {resp_json}')

    # In case of new courses launched then execute a rolling update with jhub to reload our configuration file
    if 'is_new_setup' in resp_json and resp_json['is_new_setup'] is True:
        # notify the user the browser needs to be reload (when traefik redirects to a new jhub)
        url = f'http://localhost:{int(announcement_port)}/services/announcement'
        jupyterhub_api_token = os.environ.get('JUPYTERHUB_API_TOKEN')
        headers['Authorization'] = f'token {jupyterhub_api_token}'
        body_data = {
            'announcement':
            'A new service was detected, please reload this page...'
        }
        await client.fetch(url,
                           headers=headers,
                           body=json.dumps(body_data),
                           method='POST')

        logger.debug(
            'The current jupyterhub instance will be updated by setup-course service...'
        )
        url = f'http://{service_name}:{port}/rolling-update'
        # our setup-course not requires auth
        del headers['Authorization']
        # WE'RE NOT USING <<<AWAIT>>> because the rolling update should occur later
        client.fetch(url, headers=headers, body='', method='POST')

    return authentication