From c995b0963cb485a1692164cda448d9956552a06d Mon Sep 17 00:00:00 2001 From: Philip Kalinsky Date: Tue, 4 Feb 2020 13:50:06 -0500 Subject: [PATCH 01/10] state machine triggers for resuming progress from a failed state --- atst/models/mixins/state_machines.py | 8 ++++++++ atst/models/portfolio_state_machine.py | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/atst/models/mixins/state_machines.py b/atst/models/mixins/state_machines.py index a7edf268..3ce5193f 100644 --- a/atst/models/mixins/state_machines.py +++ b/atst/models/mixins/state_machines.py @@ -94,6 +94,14 @@ def _build_transitions(csp_stages): dest=compose_state(csp_stage, StageStates.FAILED), ) ) + transitions.append( + dict( + trigger="resume_progress_" + csp_stage.name.lower(), + source=compose_state(csp_stage, StageStates.FAILED), + dest=compose_state(csp_stage, StageStates.IN_PROGRESS), + conditions=["is_ready_resume_progress"], + ) + ) return states, transitions diff --git a/atst/models/portfolio_state_machine.py b/atst/models/portfolio_state_machine.py index 14e9c01d..b0e72c8d 100644 --- a/atst/models/portfolio_state_machine.py +++ b/atst/models/portfolio_state_machine.py @@ -122,6 +122,22 @@ class PortfolioStateMachine( ) self.fail_stage(stage) + elif self.current_state == FSMStates.FAILED: + # get the first trigger that starts with 'create_' + resume_progress_trigger = next( + filter( + lambda trigger: trigger.startswith("resume_progress_"), + self.machine.get_triggers(FSMStates.FAILED.name), + ), + None, + ) + if resume_progress_trigger: + self.trigger(resume_progress_trigger, **kwargs) + else: + app.logger.info( + f"could not locate 'resume progress trigger' for {self.__repr__()}" + ) + elif state_obj.is_CREATED: # the create trigger for the next stage should be in the available # triggers for the current state @@ -196,6 +212,13 @@ class PortfolioStateMachine( return True + def is_ready_resume_progress(self, event): + """ + This function guards advancing states from *_FAILED to *_IN_PROGRESS. + """ + + return True + @property def application_id(self): return None From 0d942c73fcd37b36a101578168bab47964d3b640 Mon Sep 17 00:00:00 2001 From: Philip Kalinsky Date: Tue, 4 Feb 2020 14:31:30 -0500 Subject: [PATCH 02/10] error handling in azure create_tenant method --- atst/domain/csp/cloud/azure_cloud_provider.py | 44 +++++++++++++++---- atst/models/portfolio_state_machine.py | 40 ++++++++++------- 2 files changed, 59 insertions(+), 25 deletions(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index d5ef5204..85feae24 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -6,7 +6,13 @@ from uuid import uuid4 from atst.utils import sha256_hex from .cloud_provider_interface import CloudProviderInterface -from .exceptions import AuthenticationException, UserProvisioningException +from .exceptions import ( + AuthenticationException, + UserProvisioningException, + ConnectionException, + UnknownServerException, +) + from .models import ( SubscriptionCreationCSPPayload, SubscriptionCreationCSPResult, @@ -83,7 +89,8 @@ class AzureSDKProvider(object): self.graphrbac = graphrbac self.credentials = credentials self.identity = identity - self.exceptions = exceptions + self.azure_exceptions = exceptions + self.requests_exceptions = requests.exceptions self.secrets = secrets self.requests = requests self.cloud = AZURE_PUBLIC_CLOUD @@ -116,7 +123,7 @@ class AzureCloudProvider(CloudProviderInterface): ) try: return secret_client.set_secret(secret_key, secret_value) - except self.exceptions.HttpResponseError: + except self.azure_exceptions.HttpResponseError: app.logger.error( f"Could not SET secret in Azure keyvault for key {secret_key}.", exc_info=1, @@ -129,7 +136,7 @@ class AzureCloudProvider(CloudProviderInterface): ) try: return secret_client.get_secret(secret_key).value - except self.exceptions.HttpResponseError: + except self.azure_exceptions.HttpResponseError: app.logger.error( f"Could not GET secret in Azure keyvault for key {secret_key}.", exc_info=1, @@ -292,11 +299,30 @@ class AzureCloudProvider(CloudProviderInterface): "Authorization": f"Bearer {sp_token}", } - result = self.sdk.requests.post( - f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.SignUp/createTenant?api-version=2020-01-01-preview", - json=create_tenant_body, - headers=create_tenant_headers, - ) + try: + result = self.sdk.requests.post( + f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.SignUp/createTenant?api-version=2020-01-01-preview", + json=create_tenant_body, + headers=create_tenant_headers, + timeout=30, + ) + + except self.requests_exceptions.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + + except self.requests_exceptions.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + + try: + response.raise_for_status() + except requests_exceptions.HTTPError: + raise UnknownServerException("azure application error creating tenant") if result.status_code == 200: result_dict = result.json() diff --git a/atst/models/portfolio_state_machine.py b/atst/models/portfolio_state_machine.py index b0e72c8d..f83a7bac 100644 --- a/atst/models/portfolio_state_machine.py +++ b/atst/models/portfolio_state_machine.py @@ -158,6 +158,7 @@ class PortfolioStateMachine( payload = event.kwargs.get("csp_data") payload_data_cls = get_stage_csp_class(stage, "payload") + if not payload_data_cls: app.logger.info(f"could not resolve payload data class for stage {stage}") self.fail_stage(stage) @@ -178,27 +179,34 @@ class PortfolioStateMachine( try: func_name = f"create_{stage}" response = getattr(self.csp, func_name)(payload_data) - if self.portfolio.csp_data is None: - self.portfolio.csp_data = {} - self.portfolio.csp_data.update(response.dict()) - db.session.add(self.portfolio) - db.session.commit() - except PydanticValidationError as exc: - app.logger.error( - f"Failed to cast response to valid result class {self.__repr__()}:", - exc_info=1, - ) - app.logger.info(exc.json()) - print(exc.json()) - app.logger.info(payload_data) - self.fail_stage(stage) except (ConnectionException, UnknownServerException) as exc: app.logger.error( f"CSP api call. Caught exception for {self.__repr__()}.", exc_info=1, ) self.fail_stage(stage) - self.finish_stage(stage) + if response.get("status") == "error": + self.fail_stage(stage) + + elif response.get("status") == "ok": + try: + + if self.portfolio.csp_data is None: + self.portfolio.csp_data = {} + self.portfolio.csp_data.update(response.dict()) + db.session.add(self.portfolio) + db.session.commit() + except PydanticValidationError as exc: + app.logger.error( + f"Failed to cast response to valid result class {self.__repr__()}:", + exc_info=1, + ) + app.logger.info(exc.json()) + print(exc.json()) + app.logger.info(payload_data) + self.fail_stage(stage) + + self.finish_stage(stage) def is_csp_data_valid(self, event): """ @@ -214,7 +222,7 @@ class PortfolioStateMachine( def is_ready_resume_progress(self, event): """ - This function guards advancing states from *_FAILED to *_IN_PROGRESS. + This function guards advancing states from FAILED to *_IN_PROGRESS. """ return True From 37eda0b95926b6a3917f8c29135b962ebf08c0a2 Mon Sep 17 00:00:00 2001 From: Philip Kalinsky Date: Wed, 5 Feb 2020 15:34:59 -0500 Subject: [PATCH 03/10] create_tenant error handling --- atst/domain/csp/cloud/azure_cloud_provider.py | 10 ++++------ tests/mock_azure.py | 6 ++++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 85feae24..1ee1ad1c 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -90,7 +90,6 @@ class AzureSDKProvider(object): self.credentials = credentials self.identity = identity self.azure_exceptions = exceptions - self.requests_exceptions = requests.exceptions self.secrets = secrets self.requests = requests self.cloud = AZURE_PUBLIC_CLOUD @@ -306,22 +305,21 @@ class AzureCloudProvider(CloudProviderInterface): headers=create_tenant_headers, timeout=30, ) + result.raise_for_status() - except self.requests_exceptions.ConnectionError: + except self.sdk.requests.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.requests_exceptions.Timeout: + except self.sdk.requests.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - try: - response.raise_for_status() - except requests_exceptions.HTTPError: + except self.sdk.requests.HTTPError: raise UnknownServerException("azure application error creating tenant") if result.status_code == 200: diff --git a/tests/mock_azure.py b/tests/mock_azure.py index ce85a396..f5cfba17 100644 --- a/tests/mock_azure.py +++ b/tests/mock_azure.py @@ -63,6 +63,11 @@ def mock_policy(): return Mock(spec=policy) +def mock_azure_exceptions(): + from azure.core import exceptions + + return Mock(spec=exceptions) + def mock_adal(): import adal @@ -99,6 +104,7 @@ class MockAzureSDK(object): self.graphrbac = mock_graphrbac() self.credentials = mock_credentials() self.identity = mock_identity() + self.azure_exceptions = mock_azure_exceptions() self.policy = mock_policy() self.secrets = mock_secrets() self.requests = mock_requests() From 50ab045353d8b3d6f2c1e9c11d01b18de04e33bd Mon Sep 17 00:00:00 2001 From: Philip Kalinsky Date: Thu, 6 Feb 2020 14:44:49 -0500 Subject: [PATCH 04/10] provisioning error handling applied to all requests calls --- atst/domain/csp/cloud/azure_cloud_provider.py | 633 ++++++++++++------ tests/domain/cloud/test_azure_csp.py | 79 +-- tests/mock_azure.py | 1 + 3 files changed, 484 insertions(+), 229 deletions(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 9d10c51d..9328d704 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -124,11 +124,7 @@ class AzureCloudProvider(CloudProviderInterface): ) try: return secret_client.set_secret(secret_key, secret_value) -<<<<<<< HEAD except self.azure_exceptions.HttpResponseError: -======= - except self.sdk.exceptions.HttpResponseError as exc: ->>>>>>> staging app.logger.error( f"Could not SET secret in Azure keyvault for key {secret_key}.", exc_info=1, @@ -145,11 +141,7 @@ class AzureCloudProvider(CloudProviderInterface): ) try: return secret_client.get_secret(secret_key).value -<<<<<<< HEAD except self.azure_exceptions.HttpResponseError: -======= - except self.sdk.exceptions.HttpResponseError: ->>>>>>> staging app.logger.error( f"Could not GET secret in Azure keyvault for key {secret_key}.", exc_info=1, @@ -288,23 +280,6 @@ class AzureCloudProvider(CloudProviderInterface): timeout=30, ) result.raise_for_status() - - except self.sdk.requests.ConnectionError: - app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, - ) - raise ConnectionException("connection error creating tenant") - - except self.sdk.requests.Timeout: - app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, - ) - raise ConnectionException("timout error creating tenant") - - except self.sdk.requests.HTTPError: - raise UnknownServerException("azure application error creating tenant") - - if result.status_code == 200: result_dict = result.json() tenant_id = result_dict.get("tenantId") tenant_admin_username = ( @@ -318,11 +293,20 @@ class AzureCloudProvider(CloudProviderInterface): tenant_admin_password=payload.password, ), ) - return self._ok( - TenantCSPResult(domain_name=payload.domain_name, **result_dict) + return TenantCSPResult(domain_name=payload.domain_name, **result_dict) + + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, ) - else: - return self._error(result.json()) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") def create_billing_profile_creation( self, payload: BillingProfileCreationCSPPayload @@ -341,20 +325,32 @@ class AzureCloudProvider(CloudProviderInterface): billing_account_create_url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles?api-version=2019-10-01-preview" - result = self.sdk.requests.post( - billing_account_create_url, - json=create_billing_account_body, - headers=create_billing_account_headers, - ) + try: + result = self.sdk.requests.post( + billing_account_create_url, + json=create_billing_account_body, + headers=create_billing_account_headers, + ) + result.raise_for_status() + if result.status_code == 202: + # 202 has location/retry after headers + return BillingProfileCreationCSPResult(**result.headers) + elif result.status_code == 200: + # NB: Swagger docs imply call can sometimes resolve immediately + return BillingProfileVerificationCSPResult(**result.json()) - if result.status_code == 202: - # 202 has location/retry after headers - return self._ok(BillingProfileCreationCSPResult(**result.headers)) - elif result.status_code == 200: - # NB: Swagger docs imply call can sometimes resolve immediately - return self._ok(BillingProfileVerificationCSPResult(**result.json())) - else: - return self._error(result.json()) + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") def create_billing_profile_verification( self, payload: BillingProfileVerificationCSPPayload @@ -368,18 +364,30 @@ class AzureCloudProvider(CloudProviderInterface): auth_header = { "Authorization": f"Bearer {sp_token}", } + try: + result = self.sdk.requests.get( + payload.billing_profile_verify_url, headers=auth_header + ) + result.raise_for_status() - result = self.sdk.requests.get( - payload.billing_profile_verify_url, headers=auth_header - ) + if result.status_code == 202: + # 202 has location/retry after headers + return BillingProfileCreationCSPResult(**result.headers) + elif result.status_code == 200: + return BillingProfileVerificationCSPResult(**result.json()) - if result.status_code == 202: - # 202 has location/retry after headers - return self._ok(BillingProfileCreationCSPResult(**result.headers)) - elif result.status_code == 200: - return self._ok(BillingProfileVerificationCSPResult(**result.json())) - else: - return self._error(result.json()) + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") def create_billing_profile_tenant_access( self, payload: BillingProfileTenantAccessCSPPayload @@ -398,12 +406,23 @@ class AzureCloudProvider(CloudProviderInterface): } url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}/createBillingRoleAssignment?api-version=2019-10-01-preview" + try: + result = self.sdk.requests.post(url, headers=headers, json=request_body) + if result.status_code == 201: + return BillingProfileTenantAccessCSPResult(**result.json()) - result = self.sdk.requests.post(url, headers=headers, json=request_body) - if result.status_code == 201: - return self._ok(BillingProfileTenantAccessCSPResult(**result.json())) - else: - return self._error(result.json()) + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") def create_task_order_billing_creation( self, payload: TaskOrderBillingCreationCSPPayload @@ -423,17 +442,30 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}?api-version=2019-10-01-preview" - result = self.sdk.requests.patch( - url, headers=request_headers, json=request_body - ) + try: + result = self.sdk.requests.patch( + url, headers=request_headers, json=request_body + ) + result.raise_for_status() - if result.status_code == 202: - # 202 has location/retry after headers - return self._ok(TaskOrderBillingCreationCSPResult(**result.headers)) - elif result.status_code == 200: - return self._ok(TaskOrderBillingVerificationCSPResult(**result.json())) - else: - return self._error(result.json()) + if result.status_code == 202: + # 202 has location/retry after headers + return TaskOrderBillingCreationCSPResult(**result.headers) + elif result.status_code == 200: + return TaskOrderBillingVerificationCSPResult(**result.json()) + + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") def create_task_order_billing_verification( self, payload: TaskOrderBillingVerificationCSPPayload @@ -448,17 +480,30 @@ class AzureCloudProvider(CloudProviderInterface): "Authorization": f"Bearer {sp_token}", } - result = self.sdk.requests.get( - payload.task_order_billing_verify_url, headers=auth_header - ) + try: + result = self.sdk.requests.get( + payload.task_order_billing_verify_url, headers=auth_header + ) + result.raise_for_status() - if result.status_code == 202: - # 202 has location/retry after headers - return self._ok(TaskOrderBillingCreationCSPResult(**result.headers)) - elif result.status_code == 200: - return self._ok(TaskOrderBillingVerificationCSPResult(**result.json())) - else: - return self._error(result.json()) + if result.status_code == 202: + # 202 has location/retry after headers + return TaskOrderBillingCreationCSPResult(**result.headers) + elif result.status_code == 200: + return TaskOrderBillingVerificationCSPResult(**result.json()) + + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") def create_billing_instruction(self, payload: BillingInstructionCSPPayload): sp_token = self._get_root_provisioning_token() @@ -481,12 +526,23 @@ class AzureCloudProvider(CloudProviderInterface): "Authorization": f"Bearer {sp_token}", } - result = self.sdk.requests.put(url, headers=auth_header, json=request_body) + try: + result = self.sdk.requests.put(url, headers=auth_header, json=request_body) + result.raise_for_status() + return BillingInstructionCSPResult(**result.json()) - if result.status_code == 200: - return self._ok(BillingInstructionCSPResult(**result.json())) - else: - return self._error(result.json()) + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") def create_subscription(self, payload: SubscriptionCreationCSPPayload): sp_token = self._get_tenant_principal_token(payload.tenant_id) @@ -507,13 +563,24 @@ class AzureCloudProvider(CloudProviderInterface): "Authorization": f"Bearer {sp_token}", } - result = self.sdk.requests.put(url, headers=auth_header, json=request_body) + try: + 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()) - 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()) + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") def create_subscription_creation(self, payload: SubscriptionCreationCSPPayload): return self.create_subscription(payload) @@ -531,15 +598,27 @@ class AzureCloudProvider(CloudProviderInterface): "Authorization": f"Bearer {sp_token}", } - result = self.sdk.requests.get( - payload.subscription_verify_url, headers=auth_header - ) + try: + result = self.sdk.requests.get( + payload.subscription_verify_url, headers=auth_header + ) + result.raise_for_status() - if result.ok: # 202 has location/retry after headers return SuscriptionVerificationCSPResult(**result.json()) - else: - return self._error(result.json()) + + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") def create_product_purchase(self, payload: ProductPurchaseCSPPayload): sp_token = self._get_root_provisioning_token() @@ -559,21 +638,33 @@ class AzureCloudProvider(CloudProviderInterface): } product_purchase_url = f"https://management.azure.com/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}/purchaseProduct?api-version=2019-10-01-preview" + try: + result = self.sdk.requests.post( + product_purchase_url, + json=create_product_purchase_body, + headers=create_product_purchase_headers, + ) + result.raise_for_status() - result = self.sdk.requests.post( - product_purchase_url, - json=create_product_purchase_body, - headers=create_product_purchase_headers, - ) + if result.status_code == 202: + # 202 has location/retry after headers + return ProductPurchaseCSPResult(**result.headers) + elif result.status_code == 200: + # NB: Swagger docs imply call can sometimes resolve immediately + return ProductPurchaseVerificationCSPResult(**result.json()) - if result.status_code == 202: - # 202 has location/retry after headers - return self._ok(ProductPurchaseCSPResult(**result.headers)) - elif result.status_code == 200: - # NB: Swagger docs imply call can sometimes resolve immediately - return self._ok(ProductPurchaseVerificationCSPResult(**result.json())) - else: - return self._error(result.json()) + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") def create_product_purchase_verification( self, payload: ProductPurchaseVerificationCSPPayload @@ -587,23 +678,33 @@ class AzureCloudProvider(CloudProviderInterface): auth_header = { "Authorization": f"Bearer {sp_token}", } + try: + result = self.sdk.requests.get( + payload.product_purchase_verify_url, headers=auth_header + ) + result.raise_for_status() - result = self.sdk.requests.get( - payload.product_purchase_verify_url, headers=auth_header - ) - - if result.status_code == 202: - # 202 has location/retry after headers - return self._ok(ProductPurchaseCSPResult(**result.headers)) - elif result.status_code == 200: - premium_purchase_date = result.json()["properties"]["purchaseDate"] - return self._ok( - ProductPurchaseVerificationCSPResult( + if result.status_code == 202: + # 202 has location/retry after headers + return ProductPurchaseCSPResult(**result.headers) + elif result.status_code == 200: + premium_purchase_date = result.json()["properties"]["purchaseDate"] + return ProductPurchaseVerificationCSPResult( premium_purchase_date=premium_purchase_date ) + + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, ) - else: - return self._error(result.json()) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") def create_tenant_admin_ownership(self, payload: TenantAdminOwnershipCSPPayload): mgmt_token = self._get_elevated_management_token(payload.tenant_id) @@ -625,11 +726,27 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Management/managementGroups/{payload.tenant_id}/providers/Microsoft.Authorization/roleAssignments/{assignment_guid}?api-version=2015-07-01" - response = self.sdk.requests.put(url, headers=auth_header, json=request_body) + try: + response = self.sdk.requests.put( + url, headers=auth_header, json=request_body + ) + response.raise_for_status() - if response.ok: return TenantAdminOwnershipCSPResult(**response.json()) + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") + def create_tenant_principal_ownership( self, payload: TenantPrincipalOwnershipCSPPayload ): @@ -653,11 +770,26 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Management/managementGroups/{payload.tenant_id}/providers/Microsoft.Authorization/roleAssignments/{assignment_guid}?api-version=2015-07-01" - response = self.sdk.requests.put(url, headers=auth_header, json=request_body) - - if response.ok: + try: + response = self.sdk.requests.put( + url, headers=auth_header, json=request_body + ) + response.raise_for_status() return TenantPrincipalOwnershipCSPResult(**response.json()) + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") + def create_tenant_principal_app(self, payload: TenantPrincipalAppCSPPayload): graph_token = self._get_tenant_admin_token( payload.tenant_id, self.graph_resource @@ -675,11 +807,26 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.graph_resource}/v1.0/applications" - response = self.sdk.requests.post(url, json=request_body, headers=auth_header) - - if response.ok: + try: + response = self.sdk.requests.post( + url, json=request_body, headers=auth_header + ) + response.raise_for_status() return TenantPrincipalAppCSPResult(**response.json()) + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") + def create_tenant_principal(self, payload: TenantPrincipalCSPPayload): graph_token = self._get_tenant_admin_token( payload.tenant_id, self.graph_resource @@ -697,11 +844,26 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.graph_resource}/beta/servicePrincipals" - response = self.sdk.requests.post(url, json=request_body, headers=auth_header) - - if response.ok: + try: + response = self.sdk.requests.post( + url, json=request_body, headers=auth_header + ) + response.raise_for_status() return TenantPrincipalCSPResult(**response.json()) + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") + def create_tenant_principal_credential( self, payload: TenantPrincipalCredentialCSPPayload ): @@ -723,9 +885,11 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.graph_resource}/v1.0/applications/{payload.principal_app_object_id}/addPassword" - response = self.sdk.requests.post(url, json=request_body, headers=auth_header) - - if response.ok: + try: + response = self.sdk.requests.post( + url, json=request_body, headers=auth_header + ) + response.raise_for_status() result = response.json() self.update_tenant_creds( payload.tenant_id, @@ -740,6 +904,19 @@ class AzureCloudProvider(CloudProviderInterface): principal_creds_established=True, ) + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") + def create_admin_role_definition(self, payload: AdminRoleDefinitionCSPPayload): graph_token = self._get_tenant_admin_token( payload.tenant_id, self.graph_resource @@ -754,23 +931,37 @@ class AzureCloudProvider(CloudProviderInterface): } url = f"{self.graph_resource}/beta/roleManagement/directory/roleDefinitions" + try: + response = self.sdk.requests.get(url, headers=auth_header) + response.raise_for_status() - response = self.sdk.requests.get(url, headers=auth_header) + result = response.json() + roleList = result.get("value") - result = response.json() - roleList = result.get("value") + DEFAULT_ADMIN_RD_ID = "794bb258-3e31-42ff-9ee4-731a72f62851" + admin_role_def_id = next( + ( + role.get("id") + for role in roleList + if role.get("displayName") == "Company Administrator" + ), + DEFAULT_ADMIN_RD_ID, + ) - DEFAULT_ADMIN_RD_ID = "794bb258-3e31-42ff-9ee4-731a72f62851" - admin_role_def_id = next( - ( - role.get("id") - for role in roleList - if role.get("displayName") == "Company Administrator" - ), - DEFAULT_ADMIN_RD_ID, - ) + return AdminRoleDefinitionCSPResult(admin_role_def_id=admin_role_def_id) - return AdminRoleDefinitionCSPResult(admin_role_def_id=admin_role_def_id) + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") def create_principal_admin_role(self, payload: PrincipalAdminRoleCSPPayload): graph_token = self._get_tenant_admin_token( @@ -793,16 +984,31 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.graph_resource}/beta/roleManagement/directory/roleAssignments" - response = self.sdk.requests.post(url, headers=auth_header, json=request_body) - - if response.ok: + try: + response = self.sdk.requests.post( + url, headers=auth_header, json=request_body + ) + response.raise_for_status() return PrincipalAdminRoleCSPResult(**response.json()) + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") + def force_tenant_admin_pw_update(self, creds, tenant_owner_id): # use creds to update to force password recovery? # not sure what the endpoint/method for this is, yet - return self._ok() + return {} def _get_management_service_principal(self): # we really should be using graph.microsoft.com, but i'm getting @@ -896,12 +1102,28 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.graph_resource}v1.0/users" - response = self.sdk.requests.post(url, headers=auth_header, json=request_body) + try: + response = self.sdk.requests.post( + url, headers=auth_header, json=request_body + ) + response.raise_for_status() - if response.ok: return UserCSPResult(**response.json()) - else: - raise UserProvisioningException(f"Failed to create user: {response.json()}") + + # raise UserProvisioningException(f"Failed to create user: {response.json()}") + + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") def _update_active_directory_user_email( self, graph_token, user_id, payload: UserCSPPayload @@ -914,14 +1136,31 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.graph_resource}v1.0/users/{user_id}" - response = self.sdk.requests.patch(url, headers=auth_header, json=request_body) - - if response.ok: - return True - else: - raise UserProvisioningException( - f"Failed update user email: {response.json()}" + try: + response = self.sdk.requests.patch( + url, headers=auth_header, json=request_body ) + response.raise_for_status() + + if response.ok: + return True + else: + raise UserProvisioningException( + f"Failed update user email: {response.json()}" + ) + + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") def _extract_subscription_id(self, subscription_url): sub_id_match = SUBSCRIPTION_ID_REGEX.match(subscription_url) @@ -987,24 +1226,6 @@ class AzureCloudProvider(CloudProviderInterface): client_secret=creds.root_sp_key, ) - def _ok(self, body=None): - return self._make_response("ok", body) - - def _error(self, body=None): - return self._make_response("error", body) - - def _make_response(self, status, body=dict()): - """Create body for responses from API - - Arguments: - status {string} -- "ok" or "error" - body {dict} -- dict containing details of response or error, if applicable - - Returns: - dict -- status of call with body containing details - """ - return {"status": status, "body": body} - @property def _root_creds(self): return { @@ -1035,12 +1256,26 @@ class AzureCloudProvider(CloudProviderInterface): "Authorization": f"Bearer {mgmt_token}", } url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Authorization/elevateAccess?api-version=2016-07-01" - result = self.sdk.requests.post(url, headers=auth_header) + try: + result = self.sdk.requests.post(url, headers=auth_header) + result.raise_for_status() + if not result.ok: + raise AuthenticationException("Failed to elevate access") - if not result.ok: - raise AuthenticationException("Failed to elevate access") + return mgmt_token - return mgmt_token + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") def _source_creds(self, tenant_id=None) -> KeyVaultCredentials: if tenant_id: @@ -1096,10 +1331,26 @@ class AzureCloudProvider(CloudProviderInterface): cost_mgmt_url = ( f"/providers/Microsoft.CostManagement/query?api-version=2019-11-01" ) - result = self.sdk.requests.post( - f"{self.sdk.cloud.endpoints.resource_manager}{payload.invoice_section_id}{cost_mgmt_url}", - json=request_body, - headers=headers, - ) - if result.ok: - return CostManagementQueryCSPResult(**result.json()) + + try: + result = self.sdk.requests.post( + f"{self.sdk.cloud.endpoints.resource_manager}{payload.invoice_section_id}{cost_mgmt_url}", + json=request_body, + headers=headers, + ) + result.raise_for_status() + if result.ok: + return CostManagementQueryCSPResult(**result.json()) + + except self.sdk.requests.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) + raise ConnectionException("connection error creating tenant") + except self.sdk.requests.Timeout: + app.logger.error( + f"Could not create tenant. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant") + except self.sdk.requests.HTTPError: + raise UnknownServerException("azure application error creating tenant") diff --git a/tests/domain/cloud/test_azure_csp.py b/tests/domain/cloud/test_azure_csp.py index 3a25f849..9724d6d2 100644 --- a/tests/domain/cloud/test_azure_csp.py +++ b/tests/domain/cloud/test_azure_csp.py @@ -154,30 +154,29 @@ def test_create_tenant(mock_azure: AzureCloudProvider): ) ) mock_azure = mock_get_secret(mock_azure) - result = mock_azure.create_tenant(payload) - body: TenantCSPResult = result.get("body") - assert body.tenant_id == "60ff9d34-82bf-4f21-b565-308ef0533435" + result: TenantCSPResult = mock_azure.create_tenant(payload) + assert result.tenant_id == "60ff9d34-82bf-4f21-b565-308ef0533435" -def test_create_tenant_fails(mock_azure: AzureCloudProvider): - mock_result = Mock() - mock_result.json.return_value = {"error": "body"} - mock_result.status_code = 403 - mock_azure.sdk.requests.post.return_value = mock_result - payload = TenantCSPPayload( - **dict( - user_id="admin", - password="JediJan13$coot", # pragma: allowlist secret - domain_name="jediccpospawnedtenant2", - first_name="Tedry", - last_name="Tenet", - country_code="US", - password_recovery_email_address="thomas@promptworks.com", - ) - ) - mock_azure = mock_get_secret(mock_azure) - result = mock_azure.create_tenant(payload) - assert result.get("status") == "error" +# def test_create_tenant_fails(mock_azure: AzureCloudProvider): +# mock_result = Mock() +# mock_result.status_code = 200 +# mock_azure.sdk.requests.post.return_value = mock_result +# payload = TenantCSPPayload( +# **dict( +# user_id="admin", +# password="JediJan13$coot", # pragma: allowlist secret +# domain_name="jediccpospawnedtenant2", +# first_name="Tedry", +# last_name="Tenet", +# country_code="US", +# password_recovery_email_address="thomas@promptworks.com", +# ) +# ) +# mock_azure = mock_get_secret(mock_azure) + +# with pytest.raises(pydantic.ValidationError): +# assert mock_azure.create_tenant(payload) def test_create_billing_profile_creation(mock_azure: AzureCloudProvider): @@ -207,8 +206,9 @@ def test_create_billing_profile_creation(mock_azure: AzureCloudProvider): billing_account_name=BILLING_ACCOUNT_NAME, ) ) - result = mock_azure.create_billing_profile_creation(payload) - body: BillingProfileCreationCSPResult = result.get("body") + body: BillingProfileCreationCSPResult = mock_azure.create_billing_profile_creation( + payload + ) assert body.billing_profile_retry_after == 10 @@ -257,8 +257,9 @@ def test_validate_billing_profile_creation(mock_azure: AzureCloudProvider): ) ) - result = mock_azure.create_billing_profile_verification(payload) - body: BillingProfileVerificationCSPResult = result.get("body") + body: BillingProfileVerificationCSPResult = mock_azure.create_billing_profile_verification( + payload + ) assert body.billing_profile_name == "KQWI-W2SU-BG7-TGB" assert ( body.billing_profile_properties.billing_profile_display_name @@ -298,8 +299,9 @@ def test_create_billing_profile_tenant_access(mock_azure: AzureCloudProvider): ) ) - result = mock_azure.create_billing_profile_tenant_access(payload) - body: BillingProfileTenantAccessCSPResult = result.get("body") + body: BillingProfileTenantAccessCSPResult = mock_azure.create_billing_profile_tenant_access( + payload + ) assert ( body.billing_role_assignment_name == "40000000-aaaa-bbbb-cccc-100000000000_0a5f4926-e3ee-4f47-a6e3-8b0a30a40e3d" @@ -328,8 +330,9 @@ def test_create_task_order_billing_creation(mock_azure: AzureCloudProvider): ) ) - result = mock_azure.create_task_order_billing_creation(payload) - body: TaskOrderBillingCreationCSPResult = result.get("body") + body: TaskOrderBillingCreationCSPResult = mock_azure.create_task_order_billing_creation( + payload + ) assert ( body.task_order_billing_verify_url @@ -388,8 +391,9 @@ def test_create_task_order_billing_verification(mock_azure): ) ) - result = mock_azure.create_task_order_billing_verification(payload) - body: TaskOrderBillingVerificationCSPResult = result.get("body") + body: TaskOrderBillingVerificationCSPResult = mock_azure.create_task_order_billing_verification( + payload + ) assert body.billing_profile_name == "KQWI-W2SU-BG7-TGB" assert ( body.billing_profile_enabled_plan_details.enabled_azure_plans[0].get("skuId") @@ -428,8 +432,7 @@ def test_create_billing_instruction(mock_azure: AzureCloudProvider): billing_profile_name="KQWI-W2SU-BG7-TGB", ) ) - result = mock_azure.create_billing_instruction(payload) - body: BillingInstructionCSPResult = result.get("body") + body: BillingInstructionCSPResult = mock_azure.create_billing_instruction(payload) assert body.reported_clin_name == "TO1:CLIN001" @@ -455,8 +458,7 @@ def test_create_product_purchase(mock_azure: AzureCloudProvider): ) ) - result = mock_azure.create_product_purchase(payload) - body: ProductPurchaseCSPResult = result.get("body") + body: ProductPurchaseCSPResult = mock_azure.create_product_purchase(payload) assert ( body.product_purchase_verify_url == "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/operationResults/patchBillingProfile_KQWI-W2SU-BG7-TGB:02715576-4118-466c-bca7-b1cd3169ff46?api-version=2019-10-01-preview" @@ -500,8 +502,9 @@ def test_create_product_purchase_verification(mock_azure): ) ) - result = mock_azure.create_product_purchase_verification(payload) - body: ProductPurchaseVerificationCSPResult = result.get("body") + body: ProductPurchaseVerificationCSPResult = mock_azure.create_product_purchase_verification( + payload + ) assert body.premium_purchase_date == "01/31/2020" diff --git a/tests/mock_azure.py b/tests/mock_azure.py index f5cfba17..4b3cd974 100644 --- a/tests/mock_azure.py +++ b/tests/mock_azure.py @@ -63,6 +63,7 @@ def mock_policy(): return Mock(spec=policy) + def mock_azure_exceptions(): from azure.core import exceptions From 441e81ba8d484c301a638c5e70034c54e4090658 Mon Sep 17 00:00:00 2001 From: Philip Kalinsky Date: Tue, 11 Feb 2020 14:11:11 -0500 Subject: [PATCH 05/10] azure csp unit tests WIP --- Pipfile | 1 + Pipfile.lock | 151 +++++++++--------- atst/domain/csp/cloud/azure_cloud_provider.py | 90 ++++++----- atst/domain/csp/cloud/mock_cloud_provider.py | 4 - atst/models/mixins/state_machines.py | 10 +- atst/models/portfolio_state_machine.py | 49 ++++-- tests/domain/cloud/test_azure_csp.py | 74 ++++++++- tests/domain/cloud/test_mock_csp.py | 47 +++++- tests/domain/test_portfolio_state_machine.py | 76 ++++++++- tests/mock_azure.py | 2 +- 10 files changed, 355 insertions(+), 149 deletions(-) diff --git a/Pipfile b/Pipfile index 810da811..8e5dc976 100644 --- a/Pipfile +++ b/Pipfile @@ -54,6 +54,7 @@ selenium = "*" honcho = "*" blinker = "*" pytest-mock = "*" +requests-mock = "*" detect-secrets = "*" beautifulsoup4 = "*" mypy = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 391f766d..d6032259 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "44296f145fcb42cff5fadf14a706ec9598f4436ccbdf05e1d69fcd8316c89e8d" + "sha256": "dcc985866bfecd1e2abcd40722a657f0da223c3c9c11aee32fd4b532eca31cb8" }, "pipfile-spec": 6, "requires": { @@ -26,10 +26,10 @@ }, "alembic": { "hashes": [ - "sha256:d412982920653db6e5a44bfd13b1d0db5685cbaaccaf226195749c706e1e862a" + "sha256:2df2519a5b002f881517693b95626905a39c5faf4b5a1f94de4f1441095d1d26" ], "index": "pypi", - "version": "==1.3.3" + "version": "==1.4.0" }, "amqp": { "hashes": [ @@ -47,10 +47,10 @@ }, "azure-core": { "hashes": [ - "sha256:b8ccbd901d085048e4e3e72627b066923c5bd3780e4c43cf9cf9948aee9bdf9e", - "sha256:e2cd99f0c0aef12c168d498cb5bc47a3a45c8ab08112183e3ec97e4dcb33ceb9" + "sha256:8bdb12b8e937c5bdf495faadf7741ea1958436e2d24c9c5ca7bd7a5ca7a9e42f", + "sha256:bcfc4502c4cfbdcbe82301119439f52542fa4a42dfc1dadd647bba8b01819823" ], - "version": "==1.2.1" + "version": "==1.2.2" }, "azure-graphrbac": { "hashes": [ @@ -116,11 +116,11 @@ }, "azure-mgmt-resource": { "hashes": [ - "sha256:455a10bbae15673c7879d7515b38e1548cb1a8982dd35029ab3192565262c573", - "sha256:c2ad10cab63999c0a88ee498bc36200ee7f6e6e5d4bf82712bde882eda11146f" + "sha256:a77707bad5551bd558da450045cd2f7097fb8cbaf68610a510a9e413f8a9cf3e", + "sha256:d90b7d8f237b71b54cfd06480dc1ecd7dac81b22301bf2f4ead98a53cf269b6a" ], "index": "pypi", - "version": "==8.0.0" + "version": "==8.0.1" }, "azure-mgmt-subscription": { "hashes": [ @@ -186,41 +186,36 @@ }, "cffi": { "hashes": [ - "sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42", - "sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04", - "sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5", - "sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54", - "sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba", - "sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57", - "sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396", - "sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12", - "sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97", - "sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43", - "sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db", - "sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3", - "sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b", - "sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579", - "sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346", - "sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159", - "sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652", - "sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e", - "sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a", - "sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506", - "sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f", - "sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d", - "sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c", - "sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20", - "sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858", - "sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc", - "sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a", - "sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3", - "sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e", - "sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410", - "sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25", - "sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b", - "sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d" + "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff", + "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b", + "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac", + "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0", + "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384", + "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26", + "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6", + "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b", + "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e", + "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd", + "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2", + "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66", + "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc", + "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8", + "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55", + "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4", + "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5", + "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d", + "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78", + "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa", + "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793", + "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f", + "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a", + "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f", + "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30", + "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f", + "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3", + "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c" ], - "version": "==1.13.2" + "version": "==1.14.0" }, "chardet": { "hashes": [ @@ -296,11 +291,11 @@ }, "flask-wtf": { "hashes": [ - "sha256:5d14d55cfd35f613d99ee7cba0fc3fbbe63ba02f544d349158c14ca15561cc36", - "sha256:d9a9e366b32dcbb98ef17228e76be15702cd2600675668bca23f63a7947fd5ac" + "sha256:57b3faf6fe5d6168bda0c36b0df1d05770f8e205e18332d0376ddb954d17aef2", + "sha256:d417e3a0008b5ba583da1763e4db0f55a1269d9dd91dcc3eb3c026d3c5dbd720" ], "index": "pypi", - "version": "==0.14.2" + "version": "==0.14.3" }, "idna": { "hashes": [ @@ -333,10 +328,10 @@ }, "jinja2": { "hashes": [ - "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", - "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49" + "sha256:c10142f819c2d22bdcd17548c46fa9b77cf4fda45097854c689666bf425e7484", + "sha256:c922560ac46888d47384de1dbdc3daaa2ea993af4b26a436dec31fa2c19ec668" ], - "version": "==2.11.1" + "version": "==3.0.0a1" }, "kombu": { "hashes": [ @@ -685,10 +680,10 @@ }, "zipp": { "hashes": [ - "sha256:ccc94ed0909b58ffe34430ea5451f07bc0c76467d7081619a454bf5c98b89e28", - "sha256:feae2f18633c32fc71f2de629bfb3bd3c9325cd4419642b1f1da42ee488d9b98" + "sha256:5c56e330306215cd3553342cfafc73dda2c60792384117893f3a83f8a1209f50", + "sha256:d65287feb793213ffe11c0f31b81602be31448f38aeb8ffc2eb286c4f6f6657e" ], - "version": "==2.1.0" + "version": "==2.2.0" } }, "develop": { @@ -699,14 +694,6 @@ ], "version": "==1.4.3" }, - "appnope": { - "hashes": [ - "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", - "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" - ], - "markers": "sys_platform == 'darwin'", - "version": "==0.1.0" - }, "astroid": { "hashes": [ "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a", @@ -871,10 +858,10 @@ }, "gitpython": { "hashes": [ - "sha256:9c2398ffc3dcb3c40b27324b316f08a4f93ad646d5a6328cafbb871aa79f5e42", - "sha256:c155c6a2653593ccb300462f6ef533583a913e17857cfef8fc617c246b6dc245" + "sha256:99c77677f31f255e130f3fed4c8e0eebb35f1a09df98ff965fff6774f71688cf", + "sha256:99cd0403cecd8a13b95d2e045b9fcaa7837137fcc5ec3105f2c413305d82c143" ], - "version": "==3.0.5" + "version": "==3.0.7" }, "honcho": { "hashes": [ @@ -944,10 +931,10 @@ }, "jinja2": { "hashes": [ - "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", - "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49" + "sha256:c10142f819c2d22bdcd17548c46fa9b77cf4fda45097854c689666bf425e7484", + "sha256:c922560ac46888d47384de1dbdc3daaa2ea993af4b26a436dec31fa2c19ec668" ], - "version": "==2.11.1" + "version": "==3.0.0a1" }, "lazy-object-proxy": { "hashes": [ @@ -1056,10 +1043,10 @@ }, "parso": { "hashes": [ - "sha256:1376bdc8cb81377ca481976933773295218a2df47d3e1182ba76d372b1acb128", - "sha256:597f36de5102a8db05ffdf7ecdc761838b86565a4a111604c6e78beaedf1b045" + "sha256:56b2105a80e9c4df49de85e125feb6be69f49920e121406f15e7acde6c9dfc57", + "sha256:951af01f61e6dccd04159042a0706a31ad437864ec6e25d0d7a96a9fbb9b0095" ], - "version": "==0.6.0" + "version": "==0.6.1" }, "pathspec": { "hashes": [ @@ -1158,11 +1145,11 @@ }, "pytest-flask": { "hashes": [ - "sha256:283730b469604ecb94caac28df99a40b7c785b828dd8d3323596718b51dfaeb2", - "sha256:d874781b622210d8c5d8061cdb091cb059fcb12203125110bd8e6f9256ccbf49" + "sha256:9001f6128c5c4a0d243ce46c117f3691052828d2faf39ac151b8388657dce447", + "sha256:cbd8c5b9f8f1b83e9c159ac4294964807c4934317a5fba181739ac15e1b823e6" ], "index": "pypi", - "version": "==0.15.0" + "version": "==0.15.1" }, "pytest-mock": { "hashes": [ @@ -1230,6 +1217,14 @@ "index": "pypi", "version": "==2.22.0" }, + "requests-mock": { + "hashes": [ + "sha256:510df890afe08d36eca5bb16b4aa6308a6f85e3159ad3013bac8b9de7bd5a010", + "sha256:88d3402dd8b3c69a9e4f9d3a73ad11b15920c6efd36bc27bf1f701cf4a8e4646" + ], + "index": "pypi", + "version": "==1.7.0" + }, "rope": { "hashes": [ "sha256:52423a7eebb5306a6d63bdc91a7c657db51ac9babfb8341c9a1440831ecf3203", @@ -1270,10 +1265,10 @@ }, "stevedore": { "hashes": [ - "sha256:01d9f4beecf0fbd070ddb18e5efb10567801ba7ef3ddab0074f54e3cd4e91730", - "sha256:e0739f9739a681c7a1fda76a102b65295e96a144ccdb552f2ae03c5f0abe8a14" + "sha256:18afaf1d623af5950cc0f7e75e70f917784c73b652a34a12d90b309451b5500b", + "sha256:a4e7dc759fb0f2e3e2f7d8ffe2358c19d45b9b8297f393ef1256858d82f69c9b" ], - "version": "==1.31.0" + "version": "==1.32.0" }, "text-unidecode": { "hashes": [ @@ -1361,10 +1356,10 @@ }, "zipp": { "hashes": [ - "sha256:ccc94ed0909b58ffe34430ea5451f07bc0c76467d7081619a454bf5c98b89e28", - "sha256:feae2f18633c32fc71f2de629bfb3bd3c9325cd4419642b1f1da42ee488d9b98" + "sha256:5c56e330306215cd3553342cfafc73dda2c60792384117893f3a83f8a1209f50", + "sha256:d65287feb793213ffe11c0f31b81602be31448f38aeb8ffc2eb286c4f6f6657e" ], - "version": "==2.1.0" + "version": "==2.2.0" } } } diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 9328d704..aa5022e9 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -2,6 +2,7 @@ import json from secrets import token_urlsafe from typing import Any, Dict from uuid import uuid4 +import pydantic from atst.utils import sha256_hex @@ -274,31 +275,17 @@ class AzureCloudProvider(CloudProviderInterface): try: result = self.sdk.requests.post( - f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.SignUp/createTenant?api-version=2020-01-01-preview", + f"{self.sdk.cloud.endpoints.resource_manager}providers/Microsoft.SignUp/createTenant?api-version=2020-01-01-preview", json=create_tenant_body, headers=create_tenant_headers, timeout=30, ) result.raise_for_status() - result_dict = result.json() - tenant_id = result_dict.get("tenantId") - tenant_admin_username = ( - f"{payload.user_id}@{payload.domain_name}.onmicrosoft.com" - ) - self.update_tenant_creds( - tenant_id, - KeyVaultCredentials( - tenant_id=tenant_id, - tenant_admin_username=tenant_admin_username, - tenant_admin_password=payload.password, - ), - ) - return TenantCSPResult(domain_name=payload.domain_name, **result_dict) except self.sdk.requests.ConnectionError: - app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, - ) + #app.logger.error( + # f"Could not create tenant. Connection Error", exc_info=1, + #) raise ConnectionException("connection error creating tenant") except self.sdk.requests.Timeout: app.logger.error( @@ -308,6 +295,27 @@ class AzureCloudProvider(CloudProviderInterface): except self.sdk.requests.HTTPError: raise UnknownServerException("azure application error creating tenant") + result_dict = result.json() + tenant_id = result_dict.get("tenantId") + tenant_admin_username = ( + f"{payload.user_id}@{payload.domain_name}.onmicrosoft.com" + ) + try: + creds = KeyVaultCredentials( + tenant_id=tenant_id, + tenant_admin_username=tenant_admin_username, + tenant_admin_password=payload.password, + ) + except pydantic.ValidationError as val_exc: + return + + self.update_tenant_creds( + tenant_id, + creds, + ) + return TenantCSPResult(domain_name=payload.domain_name, **result_dict) + + def create_billing_profile_creation( self, payload: BillingProfileCreationCSPPayload ): @@ -330,6 +338,7 @@ class AzureCloudProvider(CloudProviderInterface): billing_account_create_url, json=create_billing_account_body, headers=create_billing_account_headers, + timeout=30, ) result.raise_for_status() if result.status_code == 202: @@ -366,7 +375,9 @@ class AzureCloudProvider(CloudProviderInterface): } try: result = self.sdk.requests.get( - payload.billing_profile_verify_url, headers=auth_header + payload.billing_profile_verify_url, + headers=auth_header, + timeout=30, ) result.raise_for_status() @@ -407,7 +418,12 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}/createBillingRoleAssignment?api-version=2019-10-01-preview" try: - result = self.sdk.requests.post(url, headers=headers, json=request_body) + result = self.sdk.requests.post( + url, + headers=headers, + json=request_body, + timeout=30, + ) if result.status_code == 201: return BillingProfileTenantAccessCSPResult(**result.json()) @@ -444,7 +460,7 @@ class AzureCloudProvider(CloudProviderInterface): try: result = self.sdk.requests.patch( - url, headers=request_headers, json=request_body + url, headers=request_headers, json=request_body, timeout=30, ) result.raise_for_status() @@ -482,7 +498,7 @@ class AzureCloudProvider(CloudProviderInterface): try: result = self.sdk.requests.get( - payload.task_order_billing_verify_url, headers=auth_header + payload.task_order_billing_verify_url, headers=auth_header, timeout=30, ) result.raise_for_status() @@ -527,7 +543,7 @@ class AzureCloudProvider(CloudProviderInterface): } try: - result = self.sdk.requests.put(url, headers=auth_header, json=request_body) + result = self.sdk.requests.put(url, headers=auth_header, json=request_body, timeout=30) result.raise_for_status() return BillingInstructionCSPResult(**result.json()) @@ -564,7 +580,7 @@ class AzureCloudProvider(CloudProviderInterface): } try: - result = self.sdk.requests.put(url, headers=auth_header, json=request_body) + result = self.sdk.requests.put(url, headers=auth_header, json=request_body, timeout=30) if result.status_code in [200, 202]: # 202 has location/retry after headers return SubscriptionCreationCSPResult(**result.headers, **result.json()) @@ -600,7 +616,7 @@ class AzureCloudProvider(CloudProviderInterface): try: result = self.sdk.requests.get( - payload.subscription_verify_url, headers=auth_header + payload.subscription_verify_url, headers=auth_header, timeout=30 ) result.raise_for_status() @@ -643,6 +659,7 @@ class AzureCloudProvider(CloudProviderInterface): product_purchase_url, json=create_product_purchase_body, headers=create_product_purchase_headers, + timeout=30, ) result.raise_for_status() @@ -680,7 +697,7 @@ class AzureCloudProvider(CloudProviderInterface): } try: result = self.sdk.requests.get( - payload.product_purchase_verify_url, headers=auth_header + payload.product_purchase_verify_url, headers=auth_header, timeout=30 ) result.raise_for_status() @@ -728,7 +745,7 @@ class AzureCloudProvider(CloudProviderInterface): try: response = self.sdk.requests.put( - url, headers=auth_header, json=request_body + url, headers=auth_header, json=request_body, timeout=30 ) response.raise_for_status() @@ -772,7 +789,7 @@ class AzureCloudProvider(CloudProviderInterface): try: response = self.sdk.requests.put( - url, headers=auth_header, json=request_body + url, headers=auth_header, json=request_body, timeout=30, ) response.raise_for_status() return TenantPrincipalOwnershipCSPResult(**response.json()) @@ -809,7 +826,7 @@ class AzureCloudProvider(CloudProviderInterface): try: response = self.sdk.requests.post( - url, json=request_body, headers=auth_header + url, json=request_body, headers=auth_header, timeout=30 ) response.raise_for_status() return TenantPrincipalAppCSPResult(**response.json()) @@ -846,7 +863,7 @@ class AzureCloudProvider(CloudProviderInterface): try: response = self.sdk.requests.post( - url, json=request_body, headers=auth_header + url, json=request_body, headers=auth_header, timeout=30 ) response.raise_for_status() return TenantPrincipalCSPResult(**response.json()) @@ -887,7 +904,7 @@ class AzureCloudProvider(CloudProviderInterface): try: response = self.sdk.requests.post( - url, json=request_body, headers=auth_header + url, json=request_body, headers=auth_header, timeout=30 ) response.raise_for_status() result = response.json() @@ -932,7 +949,7 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.graph_resource}/beta/roleManagement/directory/roleDefinitions" try: - response = self.sdk.requests.get(url, headers=auth_header) + response = self.sdk.requests.get(url, headers=auth_header, timeout=30) response.raise_for_status() result = response.json() @@ -986,7 +1003,7 @@ class AzureCloudProvider(CloudProviderInterface): try: response = self.sdk.requests.post( - url, headers=auth_header, json=request_body + url, headers=auth_header, json=request_body, timeout=30 ) response.raise_for_status() return PrincipalAdminRoleCSPResult(**response.json()) @@ -1104,7 +1121,7 @@ class AzureCloudProvider(CloudProviderInterface): try: response = self.sdk.requests.post( - url, headers=auth_header, json=request_body + url, headers=auth_header, json=request_body, timeout=30 ) response.raise_for_status() @@ -1138,7 +1155,7 @@ class AzureCloudProvider(CloudProviderInterface): try: response = self.sdk.requests.patch( - url, headers=auth_header, json=request_body + url, headers=auth_header, json=request_body, timeout=30 ) response.raise_for_status() @@ -1257,7 +1274,7 @@ class AzureCloudProvider(CloudProviderInterface): } url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Authorization/elevateAccess?api-version=2016-07-01" try: - result = self.sdk.requests.post(url, headers=auth_header) + result = self.sdk.requests.post(url, headers=auth_header, timeout=30) result.raise_for_status() if not result.ok: raise AuthenticationException("Failed to elevate access") @@ -1337,6 +1354,7 @@ class AzureCloudProvider(CloudProviderInterface): f"{self.sdk.cloud.endpoints.resource_manager}{payload.invoice_section_id}{cost_mgmt_url}", json=request_body, headers=headers, + timeout=30, ) result.raise_for_status() if result.ok: diff --git a/atst/domain/csp/cloud/mock_cloud_provider.py b/atst/domain/csp/cloud/mock_cloud_provider.py index 7ec0636f..2d646145 100644 --- a/atst/domain/csp/cloud/mock_cloud_provider.py +++ b/atst/domain/csp/cloud/mock_cloud_provider.py @@ -122,10 +122,6 @@ class MockCloudProvider(CloudProviderInterface): payload is an instance of TenantCSPPayload data class """ - self._authorize("admin") - - self._delay(1, 5) - 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) diff --git a/atst/models/mixins/state_machines.py b/atst/models/mixins/state_machines.py index 3ce5193f..5741476d 100644 --- a/atst/models/mixins/state_machines.py +++ b/atst/models/mixins/state_machines.py @@ -123,15 +123,21 @@ class FSMMixin: ] def fail_stage(self, stage): - fail_trigger = "fail" + stage + fail_trigger = f"fail_{stage}" + if fail_trigger in self.machine.get_triggers(self.current_state.name): self.trigger(fail_trigger) app.logger.info( f"calling fail trigger '{fail_trigger}' for '{self.__repr__()}'" ) + else: + app.logger.info( + f"could not locate fail trigger '{fail_trigger}' for '{self.__repr__()}'" + ) + def finish_stage(self, stage): - finish_trigger = "finish_" + stage + finish_trigger = f"finish_{stage}" if finish_trigger in self.machine.get_triggers(self.current_state.name): app.logger.info( f"calling finish trigger '{finish_trigger}' for '{self.__repr__()}'" diff --git a/atst/models/portfolio_state_machine.py b/atst/models/portfolio_state_machine.py index d8cc8ec8..d4fff482 100644 --- a/atst/models/portfolio_state_machine.py +++ b/atst/models/portfolio_state_machine.py @@ -15,13 +15,31 @@ from atst.database import db from atst.models.types import Id from atst.models.base import Base import atst.models.mixins as mixins -from atst.models.mixins.state_machines import FSMStates, AzureStages, _build_transitions +from atst.models.mixins.state_machines import ( + FSMStates, + AzureStages, + StageStates, + _build_transitions +) + + +class StateMachineMisconfiguredError(Exception): + def __init__(self, class_details): + self.class_details = class_details + + @property + def message(self): + return self.class_details def _stage_to_classname(stage): return "".join(map(lambda word: word.capitalize(), stage.split("_"))) +def _stage_state_to_stage_name(state, stage_state): + return state.name.split(f"_{stage_state.name}")[0].lower() + + def get_stage_csp_class(stage, class_type): """ given a stage name and class_type return the class @@ -34,7 +52,7 @@ def get_stage_csp_class(stage, class_type): importlib.import_module("atst.domain.csp.cloud.models"), cls_name ) except AttributeError: - print("could not import CSP Result class <%s>" % cls_name) + raise StateMachineMisconfiguredError(f"could not import CSP Payload/Result class {cls_name}") @add_state_features(Tags) @@ -74,7 +92,7 @@ class PortfolioStateMachine( return f" Date: Tue, 11 Feb 2020 15:32:47 -0500 Subject: [PATCH 06/10] azure csp tests catch exceptions resulting from raised requests exceptions --- Pipfile | 1 - Pipfile.lock | 28 +- atst/domain/csp/cloud/azure_cloud_provider.py | 172 ++++---- atst/models/mixins/state_machines.py | 1 - atst/models/portfolio_state_machine.py | 17 +- tests/domain/cloud/test_azure_csp.py | 381 +++++++++++++----- tests/domain/cloud/test_mock_csp.py | 1 - tests/domain/test_portfolio_state_machine.py | 74 +++- tests/mock_azure.py | 4 +- 9 files changed, 440 insertions(+), 239 deletions(-) diff --git a/Pipfile b/Pipfile index 8e5dc976..810da811 100644 --- a/Pipfile +++ b/Pipfile @@ -54,7 +54,6 @@ selenium = "*" honcho = "*" blinker = "*" pytest-mock = "*" -requests-mock = "*" detect-secrets = "*" beautifulsoup4 = "*" mypy = "*" diff --git a/Pipfile.lock b/Pipfile.lock index d6032259..f34446d9 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "dcc985866bfecd1e2abcd40722a657f0da223c3c9c11aee32fd4b532eca31cb8" + "sha256": "44296f145fcb42cff5fadf14a706ec9598f4436ccbdf05e1d69fcd8316c89e8d" }, "pipfile-spec": 6, "requires": { @@ -62,11 +62,11 @@ }, "azure-identity": { "hashes": [ - "sha256:4ce65058461c277991763ed3f121efc6b9eb9c2edefb62c414dfa85c814690d3", - "sha256:b32acd1cdb6202bfe10d9a0858dc463d8960295da70ae18097eb3b85ab12cb91" + "sha256:17fa904e0447fd2a2dc19909379edb769b05656dbaf4863b8c4fdfb2bb54350c", + "sha256:7e9c85e3f82f1e29e5edfc7beb3030b25e8b8fd02b65d5ea1c67f13cde01da0f" ], "index": "pypi", - "version": "==1.2.0" + "version": "==1.3.0" }, "azure-keyvault": { "hashes": [ @@ -78,17 +78,17 @@ }, "azure-keyvault-keys": { "hashes": [ - "sha256:2983fa42e20a0e6bf6b87976716129c108e613e0292d34c5b0f0c8dc1d488e89", - "sha256:38c27322637a2c52620a8b96da1942ad6a8d22d09b5a01f6fa257f7a51e52ed0" + "sha256:1c230c052b9f0b9ecaee97347fe4ebf3fcc798f92edbfd618ea264efc61ad554", + "sha256:711af402a0000ac329406253470c1198cc452adc8638608461ae54c8dce92afc" ], - "version": "==4.0.0" + "version": "==4.0.1" }, "azure-keyvault-secrets": { "hashes": [ - "sha256:2eae9264a8f6f59277e1a9bfdbc8b0a15969ee5a80d8efe403d7744805b4a481", - "sha256:97a602406a833e8f117c540c66059c818f4321a35168dd17365fab1e4527d718" + "sha256:0afd85eaa94962fc8ad9e71348ba0873d723d531163894ee3175f138c3180fe4", + "sha256:0f92705444f55ca0d5a892172eda898f42678602834f03fa8012e9979e5fe619" ], - "version": "==4.0.0" + "version": "==4.0.1" }, "azure-mgmt-authorization": { "hashes": [ @@ -1217,14 +1217,6 @@ "index": "pypi", "version": "==2.22.0" }, - "requests-mock": { - "hashes": [ - "sha256:510df890afe08d36eca5bb16b4aa6308a6f85e3159ad3013bac8b9de7bd5a010", - "sha256:88d3402dd8b3c69a9e4f9d3a73ad11b15920c6efd36bc27bf1f701cf4a8e4646" - ], - "index": "pypi", - "version": "==1.7.0" - }, "rope": { "hashes": [ "sha256:52423a7eebb5306a6d63bdc91a7c657db51ac9babfb8341c9a1440831ecf3203", diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index aa5022e9..7ab53806 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -3,6 +3,7 @@ from secrets import token_urlsafe from typing import Any, Dict from uuid import uuid4 import pydantic +from flask import current_app as app from atst.utils import sha256_hex @@ -281,18 +282,17 @@ class AzureCloudProvider(CloudProviderInterface): timeout=30, ) result.raise_for_status() - - except self.sdk.requests.ConnectionError: - #app.logger.error( - # f"Could not create tenant. Connection Error", exc_info=1, - #) + except self.sdk.requests.exceptions.ConnectionError: + app.logger.error( + f"Could not create tenant. Connection Error", exc_info=1, + ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") result_dict = result.json() @@ -300,22 +300,17 @@ class AzureCloudProvider(CloudProviderInterface): tenant_admin_username = ( f"{payload.user_id}@{payload.domain_name}.onmicrosoft.com" ) - try: - creds = KeyVaultCredentials( - tenant_id=tenant_id, - tenant_admin_username=tenant_admin_username, - tenant_admin_password=payload.password, - ) - except pydantic.ValidationError as val_exc: - return self.update_tenant_creds( tenant_id, - creds, + KeyVaultCredentials( + tenant_id=tenant_id, + tenant_admin_username=tenant_admin_username, + tenant_admin_password=payload.password, + ), ) return TenantCSPResult(domain_name=payload.domain_name, **result_dict) - def create_billing_profile_creation( self, payload: BillingProfileCreationCSPPayload ): @@ -348,17 +343,17 @@ class AzureCloudProvider(CloudProviderInterface): # NB: Swagger docs imply call can sometimes resolve immediately return BillingProfileVerificationCSPResult(**result.json()) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def create_billing_profile_verification( @@ -375,9 +370,7 @@ class AzureCloudProvider(CloudProviderInterface): } try: result = self.sdk.requests.get( - payload.billing_profile_verify_url, - headers=auth_header, - timeout=30, + payload.billing_profile_verify_url, headers=auth_header, timeout=30, ) result.raise_for_status() @@ -387,17 +380,17 @@ class AzureCloudProvider(CloudProviderInterface): elif result.status_code == 200: return BillingProfileVerificationCSPResult(**result.json()) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def create_billing_profile_tenant_access( @@ -419,25 +412,22 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}/createBillingRoleAssignment?api-version=2019-10-01-preview" try: result = self.sdk.requests.post( - url, - headers=headers, - json=request_body, - timeout=30, + url, headers=headers, json=request_body, timeout=30, ) if result.status_code == 201: return BillingProfileTenantAccessCSPResult(**result.json()) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def create_task_order_billing_creation( @@ -470,17 +460,17 @@ class AzureCloudProvider(CloudProviderInterface): elif result.status_code == 200: return TaskOrderBillingVerificationCSPResult(**result.json()) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def create_task_order_billing_verification( @@ -508,17 +498,17 @@ class AzureCloudProvider(CloudProviderInterface): elif result.status_code == 200: return TaskOrderBillingVerificationCSPResult(**result.json()) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def create_billing_instruction(self, payload: BillingInstructionCSPPayload): @@ -543,21 +533,23 @@ class AzureCloudProvider(CloudProviderInterface): } try: - result = self.sdk.requests.put(url, headers=auth_header, json=request_body, timeout=30) + result = self.sdk.requests.put( + url, headers=auth_header, json=request_body, timeout=30 + ) result.raise_for_status() return BillingInstructionCSPResult(**result.json()) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def create_subscription(self, payload: SubscriptionCreationCSPPayload): @@ -580,22 +572,24 @@ class AzureCloudProvider(CloudProviderInterface): } try: - result = self.sdk.requests.put(url, headers=auth_header, json=request_body, timeout=30) + result = self.sdk.requests.put( + url, headers=auth_header, json=request_body, timeout=30 + ) if result.status_code in [200, 202]: # 202 has location/retry after headers return SubscriptionCreationCSPResult(**result.headers, **result.json()) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def create_subscription_creation(self, payload: SubscriptionCreationCSPPayload): @@ -623,17 +617,17 @@ class AzureCloudProvider(CloudProviderInterface): # 202 has location/retry after headers return SuscriptionVerificationCSPResult(**result.json()) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def create_product_purchase(self, payload: ProductPurchaseCSPPayload): @@ -670,17 +664,17 @@ class AzureCloudProvider(CloudProviderInterface): # NB: Swagger docs imply call can sometimes resolve immediately return ProductPurchaseVerificationCSPResult(**result.json()) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def create_product_purchase_verification( @@ -710,17 +704,17 @@ class AzureCloudProvider(CloudProviderInterface): premium_purchase_date=premium_purchase_date ) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def create_tenant_admin_ownership(self, payload: TenantAdminOwnershipCSPPayload): @@ -751,17 +745,17 @@ class AzureCloudProvider(CloudProviderInterface): return TenantAdminOwnershipCSPResult(**response.json()) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def create_tenant_principal_ownership( @@ -794,17 +788,17 @@ class AzureCloudProvider(CloudProviderInterface): response.raise_for_status() return TenantPrincipalOwnershipCSPResult(**response.json()) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def create_tenant_principal_app(self, payload: TenantPrincipalAppCSPPayload): @@ -831,17 +825,17 @@ class AzureCloudProvider(CloudProviderInterface): response.raise_for_status() return TenantPrincipalAppCSPResult(**response.json()) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def create_tenant_principal(self, payload: TenantPrincipalCSPPayload): @@ -868,17 +862,17 @@ class AzureCloudProvider(CloudProviderInterface): response.raise_for_status() return TenantPrincipalCSPResult(**response.json()) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def create_tenant_principal_credential( @@ -921,17 +915,17 @@ class AzureCloudProvider(CloudProviderInterface): principal_creds_established=True, ) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def create_admin_role_definition(self, payload: AdminRoleDefinitionCSPPayload): @@ -967,17 +961,17 @@ class AzureCloudProvider(CloudProviderInterface): return AdminRoleDefinitionCSPResult(admin_role_def_id=admin_role_def_id) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def create_principal_admin_role(self, payload: PrincipalAdminRoleCSPPayload): @@ -1008,17 +1002,17 @@ class AzureCloudProvider(CloudProviderInterface): response.raise_for_status() return PrincipalAdminRoleCSPResult(**response.json()) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def force_tenant_admin_pw_update(self, creds, tenant_owner_id): @@ -1129,17 +1123,17 @@ class AzureCloudProvider(CloudProviderInterface): # raise UserProvisioningException(f"Failed to create user: {response.json()}") - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def _update_active_directory_user_email( @@ -1166,17 +1160,17 @@ class AzureCloudProvider(CloudProviderInterface): f"Failed update user email: {response.json()}" ) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def _extract_subscription_id(self, subscription_url): @@ -1281,17 +1275,17 @@ class AzureCloudProvider(CloudProviderInterface): return mgmt_token - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") def _source_creds(self, tenant_id=None) -> KeyVaultCredentials: @@ -1360,15 +1354,15 @@ class AzureCloudProvider(CloudProviderInterface): if result.ok: return CostManagementQueryCSPResult(**result.json()) - except self.sdk.requests.ConnectionError: + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, ) raise ConnectionException("connection error creating tenant") - except self.sdk.requests.Timeout: + except self.sdk.requests.exceptions.Timeout: app.logger.error( f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.HTTPError: + except self.sdk.requests.exceptions.HTTPError: raise UnknownServerException("azure application error creating tenant") diff --git a/atst/models/mixins/state_machines.py b/atst/models/mixins/state_machines.py index 5741476d..f4b6fae5 100644 --- a/atst/models/mixins/state_machines.py +++ b/atst/models/mixins/state_machines.py @@ -135,7 +135,6 @@ class FSMMixin: f"could not locate fail trigger '{fail_trigger}' for '{self.__repr__()}'" ) - def finish_stage(self, stage): finish_trigger = f"finish_{stage}" if finish_trigger in self.machine.get_triggers(self.current_state.name): diff --git a/atst/models/portfolio_state_machine.py b/atst/models/portfolio_state_machine.py index d4fff482..39fe727a 100644 --- a/atst/models/portfolio_state_machine.py +++ b/atst/models/portfolio_state_machine.py @@ -19,7 +19,7 @@ from atst.models.mixins.state_machines import ( FSMStates, AzureStages, StageStates, - _build_transitions + _build_transitions, ) @@ -52,7 +52,9 @@ def get_stage_csp_class(stage, class_type): importlib.import_module("atst.domain.csp.cloud.models"), cls_name ) except AttributeError: - raise StateMachineMisconfiguredError(f"could not import CSP Payload/Result class {cls_name}") + raise StateMachineMisconfiguredError( + f"could not import CSP Payload/Result class {cls_name}" + ) @add_state_features(Tags) @@ -138,7 +140,7 @@ class PortfolioStateMachine( app.logger.info( f"could not locate 'create trigger' for {self.__repr__()}" ) - self.trigger('fail') + self.trigger("fail") elif self.current_state == FSMStates.FAILED: # get the first trigger that starts with 'create_' @@ -169,15 +171,18 @@ class PortfolioStateMachine( if create_trigger is not None: self.trigger(create_trigger, **kwargs) - def after_in_progress_callback(self, event): # Accumulate payload w/ creds payload = event.kwargs.get("csp_data") - current_stage = _stage_state_to_stage_name(self.current_state, StageStates.IN_PROGRESS) + current_stage = _stage_state_to_stage_name( + self.current_state, StageStates.IN_PROGRESS + ) payload_data_cls = get_stage_csp_class(current_stage, "payload") if not payload_data_cls: - app.logger.info(f"could not resolve payload data class for stage {current_stage}") + app.logger.info( + f"could not resolve payload data class for stage {current_stage}" + ) self.fail_stage(current_stage) try: payload_data = payload_data_cls(**payload) diff --git a/tests/domain/cloud/test_azure_csp.py b/tests/domain/cloud/test_azure_csp.py index 820a47c5..b39743ad 100644 --- a/tests/domain/cloud/test_azure_csp.py +++ b/tests/domain/cloud/test_azure_csp.py @@ -4,8 +4,6 @@ from uuid import uuid4 import pendulum import pydantic import pytest -#import requests -from secrets import token_urlsafe from tests.factories import ApplicationFactory, EnvironmentFactory from tests.mock_azure import AUTH_CREDENTIALS, mock_azure @@ -67,7 +65,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 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 @@ -103,6 +100,7 @@ def mock_get_secret(azure, val=None): return azure + def test_create_application_succeeds(mock_azure: AzureCloudProvider): application = ApplicationFactory.create() mock_management_group_create(mock_azure, {"id": "Test Id"}) @@ -141,6 +139,7 @@ def test_create_policy_definition_succeeds(mock_azure: AzureCloudProvider): parameters=mock_policy_definition, ) + def test_create_tenant(mock_azure: AzureCloudProvider): mock_result = Mock() @@ -151,10 +150,11 @@ def test_create_tenant(mock_azure: AzureCloudProvider): } mock_result.status_code = 200 - mock_azure.sdk.requests.post.return_value = mock_result mock_azure.sdk.requests.post.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, - mock_result + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, ] payload = TenantCSPPayload( @@ -172,79 +172,13 @@ def test_create_tenant(mock_azure: AzureCloudProvider): with pytest.raises(ConnectionException): mock_azure.create_tenant(payload) - - result: TenantCSPResult = mock_azure.create_tenant(payload) - assert result.tenant_id == "60ff9d34-82bf-4f21-b565-308ef0533435" - - -def test_create_tenant_req_mock(mock_azure: AzureCloudProvider, requests_mock): - url = f"{mock_azure.sdk.cloud.endpoints.resource_manager}providers/Microsoft.SignUp/createTenant" - requests_mock.register_uri( - "POST", "?".join([url, "api-version=2020-01-01-preview"]), - json={ - "objectId": "0a5f4926-e3ee-4f47-a6e3-8b0a30a40e3d", - "tenantId": "60ff9d34-82bf-4f21-b565-308ef0533435", - "userId": "1153801116406515559", - }, - ) - mock_azure = mock_get_secret(mock_azure) - payload = TenantCSPPayload( - **dict( - user_id="admin", - password="JediJan13$coot", # pragma: allowlist secret - domain_name="jediccpospawnedtenant2", - first_name="Tedry", - last_name="Tenet", - country_code="US", - password_recovery_email_address="thomas@promptworks.com", - ) - ) - result: TenantCSPResult = mock_azure.create_tenant(payload) - assert result.tenant_id == "60ff9d34-82bf-4f21-b565-308ef0533435" - - -def test_create_tenant_req_mock_fail(mock_azure: AzureCloudProvider, requests_mock): - url = f"{mock_azure.sdk.cloud.endpoints.resource_manager}providers/Microsoft.SignUp/createTenant" - requests_mock.register_uri( - "POST", "?".join([url, "api-version=2020-01-01-preview"]), - exc=requests.exceptions.ConnectionError, - ) - mock_azure = mock_get_secret(mock_azure) - payload = TenantCSPPayload( - **dict( - user_id="admin", - password="JediJan13$coot", # pragma: allowlist secret - domain_name="jediccpospawnedtenant2", - first_name="Tedry", - last_name="Tenet", - country_code="US", - password_recovery_email_address="thomas@promptworks.com", - ) - ) - with pytest.raises(ConnectionException): - assert mock_azure.create_tenant(payload) + mock_azure.create_tenant(payload) + with pytest.raises(UnknownServerException): + mock_azure.create_tenant(payload) - -# def test_create_tenant_fails(mock_azure: AzureCloudProvider): -# mock_result = Mock() -# mock_result.status_code = 200 -# mock_azure.sdk.requests.post.return_value = mock_result -# payload = TenantCSPPayload( -# **dict( -# user_id="admin", -# password="JediJan13$coot", # pragma: allowlist secret -# domain_name="jediccpospawnedtenant2", -# first_name="Tedry", -# last_name="Tenet", -# country_code="US", -# password_recovery_email_address="thomas@promptworks.com", -# ) -# ) -# mock_azure = mock_get_secret(mock_azure) - -# with pytest.raises(pydantic.ValidationError): -# assert mock_azure.create_tenant(payload) + result: TenantCSPResult = mock_azure.create_tenant(payload) + assert result.tenant_id == "60ff9d34-82bf-4f21-b565-308ef0533435" def test_create_billing_profile_creation(mock_azure: AzureCloudProvider): @@ -258,7 +192,14 @@ def test_create_billing_profile_creation(mock_azure: AzureCloudProvider): "Retry-After": "10", } mock_result.status_code = 202 - mock_azure.sdk.requests.post.return_value = mock_result + + mock_azure.sdk.requests.post.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] + payload = BillingProfileCreationCSPPayload( **dict( address=dict( @@ -274,6 +215,13 @@ def test_create_billing_profile_creation(mock_azure: AzureCloudProvider): billing_account_name=BILLING_ACCOUNT_NAME, ) ) + with pytest.raises(ConnectionException): + mock_azure.create_billing_profile_creation(payload) + with pytest.raises(ConnectionException): + mock_azure.create_billing_profile_creation(payload) + with pytest.raises(UnknownServerException): + mock_azure.create_billing_profile_creation(payload) + body: BillingProfileCreationCSPResult = mock_azure.create_billing_profile_creation( payload ) @@ -316,7 +264,12 @@ def test_validate_billing_profile_creation(mock_azure: AzureCloudProvider): }, "type": "Microsoft.Billing/billingAccounts/billingProfiles", } - mock_azure.sdk.requests.get.return_value = mock_result + mock_azure.sdk.requests.get.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] payload = BillingProfileVerificationCSPPayload( **dict( @@ -324,6 +277,12 @@ def test_validate_billing_profile_creation(mock_azure: AzureCloudProvider): billing_profile_verify_url="https://management.azure.com/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/operationResults/createBillingProfile_478d5706-71f9-4a8b-8d4e-2cbaca27a668?api-version=2019-10-01-preview", ) ) + with pytest.raises(ConnectionException): + mock_azure.create_billing_profile_verification(payload) + with pytest.raises(ConnectionException): + mock_azure.create_billing_profile_verification(payload) + with pytest.raises(UnknownServerException): + mock_azure.create_billing_profile_verification(payload) body: BillingProfileVerificationCSPResult = mock_azure.create_billing_profile_verification( payload @@ -356,7 +315,12 @@ def test_create_billing_profile_tenant_access(mock_azure: AzureCloudProvider): "type": "Microsoft.Billing/billingRoleAssignments", } - mock_azure.sdk.requests.post.return_value = mock_result + mock_azure.sdk.requests.post.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] payload = BillingProfileTenantAccessCSPPayload( **dict( @@ -366,6 +330,12 @@ def test_create_billing_profile_tenant_access(mock_azure: AzureCloudProvider): billing_profile_name="KQWI-W2SU-BG7-TGB", ) ) + with pytest.raises(ConnectionException): + mock_azure.create_billing_profile_tenant_access(payload) + with pytest.raises(ConnectionException): + mock_azure.create_billing_profile_tenant_access(payload) + with pytest.raises(UnknownServerException): + mock_azure.create_billing_profile_tenant_access(payload) body: BillingProfileTenantAccessCSPResult = mock_azure.create_billing_profile_tenant_access( payload @@ -388,7 +358,12 @@ def test_create_task_order_billing_creation(mock_azure: AzureCloudProvider): "Retry-After": "10", } - mock_azure.sdk.requests.patch.return_value = mock_result + mock_azure.sdk.requests.patch.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] payload = TaskOrderBillingCreationCSPPayload( **dict( @@ -397,6 +372,12 @@ def test_create_task_order_billing_creation(mock_azure: AzureCloudProvider): billing_profile_name="KQWI-W2SU-BG7-TGB", ) ) + with pytest.raises(ConnectionException): + mock_azure.create_task_order_billing_creation(payload) + with pytest.raises(ConnectionException): + mock_azure.create_task_order_billing_creation(payload) + with pytest.raises(UnknownServerException): + mock_azure.create_task_order_billing_creation(payload) body: TaskOrderBillingCreationCSPResult = mock_azure.create_task_order_billing_creation( payload @@ -450,7 +431,12 @@ def test_create_task_order_billing_verification(mock_azure): }, "type": "Microsoft.Billing/billingAccounts/billingProfiles", } - mock_azure.sdk.requests.get.return_value = mock_result + mock_azure.sdk.requests.get.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] payload = TaskOrderBillingVerificationCSPPayload( **dict( @@ -458,6 +444,12 @@ def test_create_task_order_billing_verification(mock_azure): task_order_billing_verify_url="https://management.azure.com/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/operationResults/createBillingProfile_478d5706-71f9-4a8b-8d4e-2cbaca27a668?api-version=2019-10-01-preview", ) ) + with pytest.raises(ConnectionException): + mock_azure.create_task_order_billing_verification(payload) + with pytest.raises(ConnectionException): + mock_azure.create_task_order_billing_verification(payload) + with pytest.raises(UnknownServerException): + mock_azure.create_task_order_billing_verification(payload) body: TaskOrderBillingVerificationCSPResult = mock_azure.create_task_order_billing_verification( payload @@ -486,7 +478,12 @@ def test_create_billing_instruction(mock_azure: AzureCloudProvider): "type": "Microsoft.Billing/billingAccounts/billingProfiles/billingInstructions", } - mock_azure.sdk.requests.put.return_value = mock_result + mock_azure.sdk.requests.put.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] payload = BillingInstructionCSPPayload( **dict( @@ -500,6 +497,12 @@ def test_create_billing_instruction(mock_azure: AzureCloudProvider): billing_profile_name="KQWI-W2SU-BG7-TGB", ) ) + with pytest.raises(ConnectionException): + mock_azure.create_billing_instruction(payload) + with pytest.raises(ConnectionException): + mock_azure.create_billing_instruction(payload) + with pytest.raises(UnknownServerException): + mock_azure.create_billing_instruction(payload) body: BillingInstructionCSPResult = mock_azure.create_billing_instruction(payload) assert body.reported_clin_name == "TO1:CLIN001" @@ -516,7 +519,12 @@ def test_create_product_purchase(mock_azure: AzureCloudProvider): "Retry-After": "10", } - mock_azure.sdk.requests.post.return_value = mock_result + mock_azure.sdk.requests.post.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] payload = ProductPurchaseCSPPayload( **dict( @@ -525,6 +533,12 @@ def test_create_product_purchase(mock_azure: AzureCloudProvider): billing_profile_name="KQWI-W2SU-BG7-TGB", ) ) + with pytest.raises(ConnectionException): + mock_azure.create_product_purchase(payload) + with pytest.raises(ConnectionException): + mock_azure.create_product_purchase(payload) + with pytest.raises(UnknownServerException): + mock_azure.create_product_purchase(payload) body: ProductPurchaseCSPResult = mock_azure.create_product_purchase(payload) assert ( @@ -561,7 +575,12 @@ def test_create_product_purchase_verification(mock_azure): "type": "Microsoft.Billing/billingAccounts/billingProfiles/invoiceSections/products", } - mock_azure.sdk.requests.get.return_value = mock_result + mock_azure.sdk.requests.get.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] payload = ProductPurchaseVerificationCSPPayload( **dict( @@ -569,6 +588,12 @@ def test_create_product_purchase_verification(mock_azure): product_purchase_verify_url="https://management.azure.com/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/operationResults/createBillingProfile_478d5706-71f9-4a8b-8d4e-2cbaca27a668?api-version=2019-10-01-preview", ) ) + with pytest.raises(ConnectionException): + mock_azure.create_product_purchase_verification(payload) + with pytest.raises(ConnectionException): + mock_azure.create_product_purchase_verification(payload) + with pytest.raises(UnknownServerException): + mock_azure.create_product_purchase_verification(payload) body: ProductPurchaseVerificationCSPResult = mock_azure.create_product_purchase_verification( payload @@ -588,12 +613,25 @@ def test_create_tenant_principal_app(mock_azure: AzureCloudProvider): mock_result.ok = True mock_result.json.return_value = {"appId": "appId", "id": "id"} - mock_azure.sdk.requests.post.return_value = mock_result + mock_azure.sdk.requests.post.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] + mock_azure = mock_get_secret(mock_azure) payload = TenantPrincipalAppCSPPayload( **{"tenant_id": "6d2d2d6c-a6d6-41e1-8bb1-73d11475f8f4"} ) + with pytest.raises(ConnectionException): + mock_azure.create_tenant_principal_app(payload) + with pytest.raises(ConnectionException): + mock_azure.create_tenant_principal_app(payload) + with pytest.raises(UnknownServerException): + mock_azure.create_tenant_principal_app(payload) + result: TenantPrincipalAppCSPResult = mock_azure.create_tenant_principal_app( payload ) @@ -613,7 +651,13 @@ def test_create_tenant_principal(mock_azure: AzureCloudProvider): mock_result.ok = True mock_result.json.return_value = {"id": "principal_id"} - mock_azure.sdk.requests.post.return_value = mock_result + mock_azure.sdk.requests.post.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] + mock_azure = mock_get_secret(mock_azure) payload = TenantPrincipalCSPPayload( @@ -622,6 +666,12 @@ def test_create_tenant_principal(mock_azure: AzureCloudProvider): "principal_app_id": "appId", } ) + with pytest.raises(ConnectionException): + mock_azure.create_tenant_principal(payload) + with pytest.raises(ConnectionException): + mock_azure.create_tenant_principal(payload) + with pytest.raises(UnknownServerException): + mock_azure.create_tenant_principal(payload) result: TenantPrincipalCSPResult = mock_azure.create_tenant_principal(payload) @@ -640,7 +690,12 @@ def test_create_tenant_principal_credential(mock_azure: AzureCloudProvider): mock_result.ok = True mock_result.json.return_value = {"secretText": "new secret key"} - mock_azure.sdk.requests.post.return_value = mock_result + mock_azure.sdk.requests.post.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] mock_azure = mock_get_secret(mock_azure) @@ -651,6 +706,12 @@ def test_create_tenant_principal_credential(mock_azure: AzureCloudProvider): "principal_app_object_id": "appObjId", } ) + with pytest.raises(ConnectionException): + mock_azure.create_tenant_principal_credential(payload) + with pytest.raises(ConnectionException): + mock_azure.create_tenant_principal_credential(payload) + with pytest.raises(UnknownServerException): + mock_azure.create_tenant_principal_credential(payload) result: TenantPrincipalCredentialCSPResult = mock_azure.create_tenant_principal_credential( payload @@ -676,12 +737,23 @@ def test_create_admin_role_definition(mock_azure: AzureCloudProvider): ] } - mock_azure.sdk.requests.get.return_value = mock_result + mock_azure.sdk.requests.get.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] mock_azure = mock_get_secret(mock_azure) payload = AdminRoleDefinitionCSPPayload( **{"tenant_id": "6d2d2d6c-a6d6-41e1-8bb1-73d11475f8f4"} ) + with pytest.raises(ConnectionException): + mock_azure.create_admin_role_definition(payload) + with pytest.raises(ConnectionException): + mock_azure.create_admin_role_definition(payload) + with pytest.raises(UnknownServerException): + mock_azure.create_admin_role_definition(payload) result: AdminRoleDefinitionCSPResult = mock_azure.create_admin_role_definition( payload @@ -702,7 +774,12 @@ def test_create_tenant_admin_ownership(mock_azure: AzureCloudProvider): mock_result.ok = True mock_result.json.return_value = {"id": "id"} - mock_azure.sdk.requests.put.return_value = mock_result + mock_azure.sdk.requests.put.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] payload = TenantAdminOwnershipCSPPayload( **{ @@ -710,6 +787,12 @@ def test_create_tenant_admin_ownership(mock_azure: AzureCloudProvider): "user_object_id": "971efe4d-1e80-4e39-b3b9-4e5c63ad446d", } ) + with pytest.raises(ConnectionException): + mock_azure.create_tenant_admin_ownership(payload) + with pytest.raises(ConnectionException): + mock_azure.create_tenant_admin_ownership(payload) + with pytest.raises(UnknownServerException): + mock_azure.create_tenant_admin_ownership(payload) result: TenantAdminOwnershipCSPResult = mock_azure.create_tenant_admin_ownership( payload @@ -730,7 +813,12 @@ def test_create_tenant_principal_ownership(mock_azure: AzureCloudProvider): mock_result.ok = True mock_result.json.return_value = {"id": "id"} - mock_azure.sdk.requests.put.return_value = mock_result + mock_azure.sdk.requests.put.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] payload = TenantPrincipalOwnershipCSPPayload( **{ @@ -738,6 +826,12 @@ def test_create_tenant_principal_ownership(mock_azure: AzureCloudProvider): "principal_id": "971efe4d-1e80-4e39-b3b9-4e5c63ad446d", } ) + with pytest.raises(ConnectionException): + mock_azure.create_tenant_principal_ownership(payload) + with pytest.raises(ConnectionException): + mock_azure.create_tenant_principal_ownership(payload) + with pytest.raises(UnknownServerException): + mock_azure.create_tenant_principal_ownership(payload) result: TenantPrincipalOwnershipCSPResult = mock_azure.create_tenant_principal_ownership( payload @@ -758,7 +852,12 @@ def test_create_principal_admin_role(mock_azure: AzureCloudProvider): mock_result.ok = True mock_result.json.return_value = {"id": "id"} - mock_azure.sdk.requests.post.return_value = mock_result + mock_azure.sdk.requests.post.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] payload = PrincipalAdminRoleCSPPayload( **{ @@ -767,6 +866,12 @@ def test_create_principal_admin_role(mock_azure: AzureCloudProvider): "admin_role_def_id": uuid4().hex, } ) + with pytest.raises(ConnectionException): + mock_azure.create_principal_admin_role(payload) + with pytest.raises(ConnectionException): + mock_azure.create_principal_admin_role(payload) + with pytest.raises(UnknownServerException): + mock_azure.create_principal_admin_role(payload) result: PrincipalAdminRoleCSPResult = mock_azure.create_principal_admin_role( payload @@ -790,7 +895,14 @@ def test_create_subscription_creation(mock_azure: AzureCloudProvider): "Retry-After": 10, } mock_result.json.return_value = {} - mock_azure.sdk.requests.put.return_value = mock_result + + mock_azure.sdk.requests.put.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] + management_group_id = str(uuid4()) payload = SubscriptionCreationCSPPayload( **dict( @@ -802,6 +914,12 @@ def test_create_subscription_creation(mock_azure: AzureCloudProvider): invoice_section_name="6HMZ-2HLO-PJA-TGB", ) ) + with pytest.raises(ConnectionException): + mock_azure.create_subscription_creation(payload) + with pytest.raises(ConnectionException): + mock_azure.create_subscription_creation(payload) + with pytest.raises(UnknownServerException): + mock_azure.create_subscription_creation(payload) result: SubscriptionCreationCSPResult = mock_azure.create_subscription_creation( payload @@ -823,7 +941,13 @@ def test_create_subscription_verification(mock_azure: AzureCloudProvider): mock_result.json.return_value = { "subscriptionLink": "/subscriptions/60fbbb72-0516-4253-ab18-c92432ba3230" } - mock_azure.sdk.requests.get.return_value = mock_result + + mock_azure.sdk.requests.get.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] payload = SubscriptionVerificationCSPPayload( **dict( @@ -831,6 +955,12 @@ def test_create_subscription_verification(mock_azure: AzureCloudProvider): subscription_verify_url="https://verify.me", ) ) + with pytest.raises(ConnectionException): + mock_azure.create_subscription_verification(payload) + with pytest.raises(ConnectionException): + mock_azure.create_subscription_verification(payload) + with pytest.raises(UnknownServerException): + mock_azure.create_subscription_verification(payload) result: SuscriptionVerificationCSPResult = mock_azure.create_subscription_verification( payload @@ -859,7 +989,14 @@ def test_get_reporting_data(mock_azure: AzureCloudProvider): "type": "Microsoft.CostManagement/query", } mock_result.ok = True - mock_azure.sdk.requests.post.return_value = mock_result + + mock_azure.sdk.requests.post.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] + mock_azure = mock_get_secret(mock_azure) # Subset of a profile's CSP data that we care about for reporting @@ -873,14 +1010,19 @@ def test_get_reporting_data(mock_azure: AzureCloudProvider): ], }, } - - data: CostManagementQueryCSPResult = mock_azure.get_reporting_data( - ReportingCSPPayload( - from_date=pendulum.now().subtract(years=1).add(days=1).format("YYYY-MM-DD"), - to_date=pendulum.now().format("YYYY-MM-DD"), - **csp_data, - ) + payload = ReportingCSPPayload( + from_date=pendulum.now().subtract(years=1).add(days=1).format("YYYY-MM-DD"), + to_date=pendulum.now().format("YYYY-MM-DD"), + **csp_data, ) + with pytest.raises(ConnectionException): + mock_azure.get_reporting_data(payload) + with pytest.raises(ConnectionException): + mock_azure.get_reporting_data(payload) + with pytest.raises(UnknownServerException): + mock_azure.get_reporting_data(payload) + + data: CostManagementQueryCSPResult = mock_azure.get_reporting_data(payload) assert isinstance(data, CostManagementQueryCSPResult) assert data.name == "e82d0cda-2ffb-4476-a98a-425c83c216f9" @@ -890,7 +1032,14 @@ def test_get_reporting_data(mock_azure: AzureCloudProvider): def test_get_reporting_data_malformed_payload(mock_azure: AzureCloudProvider): mock_result = Mock() mock_result.ok = True - mock_azure.sdk.requests.post.return_value = mock_result + + mock_azure.sdk.requests.post.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] + mock_azure = mock_get_secret(mock_azure) # Malformed csp_data payloads that should throw pydantic validation errors @@ -946,7 +1095,12 @@ def test_create_active_directory_user(mock_azure: AzureCloudProvider): mock_result = Mock() mock_result.ok = True mock_result.json.return_value = {"id": "id"} - mock_azure.sdk.requests.post.return_value = mock_result + mock_azure.sdk.requests.post.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] payload = UserCSPPayload( tenant_id=uuid4().hex, @@ -955,6 +1109,12 @@ def test_create_active_directory_user(mock_azure: AzureCloudProvider): email="test@testerson.test", password="asdfghjkl", # pragma: allowlist secret ) + with pytest.raises(ConnectionException): + mock_azure._create_active_directory_user("token", payload) + with pytest.raises(ConnectionException): + mock_azure._create_active_directory_user("token", payload) + with pytest.raises(UnknownServerException): + mock_azure._create_active_directory_user("token", payload) result = mock_azure._create_active_directory_user("token", payload) @@ -964,8 +1124,12 @@ def test_create_active_directory_user(mock_azure: AzureCloudProvider): def test_update_active_directory_user_email(mock_azure: AzureCloudProvider): mock_result = Mock() mock_result.ok = True - mock_azure.sdk.requests.patch.return_value = mock_result - + mock_azure.sdk.requests.patch.side_effect = [ + mock_azure.sdk.requests.exceptions.ConnectionError, + mock_azure.sdk.requests.exceptions.Timeout, + mock_azure.sdk.requests.exceptions.HTTPError, + mock_result, + ] payload = UserCSPPayload( tenant_id=uuid4().hex, display_name="Test Testerson", @@ -973,6 +1137,12 @@ def test_update_active_directory_user_email(mock_azure: AzureCloudProvider): email="test@testerson.test", password="asdfghjkl", # pragma: allowlist secret ) + with pytest.raises(ConnectionException): + mock_azure._update_active_directory_user_email("token", uuid4().hex, payload) + with pytest.raises(ConnectionException): + mock_azure._update_active_directory_user_email("token", uuid4().hex, payload) + with pytest.raises(UnknownServerException): + mock_azure._update_active_directory_user_email("token", uuid4().hex, payload) result = mock_azure._update_active_directory_user_email( "token", uuid4().hex, payload @@ -1007,5 +1177,4 @@ def test_create_user(mock_azure: AzureCloudProvider): ) result = mock_azure.create_user(payload) - assert result.id == "id" diff --git a/tests/domain/cloud/test_mock_csp.py b/tests/domain/cloud/test_mock_csp.py index fc5ead11..098e91b1 100644 --- a/tests/domain/cloud/test_mock_csp.py +++ b/tests/domain/cloud/test_mock_csp.py @@ -45,7 +45,6 @@ def test_create_tenant(mock_csp: MockCloudProvider): last_name="Tenet", country_code="US", password_recovery_email_address="thomas@promptworks.com", - ) ) result = mock_csp.create_tenant(payload) diff --git a/tests/domain/test_portfolio_state_machine.py b/tests/domain/test_portfolio_state_machine.py index a8dddbb2..02ed4e58 100644 --- a/tests/domain/test_portfolio_state_machine.py +++ b/tests/domain/test_portfolio_state_machine.py @@ -34,6 +34,7 @@ from atst.models.portfolio_state_machine import ( class AzureStagesTest(Enum): TENANT = "tenant" + @pytest.fixture(scope="function") def portfolio(): # TODO: setup clin/to as active/funded/ready @@ -62,27 +63,56 @@ def test_state_machine_compose_state(): == FSMStates.TENANT_CREATED ) + def test_stage_to_classname(): - assert (_stage_to_classname(AzureStages.BILLING_PROFILE_CREATION.name) == "BillingProfileCreation") + assert ( + _stage_to_classname(AzureStages.BILLING_PROFILE_CREATION.name) + == "BillingProfileCreation" + ) + def test_get_stage_csp_class(): csp_class = get_stage_csp_class(list(AzureStages)[0].name.lower(), "payload") assert isinstance(csp_class, pydantic.main.ModelMetaclass) + def test_get_stage_csp_class_import_fail(): with pytest.raises(StateMachineMisconfiguredError): csp_class = get_stage_csp_class("doesnotexist", "payload") + def test_build_transitions(): states, transitions = _build_transitions(AzureStagesTest) - assert [s.get('name').name for s in states] == ['TENANT_CREATED', 'TENANT_IN_PROGRESS', 'TENANT_FAILED'] - assert [s.get('tags') for s in states] == [['TENANT', 'CREATED'], ['TENANT', 'IN_PROGRESS'], ['TENANT', 'FAILED']] - assert [t.get('trigger') for t in transitions] == ['create_tenant', 'finish_tenant', 'fail_tenant', 'resume_progress_tenant'] + assert [s.get("name").name for s in states] == [ + "TENANT_CREATED", + "TENANT_IN_PROGRESS", + "TENANT_FAILED", + ] + assert [s.get("tags") for s in states] == [ + ["TENANT", "CREATED"], + ["TENANT", "IN_PROGRESS"], + ["TENANT", "FAILED"], + ] + assert [t.get("trigger") for t in transitions] == [ + "create_tenant", + "finish_tenant", + "fail_tenant", + "resume_progress_tenant", + ] + def test_build_csp_states(): states = _build_csp_states(AzureStagesTest) - assert list(states) == ['UNSTARTED', 'STARTING', 'STARTED', 'COMPLETED', 'FAILED', 'TENANT_CREATED', 'TENANT_IN_PROGRESS', 'TENANT_FAILED'] - + assert list(states) == [ + "UNSTARTED", + "STARTING", + "STARTED", + "COMPLETED", + "FAILED", + "TENANT_CREATED", + "TENANT_IN_PROGRESS", + "TENANT_FAILED", + ] def test_state_machine_valid_data_classes_for_stages(portfolio): @@ -96,21 +126,34 @@ def test_attach_machine(portfolio): sm = PortfolioStateMachineFactory.create(portfolio=portfolio) sm.machine = None sm.attach_machine(stages=AzureStagesTest) - assert list(sm.machine.events) == ['init', 'start', 'reset', 'fail', 'create_tenant', 'finish_tenant', 'fail_tenant', 'resume_progress_tenant'] + assert list(sm.machine.events) == [ + "init", + "start", + "reset", + "fail", + "create_tenant", + "finish_tenant", + "fail_tenant", + "resume_progress_tenant", + ] def test_fail_stage(portfolio): sm = PortfolioStateMachineFactory.create(portfolio=portfolio) sm.state = FSMStates.TENANT_IN_PROGRESS - sm.fail_stage('tenant') + sm.fail_stage("tenant") assert sm.state == FSMStates.TENANT_FAILED - #import ipdb;ipdb.set_trace() + # import ipdb;ipdb.set_trace() + def test_stage_state_to_stage_name(): - #sm = PortfolioStateMachineFactory.create(portfolio=portfolio) - stage = _stage_state_to_stage_name(FSMStates.TENANT_IN_PROGRESS, StageStates.IN_PROGRESS) + # sm = PortfolioStateMachineFactory.create(portfolio=portfolio) + stage = _stage_state_to_stage_name( + FSMStates.TENANT_IN_PROGRESS, StageStates.IN_PROGRESS + ) assert stage == "tenant" + def test_state_machine_initialization(portfolio): sm = PortfolioStateMachineFactory.create(portfolio=portfolio) @@ -180,7 +223,7 @@ def test_fsm_transition_start(mock_cloud_provider, portfolio: Portfolio): csp_data = {} ppoc = portfolio.owner - user_id = "johndoe" # f"{ppoc.first_name[0]}{ppoc.last_name}".lower() + user_id = f"{ppoc.first_name[0]}{ppoc.last_name}".lower() domain_name = re.sub("[^0-9a-zA-Z]+", "", portfolio.name).lower() initial_task_order: TaskOrder = portfolio.task_orders[0] @@ -190,10 +233,10 @@ def test_fsm_transition_start(mock_cloud_provider, portfolio: Portfolio): "user_id": user_id, "password": "jklfsdNCVD83nklds2#202", # pragma: allowlist secret "domain_name": domain_name, - "first_name": "123", # ppoc.first_name, - "last_name": "123", # ppoc.last_name, + "first_name": ppoc.first_name, + "last_name": ppoc.last_name, "country_code": "US", - "password_recovery_email_address": "email@example.com", # ppoc.email, + "password_recovery_email_address": ppoc.email, "address": { # TODO: TBD if we're sourcing this from data or config "company_name": "", "address_line_1": "", @@ -222,4 +265,3 @@ def test_fsm_transition_start(mock_cloud_provider, portfolio: Portfolio): csp_data = portfolio.csp_data else: csp_data = {} - diff --git a/tests/mock_azure.py b/tests/mock_azure.py index 37a106ab..7fbc351a 100644 --- a/tests/mock_azure.py +++ b/tests/mock_azure.py @@ -79,7 +79,9 @@ def mock_adal(): def mock_requests(): import requests - return Mock(wraps=requests) + mock_requests = Mock(wraps=requests) + mock_requests.exceptions = requests.exceptions + return mock_requests def mock_secrets(): From 5df1d80c46c133d2afc69bc33a09d589fb7a9cf5 Mon Sep 17 00:00:00 2001 From: Philip Kalinsky Date: Tue, 11 Feb 2020 15:57:47 -0500 Subject: [PATCH 07/10] removed unused import --- atst/domain/csp/cloud/azure_cloud_provider.py | 1 - 1 file changed, 1 deletion(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 15c1fc3e..1c2a4181 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -1,7 +1,6 @@ import json from secrets import token_urlsafe from uuid import uuid4 -import pydantic from flask import current_app as app from atst.utils import sha256_hex From 88b3df7906817253d89705b1fa5d0b9d23d269df Mon Sep 17 00:00:00 2001 From: Philip Kalinsky Date: Wed, 12 Feb 2020 21:32:48 -0500 Subject: [PATCH 08/10] csp provider tests requests.HTTPError exceptions --- atst/domain/csp/cloud/azure_cloud_provider.py | 608 ++++++++++++------ atst/domain/csp/cloud/exceptions.py | 5 +- atst/domain/csp/cloud/mock_cloud_provider.py | 2 +- atst/models/portfolio_state_machine.py | 2 +- tests/domain/cloud/test_azure_csp.py | 279 ++++++-- tests/domain/test_portfolio_state_machine.py | 2 - tests/mock_azure.py | 2 +- 7 files changed, 661 insertions(+), 239 deletions(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index d45b9ef8..86afc69b 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -136,14 +136,15 @@ class AzureCloudProvider(CloudProviderInterface): ) try: return secret_client.set_secret(secret_key, secret_value) - except self.azure_exceptions.HttpResponseError: + except self.sdk.azure_exceptions.HttpResponseError: app.logger.error( f"Could not SET secret in Azure keyvault for key {secret_key}.", exc_info=1, ) + creds = self._source_creds() raise SecretException( + creds.tenant_id, f"Could not SET secret in Azure keyvault for key {secret_key}.", - exc.message, ) def get_secret(self, secret_key): @@ -153,14 +154,15 @@ class AzureCloudProvider(CloudProviderInterface): ) try: return secret_client.get_secret(secret_key).value - except self.azure_exceptions.HttpResponseError: + except self.sdk.azure_exceptions.HttpResponseError: app.logger.error( f"Could not GET secret in Azure keyvault for key {secret_key}.", exc_info=1, ) + creds = self._source_creds() raise SecretException( + creds.tenant_id, f"Could not GET secret in Azure keyvault for key {secret_key}.", - exc.message, ) def create_environment(self, payload: EnvironmentCSPPayload): @@ -339,8 +341,16 @@ class AzureCloudProvider(CloudProviderInterface): f"Could not create tenant. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error creating tenant", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error creating tenant. {str(exc)}", + ) result_dict = result.json() tenant_id = result_dict.get("tenantId") @@ -392,16 +402,24 @@ class AzureCloudProvider(CloudProviderInterface): except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not create billing profile. Connection Error", exc_info=1, ) - raise ConnectionException("connection error creating tenant") + raise ConnectionException("connection error creating billing profile") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not create billing profile. Request timed out.", exc_info=1, ) raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error creating billing profile", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error creating billing profile. {str(exc)}", + ) def create_billing_profile_verification( self, payload: BillingProfileVerificationCSPPayload @@ -429,16 +447,30 @@ class AzureCloudProvider(CloudProviderInterface): except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not verify billing profile creation. Connection Error", + exc_info=1, + ) + raise ConnectionException( + "connection error creating billing profile verification" ) - raise ConnectionException("connection error creating tenant") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not verify billing profile creation. Request timed out.", + exc_info=1, + ) + raise ConnectionException( + "timout error during billing profile verification" + ) + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error during billing profile verification", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error during billing profile verification. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def create_billing_profile_tenant_access( self, payload: BillingProfileTenantAccessCSPPayload @@ -461,21 +493,36 @@ class AzureCloudProvider(CloudProviderInterface): result = self.sdk.requests.post( url, headers=headers, json=request_body, timeout=30, ) + result.raise_for_status() if result.status_code == 201: return BillingProfileTenantAccessCSPResult(**result.json()) except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not create billing profile tenant access. Connection Error", + exc_info=1, + ) + raise ConnectionException( + "connection error creating billing profile tenant access" ) - raise ConnectionException("connection error creating tenant") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not create billing profile tenant access. Request timed out.", + exc_info=1, + ) + raise ConnectionException( + "timout error creating billing profile tenant access" + ) + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error creating billing profile tenant access", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error creating billing profile tenant access. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def create_task_order_billing_creation( self, payload: TaskOrderBillingCreationCSPPayload @@ -509,16 +556,24 @@ class AzureCloudProvider(CloudProviderInterface): except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not create task order billing. Connection Error", exc_info=1, ) - raise ConnectionException("connection error creating tenant") + raise ConnectionException("connection error creating task order billing") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not create task order billing. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating task order billing") + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error creating task order billing", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error creating task order billing. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def create_task_order_billing_verification( self, payload: TaskOrderBillingVerificationCSPPayload @@ -547,16 +602,27 @@ class AzureCloudProvider(CloudProviderInterface): except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not verify task order billing. Connection Error", exc_info=1, + ) + raise ConnectionException( + "connection error during task order billing verification" ) - raise ConnectionException("connection error creating tenant") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not create verify task order billing. Request timed out.", + exc_info=1, + ) + raise ConnectionException("timout error in task order billing verification") + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error in task order billing verification", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error in task order billing verification. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def create_billing_instruction(self, payload: BillingInstructionCSPPayload): sp_token = self._get_root_provisioning_token() @@ -588,16 +654,25 @@ class AzureCloudProvider(CloudProviderInterface): except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not create billing instructions. Connection Error", exc_info=1, ) - raise ConnectionException("connection error creating tenant") + raise ConnectionException("connection error creating billing instructions") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not create billing instructions. Request timed out.", + exc_info=1, + ) + raise ConnectionException("timout error creating billing instructions") + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error in creating billing instructions", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error in creating billing instructions. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def create_subscription(self, payload: SubscriptionCreationCSPPayload): sp_token = self._get_tenant_principal_token(payload.tenant_id) @@ -622,22 +697,31 @@ class AzureCloudProvider(CloudProviderInterface): result = self.sdk.requests.put( url, headers=auth_header, json=request_body, timeout=30 ) + result.raise_for_status() if result.status_code in [200, 202]: # 202 has location/retry after headers return SubscriptionCreationCSPResult(**result.headers, **result.json()) except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not create subscription. Connection Error", exc_info=1, ) - raise ConnectionException("connection error creating tenant") + raise ConnectionException("connection error creating subscription") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not create subscription. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating subscription") + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error creating subscription", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error creating subscription. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def create_subscription_creation(self, payload: SubscriptionCreationCSPPayload): return self.create_subscription(payload) @@ -666,16 +750,26 @@ class AzureCloudProvider(CloudProviderInterface): except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not verify subscription. Connection Error", exc_info=1, + ) + raise ConnectionException( + "connection error during subscription verification" ) - raise ConnectionException("connection error creating tenant") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not verify subscription. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error during subscription verification") + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error during subscription verification", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error during subscription verification. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def create_product_purchase(self, payload: ProductPurchaseCSPPayload): sp_token = self._get_root_provisioning_token() @@ -713,16 +807,24 @@ class AzureCloudProvider(CloudProviderInterface): except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not purchase product. Connection Error", exc_info=1, ) - raise ConnectionException("connection error creating tenant") + raise ConnectionException("connection error during product purchase") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not purchase product. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error during product purchase") + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error during product purchase", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error during product purchase. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def create_product_purchase_verification( self, payload: ProductPurchaseVerificationCSPPayload @@ -753,16 +855,28 @@ class AzureCloudProvider(CloudProviderInterface): except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not verify product purchase. Connection Error", exc_info=1, + ) + raise ConnectionException( + "connection error during product purchase verification" ) - raise ConnectionException("connection error creating tenant") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not verify product purchase. Request timed out.", exc_info=1, + ) + raise ConnectionException( + "timout error during product purchase verification" + ) + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error during product purchase verification", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error during product purchase verification. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def create_tenant_admin_ownership(self, payload: TenantAdminOwnershipCSPPayload): mgmt_token = self._get_elevated_management_token(payload.tenant_id) @@ -785,25 +899,37 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Management/managementGroups/{payload.tenant_id}/providers/Microsoft.Authorization/roleAssignments/{assignment_guid}?api-version=2015-07-01" try: - response = self.sdk.requests.put( + result = self.sdk.requests.put( url, headers=auth_header, json=request_body, timeout=30 ) - response.raise_for_status() + result.raise_for_status() - return TenantAdminOwnershipCSPResult(**response.json()) + return TenantAdminOwnershipCSPResult(**result.json()) except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not create tenant admin ownership. Connection Error", + exc_info=1, + ) + raise ConnectionException( + "connection error creating tenant admin ownership" ) - raise ConnectionException("connection error creating tenant") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not create tenant admin ownership. Request timed out.", + exc_info=1, + ) + raise ConnectionException("timout error creating tenant admin ownership") + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error creating tenant admin ownership", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error creating tenant admin ownership. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def create_tenant_principal_ownership( self, payload: TenantPrincipalOwnershipCSPPayload @@ -829,24 +955,38 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Management/managementGroups/{payload.tenant_id}/providers/Microsoft.Authorization/roleAssignments/{assignment_guid}?api-version=2015-07-01" try: - response = self.sdk.requests.put( + result = self.sdk.requests.put( url, headers=auth_header, json=request_body, timeout=30, ) - response.raise_for_status() - return TenantPrincipalOwnershipCSPResult(**response.json()) + result.raise_for_status() + return TenantPrincipalOwnershipCSPResult(**result.json()) except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not create tenant principal ownership. Connection Error", + exc_info=1, + ) + raise ConnectionException( + "connection error creating tenant principal ownership" ) - raise ConnectionException("connection error creating tenant") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not create tenant principal ownership. Request timed out.", + exc_info=1, + ) + raise ConnectionException( + "timout error creating tenant prinicpal ownership" + ) + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error creating tenant principal ownership", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error creating tenant principal ownership. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def create_tenant_principal_app(self, payload: TenantPrincipalAppCSPPayload): graph_token = self._get_tenant_admin_token( @@ -866,24 +1006,33 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.graph_resource}/v1.0/applications" try: - response = self.sdk.requests.post( + result = self.sdk.requests.post( url, json=request_body, headers=auth_header, timeout=30 ) - response.raise_for_status() - return TenantPrincipalAppCSPResult(**response.json()) + result.raise_for_status() + return TenantPrincipalAppCSPResult(**result.json()) except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not create tenant principal app. Connection Error", exc_info=1, ) - raise ConnectionException("connection error creating tenant") + raise ConnectionException("connection error creating tenant principal app") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not create tenant principal app. Request timed out.", + exc_info=1, + ) + raise ConnectionException("timout error creating tenant principal app") + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error creating tenant principal app", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error creating tenant principal app. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def create_tenant_principal(self, payload: TenantPrincipalCSPPayload): graph_token = self._get_tenant_admin_token( @@ -903,24 +1052,32 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.graph_resource}/beta/servicePrincipals" try: - response = self.sdk.requests.post( + result = self.sdk.requests.post( url, json=request_body, headers=auth_header, timeout=30 ) - response.raise_for_status() - return TenantPrincipalCSPResult(**response.json()) + result.raise_for_status() + return TenantPrincipalCSPResult(**result.json()) except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not create tenant principal. Connection Error", exc_info=1, ) - raise ConnectionException("connection error creating tenant") + raise ConnectionException("connection error creating tenant principal") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not create tenant principal. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error creating tenant principal") + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error creating tenant principal", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error creating tenant principal. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def create_tenant_principal_credential( self, payload: TenantPrincipalCredentialCSPPayload @@ -944,16 +1101,16 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.graph_resource}/v1.0/applications/{payload.principal_app_object_id}/addPassword" try: - response = self.sdk.requests.post( + result = self.sdk.requests.post( url, json=request_body, headers=auth_header, timeout=30 ) - response.raise_for_status() - result = response.json() + result.raise_for_status() + result_json = result.json() self.update_tenant_creds( payload.tenant_id, KeyVaultCredentials( tenant_id=payload.tenant_id, - tenant_sp_key=result.get("secretText"), + tenant_sp_key=result_json.get("secretText"), tenant_sp_client_id=payload.principal_app_id, ), ) @@ -964,16 +1121,30 @@ class AzureCloudProvider(CloudProviderInterface): except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not create tenant principal credential. Connection Error", + exc_info=1, + ) + raise ConnectionException( + "connection error creating tenant principal credential" ) - raise ConnectionException("connection error creating tenant") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not create tenant principal credential. Request timed out.", + exc_info=1, + ) + raise ConnectionException( + "timout error creating tenant principal credential" + ) + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error creating tenant principal credential", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error creating tenant principal credential. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def create_admin_role_definition(self, payload: AdminRoleDefinitionCSPPayload): graph_token = self._get_tenant_admin_token( @@ -1010,16 +1181,25 @@ class AzureCloudProvider(CloudProviderInterface): except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not create admin role definition. Connection Error", exc_info=1, ) - raise ConnectionException("connection error creating tenant") + raise ConnectionException("connection error creating admin role definition") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not create admin role definition. Request timed out.", + exc_info=1, + ) + raise ConnectionException("timout error creating admin role definition") + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + response.status_code, + "azure application error creating admin role definition", + exc_info=1, + ) + raise UnknownServerException( + response.status_code, + f"azure application error creating admin role definition. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def create_principal_admin_role(self, payload: PrincipalAdminRoleCSPPayload): graph_token = self._get_tenant_admin_token( @@ -1043,24 +1223,33 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.graph_resource}/beta/roleManagement/directory/roleAssignments" try: - response = self.sdk.requests.post( + result = self.sdk.requests.post( url, headers=auth_header, json=request_body, timeout=30 ) - response.raise_for_status() - return PrincipalAdminRoleCSPResult(**response.json()) + result.raise_for_status() + return PrincipalAdminRoleCSPResult(**result.json()) except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not create principal admin role. Connection Error", exc_info=1, ) - raise ConnectionException("connection error creating tenant") + raise ConnectionException("connection error creating principal admin role") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not create principal admin role. Request timed out.", + exc_info=1, + ) + raise ConnectionException("timout error creating principal admin role") + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error creating principal admin role", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error creating principal admin role. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def create_billing_owner(self, payload: BillingOwnerCSPPayload): graph_token = self._get_tenant_principal_token( @@ -1098,24 +1287,30 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.graph_resource}/beta/roleManagement/directory/roleAssignments" try: - response = self.sdk.requests.post( - url, headers=auth_header, json=request_body - ) - response.raise_for_status() + result = self.sdk.requests.post(url, headers=auth_header, json=request_body) + result.raise_for_status() except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not assign billing owner role. Connection Error", exc_info=1, ) - raise ConnectionException("connection error creating tenant") + raise ConnectionException("connection error assigning billing owner role") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not assign billing owner role. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error assigning billing owner role") + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error assigning billing owner role", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error assigning billing owner role. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") - if response.ok: + if result.ok: return True else: raise UserProvisioningException("Could not assign billing admin role") @@ -1127,23 +1322,31 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.graph_resource}/v1.0/directoryRoles" try: - response = self.sdk.requests.get(url, headers=auth_header) - response.raise_for_status() + result = self.sdk.requests.get(url, headers=auth_header) + result.raise_for_status() except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not get billing owner role. Connection Error", exc_info=1, ) - raise ConnectionException("connection error creating tenant") + raise ConnectionException("connection error getting billing owner role") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not get billing owner role. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error getting billing owner role") + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error getting billing owner role", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error getting billing owner role. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") - if response.ok: - result = response.json() + if result.ok: + result = result.json() for role in result["value"]: if role["displayName"] == "Billing Administrator": return role["id"] @@ -1152,12 +1355,6 @@ class AzureCloudProvider(CloudProviderInterface): "Could not find Billing Administrator role ID; role may not be enabled." ) - def force_tenant_admin_pw_update(self, creds, tenant_owner_id): - # use creds to update to force password recovery? - # not sure what the endpoint/method for this is, yet - - return {} - def _get_management_service_principal(self): # we really should be using graph.microsoft.com, but i'm getting # "expired token" errors for that @@ -1251,27 +1448,34 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.graph_resource}v1.0/users" try: - response = self.sdk.requests.post( + result = self.sdk.requests.post( url, headers=auth_header, json=request_body, timeout=30 ) - response.raise_for_status() + result.raise_for_status() - return UserCSPResult(**response.json()) - - # raise UserProvisioningException(f"Failed to create user: {response.json()}") + return UserCSPResult(**result.json()) except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not create active directory user. Connection Error", exc_info=1, ) - raise ConnectionException("connection error creating tenant") + raise ConnectionException("connection error creating active directory user") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not create active directory user. Request timed out.", + exc_info=1, + ) + raise ConnectionException("timout error creating active directory user") + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error creating active directory user", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error creating active directory user. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def _update_active_directory_user_email(self, graph_token, user_id, payload): request_body = {"otherMails": [payload.email]} @@ -1283,12 +1487,12 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.graph_resource}v1.0/users/{user_id}" try: - response = self.sdk.requests.patch( + result = self.sdk.requests.patch( url, headers=auth_header, json=request_body, timeout=30 ) - response.raise_for_status() + result.raise_for_status() - if response.ok: + if result.ok: return True else: raise UserProvisioningException( @@ -1297,16 +1501,30 @@ class AzureCloudProvider(CloudProviderInterface): except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not update active directory user email. Connection Error", + exc_info=1, + ) + raise ConnectionException( + "connection error updating active directory user email" ) - raise ConnectionException("connection error creating tenant") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not update active directory user email. Request timed out.", + exc_info=1, + ) + raise ConnectionException( + "timout error updating active directory user email" + ) + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error updating active directory user email", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error updating active directory user email. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def create_user_role(self, payload: UserRoleCSPPayload): graph_token = self._get_tenant_principal_token(payload.tenant_id) @@ -1446,16 +1664,28 @@ class AzureCloudProvider(CloudProviderInterface): except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not get elevated management token. Connection Error", + exc_info=1, + ) + raise ConnectionException( + "connection error getting elevated management token" ) - raise ConnectionException("connection error creating tenant") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not get elevated management token. Request timed out.", + exc_info=1, + ) + raise ConnectionException("timout error getting elevated management token") + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error getting elevated management token", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error getting elevated management token. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") def _source_creds(self, tenant_id=None) -> KeyVaultCredentials: if tenant_id: @@ -1523,13 +1753,21 @@ class AzureCloudProvider(CloudProviderInterface): except self.sdk.requests.exceptions.ConnectionError: app.logger.error( - f"Could not create tenant. Connection Error", exc_info=1, + f"Could not get reporting data. Connection Error", exc_info=1, ) - raise ConnectionException("connection error creating tenant") + raise ConnectionException("connection error getting reporting data") except self.sdk.requests.exceptions.Timeout: app.logger.error( - f"Could not create tenant. Request timed out.", exc_info=1, + f"Could not get reporting data. Request timed out.", exc_info=1, + ) + raise ConnectionException("timout error getting reporting data") + except self.sdk.requests.exceptions.HTTPError as exc: + app.logger.error( + result.status_code, + "azure application error getting reporting data", + exc_info=1, + ) + raise UnknownServerException( + result.status_code, + f"azure application error getting reporting data. {str(exc)}", ) - raise ConnectionException("timout error creating tenant") - except self.sdk.requests.exceptions.HTTPError: - raise UnknownServerException("azure application error creating tenant") diff --git a/atst/domain/csp/cloud/exceptions.py b/atst/domain/csp/cloud/exceptions.py index 3480180f..57eddd5d 100644 --- a/atst/domain/csp/cloud/exceptions.py +++ b/atst/domain/csp/cloud/exceptions.py @@ -61,12 +61,13 @@ class UnknownServerException(GeneralCSPException): """An error occured on the CSP side (5xx) and we don't know why """ - def __init__(self, server_error): + def __init__(self, status_code, server_error): + self.status_code = status_code self.server_error = server_error @property def message(self): - return "A server error occured: {}".format(self.server_error) + return f"A server error with status code [{self.status_code}] occured: {self.server_error}" class EnvironmentCreationException(GeneralCSPException): diff --git a/atst/domain/csp/cloud/mock_cloud_provider.py b/atst/domain/csp/cloud/mock_cloud_provider.py index 9d5bcd14..a34f40d5 100644 --- a/atst/domain/csp/cloud/mock_cloud_provider.py +++ b/atst/domain/csp/cloud/mock_cloud_provider.py @@ -72,7 +72,7 @@ class MockCloudProvider(CloudProviderInterface): AUTHENTICATION_EXCEPTION = AuthenticationException("Authentication failure.") AUTHORIZATION_EXCEPTION = AuthorizationException("Not authorized.") NETWORK_EXCEPTION = ConnectionException("Network failure.") - SERVER_EXCEPTION = UnknownServerException("Not our fault.") + SERVER_EXCEPTION = UnknownServerException(500, "Not our fault.") SERVER_FAILURE_PCT = 1 NETWORK_FAILURE_PCT = 7 diff --git a/atst/models/portfolio_state_machine.py b/atst/models/portfolio_state_machine.py index 16ee5851..e77ab553 100644 --- a/atst/models/portfolio_state_machine.py +++ b/atst/models/portfolio_state_machine.py @@ -145,7 +145,7 @@ class PortfolioStateMachine( self.trigger("fail") elif self.current_state == FSMStates.FAILED: - # get the first trigger that starts with 'create_' + # get the first trigger that starts with 'resume_progress_' resume_progress_trigger = next( filter( lambda trigger: trigger.startswith("resume_progress_"), diff --git a/tests/domain/cloud/test_azure_csp.py b/tests/domain/cloud/test_azure_csp.py index 70234414..b6a71501 100644 --- a/tests/domain/cloud/test_azure_csp.py +++ b/tests/domain/cloud/test_azure_csp.py @@ -73,6 +73,23 @@ from atst.domain.csp.cloud.exceptions import UserProvisioningException BILLING_ACCOUNT_NAME = "52865e4c-52e8-5a6c-da6b-c58f0814f06f:7ea5de9d-b8ce-4901-b1c5-d864320c7b03_2019-05-31" +def mock_requests_response( + status=200, content="CONTENT", json_data=None, raise_for_status=None +): + mock_resp = Mock() + # mock raise_for_status call w/optional error + mock_resp.raise_for_status = Mock() + if raise_for_status: + mock_resp.raise_for_status.side_effect = raise_for_status + # set status code and content + mock_resp.status_code = status + mock_resp.content = content + # add json data if provided + if json_data: + mock_resp.json = mock.Mock(return_value=json_data) + return mock_resp + + 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 @@ -190,7 +207,6 @@ def test_create_policy_definition_succeeds(mock_azure: AzureCloudProvider): def test_create_tenant(mock_azure: AzureCloudProvider): - mock_result = Mock() mock_result.json.return_value = { "objectId": "0a5f4926-e3ee-4f47-a6e3-8b0a30a40e3d", @@ -198,14 +214,18 @@ def test_create_tenant(mock_azure: AzureCloudProvider): "userId": "1153801116406515559", } mock_result.status_code = 200 - + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.post.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] - payload = TenantCSPPayload( **dict( user_id="admin", @@ -223,7 +243,7 @@ def test_create_tenant(mock_azure: AzureCloudProvider): mock_azure.create_tenant(payload) with pytest.raises(ConnectionException): mock_azure.create_tenant(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.create_tenant(payload) result: TenantCSPResult = mock_azure.create_tenant(payload) @@ -242,10 +262,16 @@ def test_create_billing_profile_creation(mock_azure: AzureCloudProvider): } mock_result.status_code = 202 + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.post.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -268,7 +294,7 @@ def test_create_billing_profile_creation(mock_azure: AzureCloudProvider): mock_azure.create_billing_profile_creation(payload) with pytest.raises(ConnectionException): mock_azure.create_billing_profile_creation(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.create_billing_profile_creation(payload) body: BillingProfileCreationCSPResult = mock_azure.create_billing_profile_creation( @@ -313,10 +339,16 @@ def test_validate_billing_profile_creation(mock_azure: AzureCloudProvider): }, "type": "Microsoft.Billing/billingAccounts/billingProfiles", } + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.get.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -330,7 +362,7 @@ def test_validate_billing_profile_creation(mock_azure: AzureCloudProvider): mock_azure.create_billing_profile_verification(payload) with pytest.raises(ConnectionException): mock_azure.create_billing_profile_verification(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.create_billing_profile_verification(payload) body: BillingProfileVerificationCSPResult = mock_azure.create_billing_profile_verification( @@ -364,10 +396,16 @@ def test_create_billing_profile_tenant_access(mock_azure: AzureCloudProvider): "type": "Microsoft.Billing/billingRoleAssignments", } + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.post.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -383,7 +421,7 @@ def test_create_billing_profile_tenant_access(mock_azure: AzureCloudProvider): mock_azure.create_billing_profile_tenant_access(payload) with pytest.raises(ConnectionException): mock_azure.create_billing_profile_tenant_access(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.create_billing_profile_tenant_access(payload) body: BillingProfileTenantAccessCSPResult = mock_azure.create_billing_profile_tenant_access( @@ -407,10 +445,16 @@ def test_create_task_order_billing_creation(mock_azure: AzureCloudProvider): "Retry-After": "10", } + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.patch.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -425,7 +469,7 @@ def test_create_task_order_billing_creation(mock_azure: AzureCloudProvider): mock_azure.create_task_order_billing_creation(payload) with pytest.raises(ConnectionException): mock_azure.create_task_order_billing_creation(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.create_task_order_billing_creation(payload) body: TaskOrderBillingCreationCSPResult = mock_azure.create_task_order_billing_creation( @@ -480,10 +524,16 @@ def test_create_task_order_billing_verification(mock_azure): }, "type": "Microsoft.Billing/billingAccounts/billingProfiles", } + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.get.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -497,7 +547,7 @@ def test_create_task_order_billing_verification(mock_azure): mock_azure.create_task_order_billing_verification(payload) with pytest.raises(ConnectionException): mock_azure.create_task_order_billing_verification(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.create_task_order_billing_verification(payload) body: TaskOrderBillingVerificationCSPResult = mock_azure.create_task_order_billing_verification( @@ -527,10 +577,16 @@ def test_create_billing_instruction(mock_azure: AzureCloudProvider): "type": "Microsoft.Billing/billingAccounts/billingProfiles/billingInstructions", } + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.put.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -550,7 +606,7 @@ def test_create_billing_instruction(mock_azure: AzureCloudProvider): mock_azure.create_billing_instruction(payload) with pytest.raises(ConnectionException): mock_azure.create_billing_instruction(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.create_billing_instruction(payload) body: BillingInstructionCSPResult = mock_azure.create_billing_instruction(payload) assert body.reported_clin_name == "TO1:CLIN001" @@ -568,10 +624,16 @@ def test_create_product_purchase(mock_azure: AzureCloudProvider): "Retry-After": "10", } + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.post.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -586,7 +648,7 @@ def test_create_product_purchase(mock_azure: AzureCloudProvider): mock_azure.create_product_purchase(payload) with pytest.raises(ConnectionException): mock_azure.create_product_purchase(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.create_product_purchase(payload) body: ProductPurchaseCSPResult = mock_azure.create_product_purchase(payload) @@ -624,10 +686,16 @@ def test_create_product_purchase_verification(mock_azure): "type": "Microsoft.Billing/billingAccounts/billingProfiles/invoiceSections/products", } + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.get.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -641,7 +709,7 @@ def test_create_product_purchase_verification(mock_azure): mock_azure.create_product_purchase_verification(payload) with pytest.raises(ConnectionException): mock_azure.create_product_purchase_verification(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.create_product_purchase_verification(payload) body: ProductPurchaseVerificationCSPResult = mock_azure.create_product_purchase_verification( @@ -662,10 +730,16 @@ def test_create_tenant_principal_app(mock_azure: AzureCloudProvider): mock_result.ok = True mock_result.json.return_value = {"appId": "appId", "id": "id"} + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.post.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -678,7 +752,7 @@ def test_create_tenant_principal_app(mock_azure: AzureCloudProvider): mock_azure.create_tenant_principal_app(payload) with pytest.raises(ConnectionException): mock_azure.create_tenant_principal_app(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.create_tenant_principal_app(payload) result: TenantPrincipalAppCSPResult = mock_azure.create_tenant_principal_app( @@ -700,10 +774,16 @@ def test_create_tenant_principal(mock_azure: AzureCloudProvider): mock_result.ok = True mock_result.json.return_value = {"id": "principal_id"} + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.post.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -719,7 +799,7 @@ def test_create_tenant_principal(mock_azure: AzureCloudProvider): mock_azure.create_tenant_principal(payload) with pytest.raises(ConnectionException): mock_azure.create_tenant_principal(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.create_tenant_principal(payload) result: TenantPrincipalCSPResult = mock_azure.create_tenant_principal(payload) @@ -739,10 +819,16 @@ def test_create_tenant_principal_credential(mock_azure: AzureCloudProvider): mock_result.ok = True mock_result.json.return_value = {"secretText": "new secret key"} + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.post.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -759,7 +845,7 @@ def test_create_tenant_principal_credential(mock_azure: AzureCloudProvider): mock_azure.create_tenant_principal_credential(payload) with pytest.raises(ConnectionException): mock_azure.create_tenant_principal_credential(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.create_tenant_principal_credential(payload) result: TenantPrincipalCredentialCSPResult = mock_azure.create_tenant_principal_credential( @@ -786,10 +872,16 @@ def test_create_admin_role_definition(mock_azure: AzureCloudProvider): ] } + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.get.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] mock_azure = mock_get_secret(mock_azure) @@ -801,7 +893,7 @@ def test_create_admin_role_definition(mock_azure: AzureCloudProvider): mock_azure.create_admin_role_definition(payload) with pytest.raises(ConnectionException): mock_azure.create_admin_role_definition(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.create_admin_role_definition(payload) result: AdminRoleDefinitionCSPResult = mock_azure.create_admin_role_definition( @@ -823,10 +915,16 @@ def test_create_tenant_admin_ownership(mock_azure: AzureCloudProvider): mock_result.ok = True mock_result.json.return_value = {"id": "id"} + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.put.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -840,7 +938,7 @@ def test_create_tenant_admin_ownership(mock_azure: AzureCloudProvider): mock_azure.create_tenant_admin_ownership(payload) with pytest.raises(ConnectionException): mock_azure.create_tenant_admin_ownership(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.create_tenant_admin_ownership(payload) result: TenantAdminOwnershipCSPResult = mock_azure.create_tenant_admin_ownership( @@ -862,10 +960,16 @@ def test_create_tenant_principal_ownership(mock_azure: AzureCloudProvider): mock_result.ok = True mock_result.json.return_value = {"id": "id"} + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.put.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -879,7 +983,7 @@ def test_create_tenant_principal_ownership(mock_azure: AzureCloudProvider): mock_azure.create_tenant_principal_ownership(payload) with pytest.raises(ConnectionException): mock_azure.create_tenant_principal_ownership(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.create_tenant_principal_ownership(payload) result: TenantPrincipalOwnershipCSPResult = mock_azure.create_tenant_principal_ownership( @@ -901,10 +1005,16 @@ def test_create_principal_admin_role(mock_azure: AzureCloudProvider): mock_result.ok = True mock_result.json.return_value = {"id": "id"} + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.post.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -919,7 +1029,7 @@ def test_create_principal_admin_role(mock_azure: AzureCloudProvider): mock_azure.create_principal_admin_role(payload) with pytest.raises(ConnectionException): mock_azure.create_principal_admin_role(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.create_principal_admin_role(payload) result: PrincipalAdminRoleCSPResult = mock_azure.create_principal_admin_role( @@ -945,10 +1055,16 @@ def test_create_subscription_creation(mock_azure: AzureCloudProvider): } mock_result.json.return_value = {} + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.put.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -967,7 +1083,7 @@ def test_create_subscription_creation(mock_azure: AzureCloudProvider): mock_azure.create_subscription_creation(payload) with pytest.raises(ConnectionException): mock_azure.create_subscription_creation(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.create_subscription_creation(payload) result: SubscriptionCreationCSPResult = mock_azure.create_subscription_creation( @@ -991,10 +1107,16 @@ def test_create_subscription_verification(mock_azure: AzureCloudProvider): "subscriptionLink": "/subscriptions/60fbbb72-0516-4253-ab18-c92432ba3230" } + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.get.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -1008,7 +1130,7 @@ def test_create_subscription_verification(mock_azure: AzureCloudProvider): mock_azure.create_subscription_verification(payload) with pytest.raises(ConnectionException): mock_azure.create_subscription_verification(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.create_subscription_verification(payload) result: SuscriptionVerificationCSPResult = mock_azure.create_subscription_verification( @@ -1039,10 +1161,16 @@ def test_get_reporting_data(mock_azure: AzureCloudProvider): } mock_result.ok = True + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.post.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -1068,7 +1196,7 @@ def test_get_reporting_data(mock_azure: AzureCloudProvider): mock_azure.get_reporting_data(payload) with pytest.raises(ConnectionException): mock_azure.get_reporting_data(payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure.get_reporting_data(payload) data: CostManagementQueryCSPResult = mock_azure.get_reporting_data(payload) @@ -1082,10 +1210,16 @@ def test_get_reporting_data_malformed_payload(mock_azure: AzureCloudProvider): mock_result = Mock() mock_result.ok = True + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.post.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -1125,6 +1259,26 @@ def test_get_secret(mock_azure: AzureCloudProvider): assert mock_azure.get_secret("secret key") == "my secret" +def test_get_secret_secret_exception(mock_azure: AzureCloudProvider): + with patch.object( + AzureCloudProvider, + "_get_client_secret_credential_obj", + wraps=mock_azure._get_client_secret_credential_obj, + ) as _get_client_secret_credential_obj: + _get_client_secret_credential_obj.return_value = {} + + mock_azure.sdk.secrets.SecretClient.return_value.get_secret.return_value = ( + "my secret" + ) + + mock_azure.sdk.secrets.SecretClient.return_value.get_secret.side_effect = [ + mock_azure.sdk.azure_exceptions.HttpResponseError, + ] + + with pytest.raises(SecretException): + mock_azure.get_secret("secret key") == "my secret" + + def test_set_secret(mock_azure: AzureCloudProvider): with patch.object( AzureCloudProvider, @@ -1136,18 +1290,43 @@ def test_set_secret(mock_azure: AzureCloudProvider): mock_azure.sdk.secrets.SecretClient.return_value.set_secret.return_value = ( "my secret" ) - assert mock_azure.set_secret("secret key", "secret_value") == "my secret" +def test_set_secret_secret_exception(mock_azure: AzureCloudProvider): + with patch.object( + AzureCloudProvider, + "_get_client_secret_credential_obj", + wraps=mock_azure._get_client_secret_credential_obj, + ) as _get_client_secret_credential_obj: + _get_client_secret_credential_obj.return_value = {} + + mock_azure.sdk.secrets.SecretClient.return_value.set_secret.return_value = ( + "my secret" + ) + + mock_azure.sdk.secrets.SecretClient.return_value.set_secret.side_effect = [ + mock_azure.sdk.azure_exceptions.HttpResponseError, + ] + + with pytest.raises(SecretException): + mock_azure.set_secret("secret key", "secret_value") + + def test_create_active_directory_user(mock_azure: AzureCloudProvider): mock_result = Mock() mock_result.ok = True mock_result.json.return_value = {"id": "id"} + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.post.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] @@ -1162,7 +1341,7 @@ def test_create_active_directory_user(mock_azure: AzureCloudProvider): mock_azure._create_active_directory_user("token", payload) with pytest.raises(ConnectionException): mock_azure._create_active_directory_user("token", payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure._create_active_directory_user("token", payload) result = mock_azure._create_active_directory_user("token", payload) @@ -1173,10 +1352,16 @@ def test_create_active_directory_user(mock_azure: AzureCloudProvider): def test_update_active_directory_user_email(mock_azure: AzureCloudProvider): mock_result = Mock() mock_result.ok = True + mock_http_error_resp = mock_requests_response( + status=500, + raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError( + "500 Server Error" + ), + ) mock_azure.sdk.requests.patch.side_effect = [ mock_azure.sdk.requests.exceptions.ConnectionError, mock_azure.sdk.requests.exceptions.Timeout, - mock_azure.sdk.requests.exceptions.HTTPError, + mock_http_error_resp, mock_result, ] payload = UserCSPPayload( @@ -1190,7 +1375,7 @@ def test_update_active_directory_user_email(mock_azure: AzureCloudProvider): mock_azure._update_active_directory_user_email("token", uuid4().hex, payload) with pytest.raises(ConnectionException): mock_azure._update_active_directory_user_email("token", uuid4().hex, payload) - with pytest.raises(UnknownServerException): + with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"): mock_azure._update_active_directory_user_email("token", uuid4().hex, payload) result = mock_azure._update_active_directory_user_email( diff --git a/tests/domain/test_portfolio_state_machine.py b/tests/domain/test_portfolio_state_machine.py index 2feafdf14..d785f8a3 100644 --- a/tests/domain/test_portfolio_state_machine.py +++ b/tests/domain/test_portfolio_state_machine.py @@ -143,11 +143,9 @@ def test_fail_stage(portfolio): sm.state = FSMStates.TENANT_IN_PROGRESS sm.fail_stage("tenant") assert sm.state == FSMStates.TENANT_FAILED - # import ipdb;ipdb.set_trace() def test_stage_state_to_stage_name(): - # sm = PortfolioStateMachineFactory.create(portfolio=portfolio) stage = _stage_state_to_stage_name( FSMStates.TENANT_IN_PROGRESS, StageStates.IN_PROGRESS ) diff --git a/tests/mock_azure.py b/tests/mock_azure.py index f3cf03c1..2ca2a547 100644 --- a/tests/mock_azure.py +++ b/tests/mock_azure.py @@ -69,7 +69,7 @@ def mock_policy(): def mock_azure_exceptions(): from azure.core import exceptions - return Mock(spec=exceptions) + return exceptions def mock_adal(): From 077c68ce870f22bb96755c57d24e2d836395ef6a Mon Sep 17 00:00:00 2001 From: Philip Kalinsky Date: Thu, 13 Feb 2020 13:22:28 -0500 Subject: [PATCH 09/10] state machine test corrected --- tests/domain/test_portfolio_state_machine.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/domain/test_portfolio_state_machine.py b/tests/domain/test_portfolio_state_machine.py index d2fe6b94..2784518f 100644 --- a/tests/domain/test_portfolio_state_machine.py +++ b/tests/domain/test_portfolio_state_machine.py @@ -94,6 +94,7 @@ def test_build_transitions(): ["TENANT", "FAILED"], ] assert [t.get("trigger") for t in transitions] == [ + "complete", "create_tenant", "finish_tenant", "fail_tenant", From e41fd63c2de24b2ea10143045948d722fc70ed3f Mon Sep 17 00:00:00 2001 From: Philip Kalinsky Date: Thu, 13 Feb 2020 14:00:47 -0500 Subject: [PATCH 10/10] state machine fix failing test --- tests/domain/test_portfolio_state_machine.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/domain/test_portfolio_state_machine.py b/tests/domain/test_portfolio_state_machine.py index 2784518f..a91a63c3 100644 --- a/tests/domain/test_portfolio_state_machine.py +++ b/tests/domain/test_portfolio_state_machine.py @@ -132,6 +132,7 @@ def test_attach_machine(portfolio): "start", "reset", "fail", + "complete", "create_tenant", "finish_tenant", "fail_tenant",