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}")
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, )
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)
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)
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