From afcc4d16cdef722d5afbdb8cc1e5df8395eac533 Mon Sep 17 00:00:00 2001 From: tomdds Date: Thu, 30 Jan 2020 15:57:45 -0500 Subject: [PATCH 1/3] Add functionality for creating and verifying subscriptions. Currently the create call will be consumed by on-demand requests from the frontend, and the 2 stage create will be used by the enviroment management group provisioning to verify an initial subscription was created. --- atst/domain/csp/cloud/azure_cloud_provider.py | 111 ++++++++++-------- .../csp/cloud/cloud_provider_interface.py | 6 - atst/domain/csp/cloud/mock_cloud_provider.py | 18 ++- atst/domain/csp/cloud/models.py | 43 +++++++ atst/models/portfolio_state_machine.py | 1 - atst/routes/applications/settings.py | 17 ++- tests/domain/cloud/test_azure_csp.py | 103 ++++++++++------ tests/routes/applications/test_settings.py | 84 +++++++++---- 8 files changed, 260 insertions(+), 123 deletions(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 7985f363..fd6f2060 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -1,5 +1,4 @@ import json -import re from secrets import token_urlsafe from typing import Any, Dict from uuid import uuid4 @@ -9,6 +8,10 @@ from atst.utils import sha256_hex from .cloud_provider_interface import CloudProviderInterface from .exceptions import AuthenticationException from .models import ( + SubscriptionCreationCSPPayload, + SubscriptionCreationCSPResult, + SubscriptionVerificationCSPPayload, + SuscriptionVerificationCSPResult, AdminRoleDefinitionCSPPayload, AdminRoleDefinitionCSPResult, ApplicationCSPPayload, @@ -44,10 +47,6 @@ from .models import ( ) from .policy import AzurePolicyManager -SUBSCRIPTION_ID_REGEX = re.compile( - "subscriptions\/([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})", - re.I, -) # This needs to be a fully pathed role definition identifier, not just a UUID # TODO: Extract these from sdk msrestazure.azure_cloud import AZURE_PUBLIC_CLOUD @@ -231,49 +230,6 @@ class AzureCloudProvider(CloudProviderInterface): # instead? return create_request.result() - def _create_subscription( - self, - credentials, - display_name, - billing_profile_id, - sku_id, - management_group_id, - billing_account_name, - invoice_section_name, - ): - sub_client = self.sdk.subscription.SubscriptionClient(credentials) - - billing_profile_id = "?" # where do we source this? - sku_id = AZURE_SKU_ID - # These 2 seem like something that might be worthwhile to allow tiebacks to - # TOs filed for the environment - billing_account_name = "?" # from TO? - invoice_section_name = "?" # from TO? - - body = self.sdk.subscription.models.ModernSubscriptionCreationParameters( - display_name=display_name, - billing_profile_id=billing_profile_id, - sku_id=sku_id, - management_group_id=management_group_id, - ) - - # We may also want to create billing sections in the enrollment account - sub_creation_operation = sub_client.subscription_factory.create_subscription( - billing_account_name, invoice_section_name, body - ) - - # the resulting object from this process is a link to the new subscription - # not a subscription model, so we'll have to unpack the ID - new_sub = sub_creation_operation.result() - - subscription_id = self._extract_subscription_id(new_sub.subscription_link) - if subscription_id: - return subscription_id - else: - # troublesome error, subscription should exist at this point - # but we just don't have a valid ID - pass - def _create_policy_definition( self, credentials, subscription_id, management_group_id, properties, ): @@ -517,6 +473,59 @@ class AzureCloudProvider(CloudProviderInterface): else: return self._error(result.json()) + def create_subscription(self, payload: SubscriptionCreationCSPPayload): + sp_token = self._get_tenant_principal_token(payload.tenant_id) + if sp_token is None: + raise AuthenticationException( + "Could not resolve token for subscription creation" + ) + + request_body = { + "displayName": "Test Sub 1", + "skuId": AZURE_SKU_ID, + "managementGroupId": payload.parent_group_id, + } + + url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}/invoiceSections/{payload.invoice_section_name}/providers/Microsoft.Subscription/createSubscription?api-version=2019-10-01-preview" + + auth_header = { + "Authorization": f"Bearer {sp_token}", + } + + result = self.sdk.requests.put(url, headers=auth_header, json=request_body) + + if result.status_code in [200, 202]: + # 202 has location/retry after headers + return SubscriptionCreationCSPResult(**result.headers, **result.json()) + else: + return self._error(result.json()) + + def create_subscription_creation(self, payload: SubscriptionCreationCSPPayload): + return self.create_subscription(payload) + + def create_subscription_verification( + self, payload: SubscriptionVerificationCSPPayload + ): + sp_token = self._get_tenant_principal_token(payload.tenant_id) + if sp_token is None: + raise AuthenticationException( + "Could not resolve token for subscription verification" + ) + + auth_header = { + "Authorization": f"Bearer {sp_token}", + } + + result = self.sdk.requests.get( + payload.subscription_verify_url, headers=auth_header + ) + + if result.ok: + # 202 has location/retry after headers + return SuscriptionVerificationCSPResult(**result.json()) + else: + return self._error(result.json()) + def create_tenant_admin_ownership(self, payload: TenantAdminOwnershipCSPPayload): mgmt_token = self._get_elevated_management_token(payload.tenant_id) @@ -861,6 +870,12 @@ class AzureCloudProvider(CloudProviderInterface): "tenant_id": self.tenant_id, } + def _get_tenant_principal_token(self, tenant_id): + creds = self._source_creds(tenant_id) + return self._get_sp_token( + creds.tenant_id, creds.tenant_sp_client_id, creds.tenant_sp_key + ) + def _get_elevated_management_token(self, tenant_id): mgmt_token = self._get_tenant_admin_token( tenant_id, self.sdk.cloud.endpoints.resource_manager diff --git a/atst/domain/csp/cloud/cloud_provider_interface.py b/atst/domain/csp/cloud/cloud_provider_interface.py index 5f4b9ab5..d173396a 100644 --- a/atst/domain/csp/cloud/cloud_provider_interface.py +++ b/atst/domain/csp/cloud/cloud_provider_interface.py @@ -112,9 +112,3 @@ class CloudProviderInterface: This may move to be a computed property on the Environment domain object """ raise NotImplementedError() - - def create_subscription(self, environment): - """Returns True if a new subscription has been created or raises an - exception if an error occurs while creating a subscription. - """ - raise NotImplementedError() diff --git a/atst/domain/csp/cloud/mock_cloud_provider.py b/atst/domain/csp/cloud/mock_cloud_provider.py index fcc9495a..414b0550 100644 --- a/atst/domain/csp/cloud/mock_cloud_provider.py +++ b/atst/domain/csp/cloud/mock_cloud_provider.py @@ -27,6 +27,8 @@ from .models import ( BillingProfileVerificationCSPResult, PrincipalAdminRoleCSPPayload, PrincipalAdminRoleCSPResult, + SubscriptionCreationCSPPayload, + SubscriptionVerificationCSPPayload, TaskOrderBillingCreationCSPPayload, TaskOrderBillingCreationCSPResult, TaskOrderBillingVerificationCSPPayload, @@ -109,6 +111,17 @@ class MockCloudProvider(CloudProviderInterface): return csp_environment_id + def create_subscription(self, payload: SubscriptionCreationCSPPayload): + pass + + def create_subscription_creation(self, payload: SubscriptionCreationCSPPayload): + pass + + def create_subscription_verification( + self, payload: SubscriptionVerificationCSPPayload + ): + pass + def create_atat_admin_user(self, auth_credentials, csp_environment_id): self._authorize(auth_credentials) @@ -382,11 +395,6 @@ class MockCloudProvider(CloudProviderInterface): return self._maybe(12) - def create_subscription(self, environment): - self._maybe_raise(self.UNAUTHORIZED_RATE, GeneralCSPException) - - return True - def get_calculator_url(self): return "https://www.rackspace.com/en-us/calculator" diff --git a/atst/domain/csp/cloud/models.py b/atst/domain/csp/cloud/models.py index 18c969d6..dbe0cf01 100644 --- a/atst/domain/csp/cloud/models.py +++ b/atst/domain/csp/cloud/models.py @@ -406,3 +406,46 @@ class KeyVaultCredentials(BaseModel): ) return values + + +class SubscriptionCreationCSPPayload(BaseCSPPayload): + parent_group_id: str + billing_account_name: str + billing_profile_name: str + invoice_section_name: str + + +class SubscriptionCreationCSPResult(AliasModel): + subscription_verify_url: str + subscription_retry_after: str + + class Config: + fields = { + "subscription_verify_url": "Location", + "subscription_retry_after": "Retry-After", + } + + +class SubscriptionVerificationCSPPayload(BaseCSPPayload): + subscription_verify_url: str + + +SUBSCRIPTION_ID_REGEX = re.compile( + "\/?subscriptions\/([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})", + re.I, +) + + +class SuscriptionVerificationCSPResult(AliasModel): + subscription_id: str + + @validator("subscription_id", pre=True, always=True) + def enforce_display_name_length(cls, sub_id): + sub_id_match = SUBSCRIPTION_ID_REGEX.match(sub_id) + if sub_id_match: + return sub_id_match.group(1) + + return False + + class Config: + fields = {"subscription_id": "subscriptionLink"} diff --git a/atst/models/portfolio_state_machine.py b/atst/models/portfolio_state_machine.py index 4b14a087..14e9c01d 100644 --- a/atst/models/portfolio_state_machine.py +++ b/atst/models/portfolio_state_machine.py @@ -140,7 +140,6 @@ class PortfolioStateMachine( # Accumulate payload w/ creds payload = event.kwargs.get("csp_data") - payload["creds"] = event.kwargs.get("creds") payload_data_cls = get_stage_csp_class(stage, "payload") if not payload_data_cls: diff --git a/atst/routes/applications/settings.py b/atst/routes/applications/settings.py index 443989db..7707eaed 100644 --- a/atst/routes/applications/settings.py +++ b/atst/routes/applications/settings.py @@ -14,6 +14,8 @@ from atst.domain.applications import Applications from atst.domain.application_roles import ApplicationRoles from atst.domain.audit_log import AuditLog from atst.domain.csp.cloud.exceptions import GeneralCSPException + +from atst.domain.csp.cloud.models import SubscriptionCreationCSPPayload from atst.domain.common import Paginator from atst.domain.environment_roles import EnvironmentRoles from atst.domain.invitations import ApplicationInvitations @@ -533,7 +535,20 @@ def create_subscription(environment_id): environment = Environments.get(environment_id) try: - app.csp.cloud.create_subscription(environment) + csp_data = environment.application.portfolio.csp_data + parent_group_id = environment.cloud_id + invoice_section_name = csp_data["billing_profile_properties"][ + "invoice_sections" + ][0]["invoice_section_name"] + + payload = SubscriptionCreationCSPPayload( + tenant_id=csp_data.get("tenant_id"), + parent_group_id=parent_group_id, + billing_account_name=csp_data.get("billing_account_name"), + billing_profile_name=csp_data.get("billing_profile_name"), + invoice_section_name=invoice_section_name, + ) + app.csp.cloud.create_subscription(payload) flash("environment_subscription_success", name=environment.displayname) except GeneralCSPException: diff --git a/tests/domain/cloud/test_azure_csp.py b/tests/domain/cloud/test_azure_csp.py index 67c03c9f..10bed1bb 100644 --- a/tests/domain/cloud/test_azure_csp.py +++ b/tests/domain/cloud/test_azure_csp.py @@ -12,7 +12,6 @@ from atst.domain.csp.cloud.models import ( AdminRoleDefinitionCSPResult, ApplicationCSPPayload, ApplicationCSPResult, - BaseCSPPayload, BillingInstructionCSPPayload, BillingInstructionCSPResult, BillingProfileCreationCSPPayload, @@ -21,6 +20,10 @@ from atst.domain.csp.cloud.models import ( BillingProfileTenantAccessCSPResult, BillingProfileVerificationCSPPayload, BillingProfileVerificationCSPResult, + SubscriptionCreationCSPPayload, + SubscriptionCreationCSPResult, + SubscriptionVerificationCSPPayload, + SuscriptionVerificationCSPResult, TaskOrderBillingCreationCSPPayload, TaskOrderBillingCreationCSPResult, TaskOrderBillingVerificationCSPPayload, @@ -42,40 +45,6 @@ from atst.domain.csp.cloud.models import ( BILLING_ACCOUNT_NAME = "52865e4c-52e8-5a6c-da6b-c58f0814f06f:7ea5de9d-b8ce-4901-b1c5-d864320c7b03_2019-05-31" -def test_create_subscription_succeeds(mock_azure: AzureCloudProvider): - environment = EnvironmentFactory.create() - - subscription_id = str(uuid4()) - - credentials = mock_azure._get_credential_obj(AUTH_CREDENTIALS) - display_name = "Test Subscription" - billing_profile_id = str(uuid4()) - sku_id = str(uuid4()) - management_group_id = ( - environment.cloud_id # environment.csp_details.management_group_id? - ) - billing_account_name = ( - "?" # environment.application.portfilio.csp_details.billing_account.name? - ) - invoice_section_name = "?" # environment.name? or something specific to billing? - - mock_azure.sdk.subscription.SubscriptionClient.return_value.subscription_factory.create_subscription.return_value.result.return_value.subscription_link = ( - f"subscriptions/{subscription_id}" - ) - - result = mock_azure._create_subscription( - credentials, - display_name, - billing_profile_id, - sku_id, - management_group_id, - billing_account_name, - invoice_section_name, - ) - - assert result == subscription_id - - def mock_management_group_create(mock_azure, spec_dict): mock_azure.sdk.managementgroups.ManagementGroupsAPI.return_value.management_groups.create_or_update.return_value.result.return_value = ( spec_dict @@ -119,7 +88,7 @@ def test_create_application_succeeds(mock_azure: AzureCloudProvider): tenant_id="1234", display_name=application.name, parent_id=str(uuid4()) ) - result = mock_azure.create_application(payload) + result: ApplicationCSPResult = mock_azure.create_application(payload) assert result.id == "Test Id" @@ -609,3 +578,65 @@ def test_create_tenant_principal_ownership(mock_azure: AzureCloudProvider): ) assert result.principal_owner_assignment_id == "id" + + +def test_create_subscription_creation(mock_azure: AzureCloudProvider): + with patch.object( + AzureCloudProvider, + "_get_tenant_principal_token", + wraps=mock_azure._get_tenant_principal_token, + ) as _get_tenant_principal_token: + _get_tenant_principal_token.return_value = "my fake token" + + mock_result = Mock() + mock_result.status_code = 202 + mock_result.headers = { + "Location": "https://verify.me", + "Retry-After": "10", + } + mock_result.json.return_value = {} + mock_azure.sdk.requests.put.return_value = mock_result + management_group_id = str(uuid4()) + payload = SubscriptionCreationCSPPayload( + **dict( + tenant_id="60ff9d34-82bf-4f21-b565-308ef0533435", + parent_group_id=management_group_id, + billing_account_name="7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31", + billing_profile_name="KQWI-W2SU-BG7-TGB", + invoice_section_name="6HMZ-2HLO-PJA-TGB", + ) + ) + + result: SubscriptionCreationCSPResult = mock_azure.create_subscription_creation( + payload + ) + + assert result.subscription_verify_url == "https://verify.me" + + +def test_create_subscription_verification(mock_azure: AzureCloudProvider): + with patch.object( + AzureCloudProvider, + "_get_tenant_principal_token", + wraps=mock_azure._get_tenant_principal_token, + ) as _get_tenant_principal_token: + _get_tenant_principal_token.return_value = "my fake token" + + mock_result = Mock() + mock_result.ok = True + mock_result.json.return_value = { + "subscriptionLink": "/subscriptions/60fbbb72-0516-4253-ab18-c92432ba3230" + } + mock_azure.sdk.requests.get.return_value = mock_result + + payload = SubscriptionVerificationCSPPayload( + **dict( + tenant_id="60ff9d34-82bf-4f21-b565-308ef0533435", + subscription_verify_url="https://verify.me", + ) + ) + + result: SuscriptionVerificationCSPResult = mock_azure.create_subscription_verification( + payload + ) + assert result.subscription_id == "60fbbb72-0516-4253-ab18-c92432ba3230" diff --git a/tests/routes/applications/test_settings.py b/tests/routes/applications/test_settings.py index 8f2595f2..14517149 100644 --- a/tests/routes/applications/test_settings.py +++ b/tests/routes/applications/test_settings.py @@ -1,35 +1,38 @@ -import uuid -from flask import url_for, get_flashed_messages -from unittest.mock import Mock import datetime -from werkzeug.datastructures import ImmutableMultiDict +import uuid +from unittest.mock import Mock, patch + import pytest - +from flask import get_flashed_messages, url_for from tests.factories import * +from tests.mock_azure import mock_azure +from tests.utils import captured_templates +from werkzeug.datastructures import ImmutableMultiDict -from atst.domain.applications import Applications +from atst.database import db from atst.domain.application_roles import ApplicationRoles +from atst.domain.applications import Applications +from atst.domain.common import Paginator +from atst.domain.csp.cloud.azure_cloud_provider import AzureCloudProvider +from atst.domain.csp.cloud.exceptions import GeneralCSPException +from atst.domain.csp.cloud.models import SubscriptionCreationCSPResult from atst.domain.environment_roles import EnvironmentRoles from atst.domain.invitations import ApplicationInvitations -from atst.domain.common import Paginator -from atst.domain.csp.cloud.exceptions import GeneralCSPException from atst.domain.permission_sets import PermissionSets -from atst.models.application_role import Status as ApplicationRoleStatus -from atst.models.environment_role import CSPRole, EnvironmentRole -from atst.models.permissions import Permissions from atst.forms.application import EditEnvironmentForm from atst.forms.application_member import UpdateMemberForm from atst.forms.data import ENV_ROLE_NO_ACCESS as NO_ACCESS +from atst.models.application_role import Status as ApplicationRoleStatus +from atst.models.environment_role import CSPRole, EnvironmentRole +from atst.models.permissions import Permissions from atst.routes.applications.settings import ( - filter_env_roles_form_data, filter_env_roles_data, + filter_env_roles_form_data, get_environments_obj_for_app, handle_create_member, handle_update_member, ) -from tests.utils import captured_templates - def test_updating_application_environments_success(client, user_session): portfolio = PortfolioFactory.create() @@ -779,22 +782,41 @@ def test_handle_update_member_with_error(set_g, monkeypatch, mock_logger): assert mock_logger.messages[-1] == exception -def test_create_subscription_success(client, user_session): +def test_create_subscription_success( + client, user_session, mock_azure: AzureCloudProvider +): environment = EnvironmentFactory.create() user_session(environment.portfolio.owner) - response = client.post( - url_for("applications.create_subscription", environment_id=environment.id), - ) + environment.cloud_id = "management/group/id" + environment.application.portfolio.csp_data = { + "billing_account_name": "xxxx-xxxx-xxx-xxx", + "billing_profile_name": "xxxxxxxxxxx:xxxxxxxxxxxxx_xxxxxx", + "tenant_id": "xxxxxxxxxxx-xxxxxxxxxx-xxxxxxx-xxxxx", + "billing_profile_properties": { + "invoice_sections": [{"invoice_section_name": "xxxx-xxxx-xxx-xxx"}] + }, + } - assert response.status_code == 302 - assert response.location == url_for( - "applications.settings", - application_id=environment.application.id, - _external=True, - fragment="application-environments", - _anchor="application-environments", - ) + with patch.object( + AzureCloudProvider, "create_subscription", wraps=mock_azure.create_subscription, + ) as create_subscription: + create_subscription.return_value = SubscriptionCreationCSPResult( + subscription_verify_url="", subscription_retry_after="" + ) + + response = client.post( + url_for("applications.create_subscription", environment_id=environment.id), + ) + + assert response.status_code == 302 + assert response.location == url_for( + "applications.settings", + application_id=environment.application.id, + _external=True, + fragment="application-environments", + _anchor="application-environments", + ) def test_create_subscription_failure(client, user_session, monkeypatch): @@ -809,6 +831,16 @@ def test_create_subscription_failure(client, user_session, monkeypatch): ) user_session(environment.portfolio.owner) + environment.cloud_id = "management/group/id" + environment.application.portfolio.csp_data = { + "billing_account_name": "xxxx-xxxx-xxx-xxx", + "billing_profile_name": "xxxxxxxxxxx:xxxxxxxxxxxxx_xxxxxx", + "tenant_id": "xxxxxxxxxxx-xxxxxxxxxx-xxxxxxx-xxxxx", + "billing_profile_properties": { + "invoice_sections": [{"invoice_section_name": "xxxx-xxxx-xxx-xxx"}] + }, + } + response = client.post( url_for("applications.create_subscription", environment_id=environment.id), ) From 9acbeeb824cb37306c2b7df472ef2834359b72eb Mon Sep 17 00:00:00 2001 From: tomdds Date: Fri, 31 Jan 2020 14:22:10 -0500 Subject: [PATCH 2/3] Add display name to subscription creation payload Also extracts environment -> subscription payload construction to it's own method. --- atst/domain/csp/cloud/azure_cloud_provider.py | 2 +- atst/domain/csp/cloud/models.py | 1 + atst/routes/applications/settings.py | 33 +++++++++++-------- tests/domain/cloud/test_azure_csp.py | 3 +- tests/routes/applications/test_settings.py | 2 +- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index fd6f2060..0e0511b8 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -481,7 +481,7 @@ class AzureCloudProvider(CloudProviderInterface): ) request_body = { - "displayName": "Test Sub 1", + "displayName": payload.display_name, "skuId": AZURE_SKU_ID, "managementGroupId": payload.parent_group_id, } diff --git a/atst/domain/csp/cloud/models.py b/atst/domain/csp/cloud/models.py index dbe0cf01..2fd422d4 100644 --- a/atst/domain/csp/cloud/models.py +++ b/atst/domain/csp/cloud/models.py @@ -409,6 +409,7 @@ class KeyVaultCredentials(BaseModel): class SubscriptionCreationCSPPayload(BaseCSPPayload): + display_name: str parent_group_id: str billing_account_name: str billing_profile_name: str diff --git a/atst/routes/applications/settings.py b/atst/routes/applications/settings.py index 7707eaed..8b744b04 100644 --- a/atst/routes/applications/settings.py +++ b/atst/routes/applications/settings.py @@ -527,6 +527,25 @@ def resend_invite(application_id, application_role_id): ) +def build_subscription_payload(environment) -> SubscriptionCreationCSPPayload: + csp_data = environment.application.portfolio.csp_data + parent_group_id = environment.cloud_id + invoice_section_name = csp_data["billing_profile_properties"]["invoice_sections"][ + 0 + ]["invoice_section_name"] + + display_name = f"{environment.application.name}-{environment.name}" + + return SubscriptionCreationCSPPayload( + tenant_id=csp_data.get("tenant_id"), + display_name=display_name, + parent_group_id=parent_group_id, + billing_account_name=csp_data.get("billing_account_name"), + billing_profile_name=csp_data.get("billing_profile_name"), + invoice_section_name=invoice_section_name, + ) + + @applications_bp.route( "/environments//add_subscription", methods=["POST"] ) @@ -535,19 +554,7 @@ def create_subscription(environment_id): environment = Environments.get(environment_id) try: - csp_data = environment.application.portfolio.csp_data - parent_group_id = environment.cloud_id - invoice_section_name = csp_data["billing_profile_properties"][ - "invoice_sections" - ][0]["invoice_section_name"] - - payload = SubscriptionCreationCSPPayload( - tenant_id=csp_data.get("tenant_id"), - parent_group_id=parent_group_id, - billing_account_name=csp_data.get("billing_account_name"), - billing_profile_name=csp_data.get("billing_profile_name"), - invoice_section_name=invoice_section_name, - ) + payload = build_subscription_payload(environment) app.csp.cloud.create_subscription(payload) flash("environment_subscription_success", name=environment.displayname) diff --git a/tests/domain/cloud/test_azure_csp.py b/tests/domain/cloud/test_azure_csp.py index 10bed1bb..0d84f2a4 100644 --- a/tests/domain/cloud/test_azure_csp.py +++ b/tests/domain/cloud/test_azure_csp.py @@ -592,7 +592,7 @@ def test_create_subscription_creation(mock_azure: AzureCloudProvider): mock_result.status_code = 202 mock_result.headers = { "Location": "https://verify.me", - "Retry-After": "10", + "Retry-After": 10, } mock_result.json.return_value = {} mock_azure.sdk.requests.put.return_value = mock_result @@ -600,6 +600,7 @@ def test_create_subscription_creation(mock_azure: AzureCloudProvider): payload = SubscriptionCreationCSPPayload( **dict( tenant_id="60ff9d34-82bf-4f21-b565-308ef0533435", + display_name="application_env_sub1", parent_group_id=management_group_id, billing_account_name="7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31", billing_profile_name="KQWI-W2SU-BG7-TGB", diff --git a/tests/routes/applications/test_settings.py b/tests/routes/applications/test_settings.py index 14517149..04d59f03 100644 --- a/tests/routes/applications/test_settings.py +++ b/tests/routes/applications/test_settings.py @@ -802,7 +802,7 @@ def test_create_subscription_success( AzureCloudProvider, "create_subscription", wraps=mock_azure.create_subscription, ) as create_subscription: create_subscription.return_value = SubscriptionCreationCSPResult( - subscription_verify_url="", subscription_retry_after="" + subscription_verify_url="https://zombo.com", subscription_retry_after=10 ) response = client.post( From d5e739ea684ce00030cb1fe495a7b39cfa2d5202 Mon Sep 17 00:00:00 2001 From: tomdds Date: Fri, 31 Jan 2020 14:28:08 -0500 Subject: [PATCH 3/3] Fill in subscription creation mocks --- atst/domain/csp/cloud/mock_cloud_provider.py | 20 +++++++++++++++++--- atst/domain/csp/cloud/models.py | 2 +- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/atst/domain/csp/cloud/mock_cloud_provider.py b/atst/domain/csp/cloud/mock_cloud_provider.py index 414b0550..7baa2003 100644 --- a/atst/domain/csp/cloud/mock_cloud_provider.py +++ b/atst/domain/csp/cloud/mock_cloud_provider.py @@ -28,7 +28,9 @@ from .models import ( PrincipalAdminRoleCSPPayload, PrincipalAdminRoleCSPResult, SubscriptionCreationCSPPayload, + SubscriptionCreationCSPResult, SubscriptionVerificationCSPPayload, + SuscriptionVerificationCSPResult, TaskOrderBillingCreationCSPPayload, TaskOrderBillingCreationCSPResult, TaskOrderBillingVerificationCSPPayload, @@ -112,15 +114,27 @@ class MockCloudProvider(CloudProviderInterface): return csp_environment_id def create_subscription(self, payload: SubscriptionCreationCSPPayload): - pass + return self.create_subscription_creation(payload) def create_subscription_creation(self, payload: SubscriptionCreationCSPPayload): - pass + self._maybe_raise(self.NETWORK_FAILURE_PCT, self.NETWORK_EXCEPTION) + self._maybe_raise(self.SERVER_FAILURE_PCT, self.SERVER_EXCEPTION) + self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION) + + return SubscriptionCreationCSPResult( + subscription_verify_url="https://zombo.com", subscription_retry_after=10 + ) def create_subscription_verification( self, payload: SubscriptionVerificationCSPPayload ): - pass + self._maybe_raise(self.NETWORK_FAILURE_PCT, self.NETWORK_EXCEPTION) + self._maybe_raise(self.SERVER_FAILURE_PCT, self.SERVER_EXCEPTION) + self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION) + + return SuscriptionVerificationCSPResult( + subscription_id="subscriptions/60fbbb72-0516-4253-ab18-c92432ba3230" + ) def create_atat_admin_user(self, auth_credentials, csp_environment_id): self._authorize(auth_credentials) diff --git a/atst/domain/csp/cloud/models.py b/atst/domain/csp/cloud/models.py index 2fd422d4..db3efa05 100644 --- a/atst/domain/csp/cloud/models.py +++ b/atst/domain/csp/cloud/models.py @@ -418,7 +418,7 @@ class SubscriptionCreationCSPPayload(BaseCSPPayload): class SubscriptionCreationCSPResult(AliasModel): subscription_verify_url: str - subscription_retry_after: str + subscription_retry_after: int class Config: fields = {