def test_deploy_for_generic(helm, token_hex, tool, users):
    cookie_secret_proxy = "cookie secret proxy"
    cookie_secret_tool = "cookie secret tool"
    token_hex.side_effect = [cookie_secret_proxy, cookie_secret_tool]
    user = users["normal_user"]

    # simulate release with old naming scheme installed
    old_release_name = f"{user.username}-{tool.chart_name}"
    helm.list_releases.return_value = [old_release_name]

    tool_deployment = ToolDeployment(tool, user)
    tool_deployment.save()

    # uninstall tool with old naming scheme
    helm.delete.assert_called_with(True, old_release_name)

    # install new release
    helm.upgrade_release.assert_called_with(
        f"{tool.chart_name}-{user.slug}",
        f"mojanalytics/{tool.chart_name}",
        "--version",
        tool.version,
        "--namespace",
        user.k8s_namespace,
        "--set",
        f"username={user.username}",
        "--set",
        f"Username={user.username}",
        "--set",
        f"aws.iamRole={user.iam_role_name}",
        "--set",
        f"toolsDomain={settings.TOOLS_DOMAIN}",
    )
    def tool_deploy(self, message):
        """
        Deploy the named tool for the specified user
        Expects a message with `tool_name`, `version` and `user_id` values
        """

        tool, user = self.get_tool_and_user(message)
        id_token = message["id_token"]
        old_chart_name = message.get("old_chart_name", None)
        tool_deployment = ToolDeployment(tool, user, old_chart_name)

        update_tool_status(tool_deployment, id_token, TOOL_DEPLOYING)
        try:
            tool_deployment.save()
        except ToolDeployment.Error as err:
            update_tool_status(tool_deployment, id_token, TOOL_DEPLOY_FAILED)
            log.error(err)
            return

        status = wait_for_deployment(tool_deployment, id_token)

        if status == TOOL_DEPLOY_FAILED:
            log.warning(f"Failed deploying {tool.name} for {user}")
        else:
            log.debug(f"Deployed {tool.name} for {user}")
Exemple #3
0
def test_tool_upgrade(users, tools, update_tool_status):
    user = User.objects.first()
    tool = Tool.objects.first()
    id_token = "secret user id_token"

    with patch("controlpanel.frontend.consumers.ToolDeployment"
               ) as ToolDeployment:
        tool_deployment = Mock()
        ToolDeployment.return_value = tool_deployment

        message = {
            "user_id": user.auth0_id,
            "tool_name": tool.chart_name,
            "id_token": id_token,
        }

        consumer = consumers.BackgroundTaskConsumer("test")
        consumer.tool_deploy = Mock()  # mock tool_deploy() method
        consumer.tool_upgrade(message=message)

        # 1. calls/reuse tool_deploy()
        consumer.tool_deploy.assert_called_with(message)
        # 2. Instanciate `ToolDeployment` correctly
        ToolDeployment.assert_called_with(tool, user)
        # 3. Send status update
        update_tool_status.assert_called_with(
            tool_deployment,
            id_token,
            TOOL_UPGRADED,
        )
Exemple #4
0
def test_tool_deploy(users, tools, update_tool_status, wait_for_deployment):
    user = User.objects.first()
    tool = Tool.objects.first()
    id_token = "secret user id_token"

    with patch("controlpanel.frontend.consumers.ToolDeployment"
               ) as ToolDeployment:
        tool_deployment = Mock()
        ToolDeployment.return_value = tool_deployment

        consumer = consumers.BackgroundTaskConsumer("test")
        consumer.tool_deploy(
            message={
                "user_id": user.auth0_id,
                "tool_name": tool.chart_name,
                "id_token": id_token,
            })

        # 1. Instanciate `ToolDeployment` correctly
        ToolDeployment.assert_called_with(tool, user)
        # 2. Send status update
        update_tool_status.assert_called_with(
            tool_deployment,
            id_token,
            TOOL_DEPLOYING,
        )
        # 3. Call save() on ToolDeployment (trigger deployment)
        tool_deployment.save.assert_called()
        # 4. Wait for deployment to complete
        wait_for_deployment.assert_called_with(tool_deployment, id_token)
 def create(self, request):
     try:
         tool = Tool.objects.get(chart_name=request.data["name"])
     except Tool.DoesNotExist:
         return Response({}, status=status.HTTP_400_BAD_REQUEST)
     tool_deployment = ToolDeployment(tool, request.user)
     tool_deployment.save()
     return Response({}, status=status.HTTP_201_CREATED)
Exemple #6
0
def test_tool_deployment_outdated(cluster, chart_version, expected_outdated):
    tool = Tool(chart_name="test-tool", version="1.0.0")
    user = User(username="******")
    td = ToolDeployment(tool, user)
    id_token = "dummy"

    cluster_td = cluster.ToolDeployment.return_value
    cluster_td.get_installed_chart_version.return_value = chart_version

    assert td.outdated(id_token) == expected_outdated
    cluster.ToolDeployment.assert_called_with(user, tool)
    cluster_td.get_installed_chart_version.assert_called_with(id_token)
Exemple #7
0
def test_tool_deployment_get_installed_app_version(helm_repository_index, cluster, chart_version, expected_app_version):
    tool = Tool(chart_name="rstudio")
    user = User(username="******")
    td = ToolDeployment(tool, user)
    id_token = "dummy"

    cluster_td = cluster.ToolDeployment.return_value
    cluster_td.get_installed_chart_version.return_value = chart_version

    assert td.get_installed_app_version(id_token) == expected_app_version
    cluster.ToolDeployment.assert_called_with(user, tool)
    cluster_td.get_installed_chart_version.assert_called_with(id_token)
    def tool_restart(self, message):
        """
        Restart the named tool for the specified user
        """
        tool, user = self.get_tool_and_user(message)
        id_token = message["id_token"]

        tool_deployment = ToolDeployment(tool, user)
        update_tool_status(tool_deployment, id_token, TOOL_RESTARTING)

        tool_deployment.restart(id_token=id_token)

        status = wait_for_deployment(tool_deployment, id_token)

        if status == TOOL_DEPLOY_FAILED:
            log.warning(f"Failed restarting {tool.name} for {user}")
        else:
            log.debug(f"Restarted {tool.name} for {user}")
    def get_context_data(self, *args, **kwargs):
        """
        Retrieve information about tools and arrange them for the
        template to use them when being rendered.

        The `tool_info` dictionary in the contexts contains information
        about the tools, whether they're deployed for the user,
        versions, etc...arranged by `chart_name`.

        For example:

        ```
        {
           "tools_info": {
               "rstudio": {
                   "name": "RStudio",
                   "url: "https://john-rstudio.tools.example.com",
                   "deployment": ToolDeployment(RStudio, John),
                   "versions": {
                       "2.2.5": "RStudio: 1.2.1335+conda, R: 3.5.1, Python: 3.7.1, patch: 10",
                       "1.0.0": None,
                    }
               },
               # ...
           }
        }
        ```
        """

        user = self.request.user
        id_token = user.get_id_token()

        context = super().get_context_data(*args, **kwargs)
        context["id_token"] = id_token

        # Get list of deployed tools
        deployments = cluster.ToolDeployment.get_deployments(user, id_token)
        deployed_chart_names = []
        for deployment in deployments:
            chart_name, _ = deployment.metadata.labels["chart"].rsplit("-", 1)
            deployed_chart_names.append(chart_name)

        # Arrange tools information
        context["tools_info"] = {}
        for tool in context["tools"]:
            chart_name = tool.chart_name
            if chart_name not in context["tools_info"]:
                context["tools_info"][chart_name] = {
                    "name": tool.name,
                    "url": tool.url(user),
                    "deployment": None,
                    "versions": {},
                }

            if chart_name in deployed_chart_names:
                context["tools_info"][chart_name][
                    "deployment"] = ToolDeployment(tool, user)

            context["tools_info"][chart_name]["versions"][
                tool.version] = tool.app_version

        return context
    def get_context_data(self, *args, **kwargs):
        """
        Retrieve information about tools and arrange them for the
        template to use them when being rendered.

        The `tool_info` dictionary in the contexts contains information
        about the tools, whether they're deployed for the user,
        versions, etc...arranged by `chart_name`.

        For example:

        ```
        {
           "tools_info": {
               "rstudio": {
                   "name": "RStudio",
                   "url: "https://john-rstudio.tools.example.com",
                   "deployment": ToolDeployment(RStudio, John),
                   "versions": {
                       "2.2.5": {
                           "chart_name": "rstudio",
                           "description": "RStudio: 1.2.1335+conda, R: 3.5.1, Python: 3.7.1, patch: 10",
                       },
                       "1.0.0": {
                           "chart_name": "rstudio",
                           "description": None,
                       }
                    }
               },
               # ...
           }
        }
        ```
        """

        user = self.request.user
        id_token = user.get_id_token()

        context = super().get_context_data(*args, **kwargs)
        context["id_token"] = id_token

        # Get list of deployed tools
        deployments = cluster.ToolDeployment.get_deployments(user, id_token)
        deployed_chart_names = []
        for deployment in deployments:
            chart_name, _ = deployment.metadata.labels["chart"].rsplit("-", 1)
            deployed_chart_names.append(chart_name)
        # Defines how a matching chart name is put into a named tool bucket.
        # E.g. jupyter-* charts all end up in the jupyter-lab bucket.
        # chart name match: tool bucket
        tool_chart_lookup = {
            "airflow": "airflow-sqlite",
            "jupyter": "jupyter-lab",
            "rstudio": "rstudio",
        }
        # Arrange tools information
        context["tools_info"] = {}
        for tool in context["tools"]:
            chart_name = tool.chart_name
            # Work out which bucket the chart should be in (it'll be one of
            # those defined in
            tool_bucket = ""
            for key, bucket_name in tool_chart_lookup.items():
                if key in chart_name:
                    tool_bucket = bucket_name
                    break
            if not tool_bucket:
                # No matching tool bucket for the given chart. So ignore.
                break
            if tool_bucket not in context["tools_info"]:
                context["tools_info"][tool_bucket] = {
                    "name": tool.name,
                    "url": tool.url(user),
                    "deployment": None,
                    "versions": {},
                }

            if chart_name in deployed_chart_names:
                context["tools_info"][tool_bucket][
                    "deployment"] = ToolDeployment(tool, user)
            # Each version now needs to display the chart_name and the
            # "app_version" metadata from helm. TODO: Stop using helm.
            context["tools_info"][tool_bucket]["versions"][tool.version] = {
                "chart_name": chart_name,
                "description": tool.app_version
            }

        return context