Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability for workspace service to request an address space #2902

Merged
merged 39 commits into from
Dec 25, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
3b36858
Add ability for workspace service to request an address space
marrobi Nov 23, 2022
03f5a57
Increase API version
marrobi Nov 23, 2022
52fb385
Fix issue in prior PR that added array to an array.
marrobi Nov 23, 2022
4c44c64
remove blank lines
marrobi Nov 23, 2022
9b5fa48
Merge branch 'main' of github.com:microsoft/AzureTRE into marrobi/iss…
marrobi Nov 23, 2022
e3578a0
Public works, private fails 445
marrobi Nov 25, 2022
0659a06
Merge branch 'main' into marrobi/issue2810
tamirkamara Nov 28, 2022
47efdf5
Merge main
marrobi Dec 2, 2022
bf84bf3
Add comment.
marrobi Dec 2, 2022
e5bc1ec
Merge branch 'main' of github.com:microsoft/AzureTRE into marrobi/iss…
marrobi Dec 8, 2022
c7402c3
Add false for force update.
marrobi Dec 8, 2022
4bfb103
Update etag logic
marrobi Dec 9, 2022
c2654cf
Add docs and changelog
marrobi Dec 9, 2022
c816d20
Merge branch 'main' into marrobi/issue2810
marrobi Dec 9, 2022
5b7f541
tf linting issues
marrobi Dec 9, 2022
712491b
Merge branch 'marrobi/issue2810' of github.com:marrobi/AzureTRE into …
marrobi Dec 9, 2022
24edf34
Remove etag from tests
marrobi Dec 9, 2022
cef3700
Remove etag from tests
marrobi Dec 9, 2022
098d368
set etag in test
marrobi Dec 9, 2022
bcfeebd
update assignent
marrobi Dec 9, 2022
9479cbd
change return object for mock
marrobi Dec 9, 2022
959129e
Set etag on modified workspace
marrobi Dec 9, 2022
5c6f9c2
add template version to modified resource
marrobi Dec 9, 2022
1d3ccea
Update templates/workspaces/base/terraform/network/providers.tf
marrobi Dec 12, 2022
42868e4
Update api_app/_version.py
marrobi Dec 12, 2022
66f16db
Update lock file and providers as per comment.
marrobi Dec 12, 2022
727afca
Merge branch 'main' into marrobi/issue2810
marrobi Dec 12, 2022
529f52e
add missing dependancies on az login
marrobi Dec 12, 2022
53441fa
Merge branch 'marrobi/issue2810' of github.com:marrobi/AzureTRE into …
marrobi Dec 12, 2022
6dc6553
Merge branch 'main' into marrobi/issue2810
marrobi Dec 14, 2022
99e6ce2
Merge branch 'main' into marrobi/issue2810
tamirkamara Dec 19, 2022
fc5565f
Merge branch 'main' into marrobi/issue2810
tamirkamara Dec 19, 2022
0de176b
Merge branch 'main' into marrobi/issue2810
tamirkamara Dec 19, 2022
00fe0d1
Merge branch 'main' into marrobi/issue2810
tamirkamara Dec 20, 2022
42badb6
Merge branch 'main' into marrobi/issue2810
tamirkamara Dec 20, 2022
8f93a8c
fix unit tests failing by async cosmos client bug
anatbal Dec 21, 2022
d1ca5bf
Merge branch 'main' into marrobi/issue2810
anatbal Dec 25, 2022
46bc406
Merge branch 'main' into marrobi/issue2810
tamirkamara Dec 25, 2022
3e5e7f8
Merge branch 'main' into marrobi/issue2810
anatbal Dec 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api_app/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.5.18"
__version__ = "0.5.19"
12 changes: 11 additions & 1 deletion api_app/api/routes/workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ async def retrieve_workspace_service_by_id(workspace_service=Depends(get_workspa


@workspace_services_workspace_router.post("/workspaces/{workspace_id}/workspace-services", status_code=status.HTTP_202_ACCEPTED, response_model=OperationInResponse, name=strings.API_CREATE_WORKSPACE_SERVICE, dependencies=[Depends(get_current_workspace_owner_user)])
async def create_workspace_service(response: Response, workspace_service_input: WorkspaceServiceInCreate, user=Depends(get_current_workspace_owner_user), workspace_service_repo=Depends(get_repository(WorkspaceServiceRepository)), resource_template_repo=Depends(get_repository(ResourceTemplateRepository)), operations_repo=Depends(get_repository(OperationRepository)), workspace=Depends(get_deployed_workspace_by_id_from_path)) -> OperationInResponse:
async def create_workspace_service(response: Response, workspace_service_input: WorkspaceServiceInCreate, user=Depends(get_current_workspace_owner_user), workspace_service_repo=Depends(get_repository(WorkspaceServiceRepository)), workspace_repo=Depends(get_repository(WorkspaceRepository)), resource_template_repo=Depends(get_repository(ResourceTemplateRepository)), operations_repo=Depends(get_repository(OperationRepository)), workspace=Depends(get_deployed_workspace_by_id_from_path), etag: str = Header(...)) -> OperationInResponse:

try:
workspace_service, resource_template = workspace_service_repo.create_workspace_service_item(workspace_service_input, workspace.id, user.roles)
Expand All @@ -229,6 +229,16 @@ async def create_workspace_service(response: Response, workspace_service_input:
logging.error(f"User not authorized to use template: {e}")
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(e))

# if template has address_space get an address space
if resource_template.properties.get("address_space"):
# check workspace has address_spaces property
if not workspace.properties.get("address_spaces"):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=strings.WORKSPACE_DOES_NOT_HAVE_ADDRESS_SPACES_PROPERTY)
tamirkamara marked this conversation as resolved.
Show resolved Hide resolved
workspace_service.properties["address_space"] = workspace_repo.get_address_space_based_on_size(workspace_service_input.properties)
workspace_patch = ResourcePatch()
workspace_patch.properties = {"address_spaces": workspace.properties["address_spaces"] + [workspace_service.properties["address_space"]]}
workspace_repo.patch_workspace(workspace, workspace_patch, etag, resource_template_repo, user)
tamirkamara marked this conversation as resolved.
Show resolved Hide resolved

operation = await save_and_deploy_resource(
resource=workspace_service,
resource_repo=workspace_service_repo,
Expand Down
5 changes: 3 additions & 2 deletions api_app/db/repositories/workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ def create_workspace_item(self, workspace_input: WorkspaceInCreate, auth_info: d
template = self.validate_input_against_template(workspace_input.templateName, workspace_input, ResourceType.Workspace, user_roles)

# allow for workspace template taking a single address_space or multiple address_spaces
address_space_param = {"address_space": self.get_address_space_based_on_size(workspace_input.properties)}
address_spaces_param = {"address_spaces": [address_space_param]}
intial_address_space = self.get_address_space_based_on_size(workspace_input.properties)
address_space_param = {"address_space": intial_address_space}
address_spaces_param = {"address_spaces": [intial_address_space]}

auto_app_registration_param = {"register_aad_application": self.automatically_create_application_registration(workspace_input.properties)}
workspace_owner_param = {"workspace_owner_object_id": self.get_workspace_owner(workspace_input.properties, workspace_owner_object_id)}
Expand Down
1 change: 1 addition & 0 deletions api_app/resources/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
WORKSPACE_SERVICE_IS_NOT_DEPLOYED = "Workspace service is not deployed."
WORKSPACE_SERVICE_NEEDS_TO_BE_DISABLED_BEFORE_DELETION = "The workspace service needs to be disabled before you can delete it"
WORKSPACE_SERVICES_NEED_TO_BE_DELETED_BEFORE_WORKSPACE = "All workspace services need to be deleted before you can delete the workspace"
WORKSPACE_DOES_NOT_HAVE_ADDRESS_SPACES_PROPERTY = "Workspace does not have address_spaces property"
WORKSPACE_TEMPLATE_VERSION_EXISTS = "A template with this version already exists"
OPERATION_DOES_NOT_EXIST = "Operation does not exist"
CUSTOM_ACTION_NOT_DEFINED = "The specified custom action isn't defined in the targeted resource."
Expand Down
3 changes: 1 addition & 2 deletions api_app/schemas/workspace.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
"description": "These parameters are required for all workspaces",
"required": [
"display_name",
"description",
"address_space_size"
tamirkamara marked this conversation as resolved.
Show resolved Hide resolved
"description"
],
"properties": {
"display_name": {
Expand Down
59 changes: 57 additions & 2 deletions api_app/tests_ma/test_api/test_routes/test_workspaces.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import uuid
from pydantic import Field
import pytest
from mock import patch

Expand Down Expand Up @@ -553,25 +554,79 @@ def log_in_with_owner_user(self, app, owner_user):
@ patch("api.routes.workspaces.WorkspaceServiceRepository.create_workspace_service_item", return_value=[sample_workspace_service(), sample_resource_template()])
async def test_post_workspace_services_creates_workspace_service(self, _, __, ___, ____, get_workspace_mock, resource_template_repo, app, client, workspace_service_input, basic_workspace_service_template):
auth_info_user_in_workspace_owner_role = {'sp_id': 'ab123', 'roles': {'WorkspaceOwner': 'ab124', 'WorkspaceResearcher': 'ab125'}}
etag = "some-etag-value"
workspace = sample_workspace(auth_info=auth_info_user_in_workspace_owner_role)
get_workspace_mock.return_value = workspace

resource_template_repo.return_value = basic_workspace_service_template
response = await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_SERVICE, workspace_id=WORKSPACE_ID), json=workspace_service_input)
response = await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_SERVICE, workspace_id=WORKSPACE_ID), json=workspace_service_input, headers={"etag": etag})

assert response.status_code == status.HTTP_202_ACCEPTED
assert response.json()["operation"]["resourceId"] == SERVICE_ID

# [POST] /workspaces/{workspace_id}/workspace-services
@ patch("api.routes.workspaces.save_and_deploy_resource", return_value=sample_resource_operation(resource_id=SERVICE_ID, operation_id=OPERATION_ID))
@ patch("api.routes.workspaces.WorkspaceRepository.get_timestamp", return_value=FAKE_UPDATE_TIMESTAMP)
@ patch("api.routes.workspaces.WorkspaceRepository.update_item_with_etag", return_value=sample_workspace())
@ patch("api.dependencies.workspaces.WorkspaceRepository.get_new_address_space", return_value="10.1.4.0/24")
@ patch("api.routes.workspaces.ResourceTemplateRepository.get_template_by_name_and_version")
@ patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id")
@ patch("api.routes.workspaces.OperationRepository.resource_has_deployed_operation", return_value=True)
@ patch("api.routes.workspaces.WorkspaceServiceRepository.create_workspace_service_item")
async def test_post_workspace_services_creates_workspace_service_with_address_space(self, create_workspace_service_item_mock, __, get_workspace_mock, resource_template_repo, ___, update_item_mock, ____, _____, app, client, workspace_service_input, basic_workspace_service_template, basic_resource_template):
etag = "some-etag-value"
workspace = sample_workspace()
workspace.properties["address_spaces"] = ["192.168.0.1/24"]
get_workspace_mock.return_value = workspace
basic_workspace_service_template.properties["address_space"]: str = Field()
create_workspace_service_item_mock.return_value = [sample_workspace_service(), basic_workspace_service_template]
basic_resource_template.properties["address_spaces"] = {"type": "array", "updateable": True}
resource_template_repo.side_effect = [basic_resource_template, basic_workspace_service_template]

modified_workspace = sample_workspace()
modified_workspace.isEnabled = True
modified_workspace.history = [ResourceHistoryItem(properties={'client_id': '12345', 'scope_id': 'test_scope_id', 'address_spaces': ["192.168.0.1/24"]}, isEnabled=True, resourceVersion=0, updatedWhen=FAKE_CREATE_TIMESTAMP, user=create_admin_user())]
modified_workspace.resourceVersion = 1
modified_workspace.user = create_workspace_owner_user()
modified_workspace.updatedWhen = FAKE_UPDATE_TIMESTAMP
modified_workspace.properties["address_spaces"] = ["192.168.0.1/24", "10.1.4.0/24"]

response = await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_SERVICE, workspace_id=WORKSPACE_ID), json=workspace_service_input, headers={"etag": etag})

update_item_mock.assert_called_once_with(modified_workspace, etag)
assert response.status_code == status.HTTP_202_ACCEPTED
assert response.json()["operation"]["resourceId"] == SERVICE_ID

# [POST] /workspaces/{workspace_id}/workspace-services
@ patch("api.dependencies.workspaces.WorkspaceRepository.get_new_address_space", return_value="10.1.4.0/24")
@ patch("api.routes.workspaces.ResourceTemplateRepository.get_template_by_name_and_version")
@ patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id")
@ patch("api.routes.resource_helpers.send_resource_request_message", return_value=sample_resource_operation(resource_id=SERVICE_ID, operation_id=OPERATION_ID))
@ patch("api.routes.workspaces.OperationRepository.resource_has_deployed_operation", return_value=True)
@ patch("api.routes.workspaces.WorkspaceServiceRepository.create_workspace_service_item")
async def test_post_workspace_services_creates_workspace_service_with_address_space_workspace_has_no_address_spaces_property(self, create_workspace_service_item_mock, __, ___, get_workspace_mock, resource_template_repo, _____, app, client, workspace_service_input, basic_workspace_service_template, basic_resource_template):
etag = "some-etag-value"
workspace = sample_workspace()
get_workspace_mock.return_value = workspace
basic_workspace_service_template.properties["address_space"]: str = Field()
create_workspace_service_item_mock.return_value = [sample_workspace_service(), basic_workspace_service_template]
resource_template_repo.return_value = [basic_workspace_service_template, basic_resource_template]
response = await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_SERVICE, workspace_id=WORKSPACE_ID), json=workspace_service_input, headers={"etag": etag})

assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.text == strings.WORKSPACE_DOES_NOT_HAVE_ADDRESS_SPACES_PROPERTY

# [POST] /workspaces/{workspace_id}/workspace-services
@ patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id")
@ patch("api.routes.workspaces.OperationRepository.resource_has_deployed_operation", return_value=True)
@ patch("api.routes.workspaces.WorkspaceServiceRepository.create_workspace_service_item", side_effect=ValueError)
async def test_post_workspace_services_raises_400_bad_request_if_input_is_bad(self, _, __, get_workspace_mock, app, client, workspace_service_input):
auth_info_user_in_workspace_owner_role = {'sp_id': 'ab123', 'roles': {'WorkspaceOwner': 'ab124', 'WorkspaceResearcher': 'ab125'}}
etag = "some-etag-value"
workspace = sample_workspace(auth_info=auth_info_user_in_workspace_owner_role)
get_workspace_mock.return_value = workspace

response = await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_SERVICE, workspace_id=WORKSPACE_ID), json=workspace_service_input)
response = await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_SERVICE, workspace_id=WORKSPACE_ID), json=workspace_service_input, headers={"etag": etag})

assert response.status_code == status.HTTP_400_BAD_REQUEST

Expand Down