diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index 137ab980cdf..b8dbe4d02a2 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -788,7 +788,7 @@ ## budgets
-26% implemented +30% implemented - [X] create_budget - [ ] create_budget_action @@ -809,9 +809,6 @@ - [X] describe_notifications_for_budget - [ ] describe_subscribers_for_notification - [ ] execute_budget_action -- [ ] list_tags_for_resource -- [ ] tag_resource -- [ ] untag_resource - [ ] update_budget - [ ] update_budget_action - [ ] update_notification @@ -1347,7 +1344,6 @@ - [ ] put_webhook - [ ] register_webhook_with_third_party - [ ] retry_stage_execution -- [ ] rollback_stage - [ ] start_pipeline_execution - [ ] stop_pipeline_execution - [X] tag_resource @@ -2568,7 +2564,6 @@ - [ ] get_host_reservation_purchase_preview - [ ] get_image_block_public_access_state - [ ] get_instance_metadata_defaults -- [ ] get_instance_tpm_ek_pub - [ ] get_instance_types_from_instance_requirements - [ ] get_instance_uefi_data - [ ] get_ipam_address_history @@ -3272,12 +3267,11 @@ ## emr-containers
-34% implemented +40% implemented - [X] cancel_job_run - [ ] create_job_template - [ ] create_managed_endpoint -- [ ] create_security_configuration - [X] create_virtual_cluster - [ ] delete_job_template - [ ] delete_managed_endpoint @@ -3285,13 +3279,11 @@ - [X] describe_job_run - [ ] describe_job_template - [ ] describe_managed_endpoint -- [ ] describe_security_configuration - [X] describe_virtual_cluster - [ ] get_managed_endpoint_session_credentials - [X] list_job_runs - [ ] list_job_templates - [ ] list_managed_endpoints -- [ ] list_security_configurations - [ ] list_tags_for_resource - [X] list_virtual_clusters - [X] start_job_run @@ -5460,9 +5452,9 @@ ## panorama
-23% implemented +32% implemented -- [ ] create_application_instance +- [X] create_application_instance - [ ] create_job_for_devices - [X] create_node_from_template_job - [ ] create_package @@ -5470,7 +5462,7 @@ - [X] delete_device - [ ] delete_package - [ ] deregister_package_version -- [ ] describe_application_instance +- [X] describe_application_instance - [ ] describe_application_instance_details - [X] describe_device - [ ] describe_device_job @@ -5481,7 +5473,7 @@ - [ ] describe_package_version - [ ] list_application_instance_dependencies - [ ] list_application_instance_node_instances -- [ ] list_application_instances +- [X] list_application_instances - [X] list_devices - [ ] list_devices_jobs - [ ] list_node_from_template_jobs @@ -5505,7 +5497,6 @@ - [ ] create_batch_inference_job - [ ] create_batch_segment_job - [ ] create_campaign -- [ ] create_data_deletion_job - [ ] create_dataset - [ ] create_dataset_export_job - [ ] create_dataset_group @@ -5530,7 +5521,6 @@ - [ ] describe_batch_inference_job - [ ] describe_batch_segment_job - [ ] describe_campaign -- [ ] describe_data_deletion_job - [ ] describe_dataset - [ ] describe_dataset_export_job - [ ] describe_dataset_group @@ -5548,7 +5538,6 @@ - [ ] list_batch_inference_jobs - [ ] list_batch_segment_jobs - [ ] list_campaigns -- [ ] list_data_deletion_jobs - [ ] list_dataset_export_jobs - [ ] list_dataset_groups - [ ] list_dataset_import_jobs @@ -5881,7 +5870,6 @@ - [ ] update_public_sharing_settings - [ ] update_refresh_schedule - [ ] update_role_custom_permission -- [ ] update_spice_capacity_configuration - [ ] update_template - [ ] update_template_alias - [ ] update_template_permissions @@ -6353,7 +6341,7 @@ ## resiliencehub
-30% implemented +31% implemented - [ ] add_draft_app_version_resource_mappings - [ ] batch_update_recommendation_status @@ -6381,7 +6369,6 @@ - [X] import_resources_to_draft_app_version - [ ] list_alarm_recommendations - [ ] list_app_assessment_compliance_drifts -- [ ] list_app_assessment_resource_drifts - [X] list_app_assessments - [ ] list_app_component_compliances - [ ] list_app_component_recommendations @@ -7697,7 +7684,6 @@ - [ ] describe_instance_patch_states - [ ] describe_instance_patch_states_for_patch_group - [ ] describe_instance_patches -- [ ] describe_instance_properties - [ ] describe_inventory_deletions - [ ] describe_maintenance_window_execution_task_invocations - [ ] describe_maintenance_window_execution_tasks @@ -7875,7 +7861,7 @@ ## stepfunctions
-54% implemented +55% implemented - [ ] create_activity - [X] create_state_machine @@ -7913,7 +7899,6 @@ - [X] update_map_run - [X] update_state_machine - [ ] update_state_machine_alias -- [ ] validate_state_machine_definition
## sts diff --git a/docs/docs/services/budgets.rst b/docs/docs/services/budgets.rst index 236b80b6be0..56d8721b529 100644 --- a/docs/docs/services/budgets.rst +++ b/docs/docs/services/budgets.rst @@ -43,9 +43,6 @@ budgets - [ ] describe_subscribers_for_notification - [ ] execute_budget_action -- [ ] list_tags_for_resource -- [ ] tag_resource -- [ ] untag_resource - [ ] update_budget - [ ] update_budget_action - [ ] update_notification diff --git a/docs/docs/services/codepipeline.rst b/docs/docs/services/codepipeline.rst index 4a53c967ebc..9247933f16a 100644 --- a/docs/docs/services/codepipeline.rst +++ b/docs/docs/services/codepipeline.rst @@ -47,7 +47,6 @@ codepipeline - [ ] put_webhook - [ ] register_webhook_with_third_party - [ ] retry_stage_execution -- [ ] rollback_stage - [ ] start_pipeline_execution - [ ] stop_pipeline_execution - [X] tag_resource diff --git a/docs/docs/services/ec2.rst b/docs/docs/services/ec2.rst index e5745efcba7..e6c917d3250 100644 --- a/docs/docs/services/ec2.rst +++ b/docs/docs/services/ec2.rst @@ -495,7 +495,6 @@ ec2 - [ ] get_host_reservation_purchase_preview - [ ] get_image_block_public_access_state - [ ] get_instance_metadata_defaults -- [ ] get_instance_tpm_ek_pub - [ ] get_instance_types_from_instance_requirements - [ ] get_instance_uefi_data - [ ] get_ipam_address_history diff --git a/docs/docs/services/emr-containers.rst b/docs/docs/services/emr-containers.rst index 888b7cc8456..779f0d27f54 100644 --- a/docs/docs/services/emr-containers.rst +++ b/docs/docs/services/emr-containers.rst @@ -19,7 +19,6 @@ emr-containers - [X] cancel_job_run - [ ] create_job_template - [ ] create_managed_endpoint -- [ ] create_security_configuration - [X] create_virtual_cluster - [ ] delete_job_template - [ ] delete_managed_endpoint @@ -27,13 +26,11 @@ emr-containers - [X] describe_job_run - [ ] describe_job_template - [ ] describe_managed_endpoint -- [ ] describe_security_configuration - [X] describe_virtual_cluster - [ ] get_managed_endpoint_session_credentials - [X] list_job_runs - [ ] list_job_templates - [ ] list_managed_endpoints -- [ ] list_security_configurations - [ ] list_tags_for_resource - [X] list_virtual_clusters - [X] start_job_run diff --git a/docs/docs/services/panorama.rst b/docs/docs/services/panorama.rst index 807e05d105f..0faea429af9 100644 --- a/docs/docs/services/panorama.rst +++ b/docs/docs/services/panorama.rst @@ -14,7 +14,7 @@ panorama |start-h3| Implemented features for this service |end-h3| -- [ ] create_application_instance +- [X] create_application_instance - [ ] create_job_for_devices - [X] create_node_from_template_job - [ ] create_package @@ -22,7 +22,7 @@ panorama - [X] delete_device - [ ] delete_package - [ ] deregister_package_version -- [ ] describe_application_instance +- [X] describe_application_instance - [ ] describe_application_instance_details - [X] describe_device - [ ] describe_device_job @@ -33,7 +33,7 @@ panorama - [ ] describe_package_version - [ ] list_application_instance_dependencies - [ ] list_application_instance_node_instances -- [ ] list_application_instances +- [X] list_application_instances - [X] list_devices - [ ] list_devices_jobs - [ ] list_node_from_template_jobs diff --git a/docs/docs/services/personalize.rst b/docs/docs/services/personalize.rst index 756a25694b4..2a07c5726d7 100644 --- a/docs/docs/services/personalize.rst +++ b/docs/docs/services/personalize.rst @@ -19,7 +19,6 @@ personalize - [ ] create_batch_inference_job - [ ] create_batch_segment_job - [ ] create_campaign -- [ ] create_data_deletion_job - [ ] create_dataset - [ ] create_dataset_export_job - [ ] create_dataset_group @@ -44,7 +43,6 @@ personalize - [ ] describe_batch_inference_job - [ ] describe_batch_segment_job - [ ] describe_campaign -- [ ] describe_data_deletion_job - [ ] describe_dataset - [ ] describe_dataset_export_job - [ ] describe_dataset_group @@ -62,7 +60,6 @@ personalize - [ ] list_batch_inference_jobs - [ ] list_batch_segment_jobs - [ ] list_campaigns -- [ ] list_data_deletion_jobs - [ ] list_dataset_export_jobs - [ ] list_dataset_groups - [ ] list_dataset_import_jobs diff --git a/docs/docs/services/quicksight.rst b/docs/docs/services/quicksight.rst index 67f3aaed29a..15ff6cd9c6d 100644 --- a/docs/docs/services/quicksight.rst +++ b/docs/docs/services/quicksight.rst @@ -193,7 +193,6 @@ quicksight - [ ] update_public_sharing_settings - [ ] update_refresh_schedule - [ ] update_role_custom_permission -- [ ] update_spice_capacity_configuration - [ ] update_template - [ ] update_template_alias - [ ] update_template_permissions diff --git a/docs/docs/services/resiliencehub.rst b/docs/docs/services/resiliencehub.rst index fc1394c2f9c..1a0eddf0934 100644 --- a/docs/docs/services/resiliencehub.rst +++ b/docs/docs/services/resiliencehub.rst @@ -48,7 +48,6 @@ resiliencehub - [X] import_resources_to_draft_app_version - [ ] list_alarm_recommendations - [ ] list_app_assessment_compliance_drifts -- [ ] list_app_assessment_resource_drifts - [X] list_app_assessments - [ ] list_app_component_compliances - [ ] list_app_component_recommendations diff --git a/docs/docs/services/ssm.rst b/docs/docs/services/ssm.rst index dd733ff341a..24db63e85e3 100644 --- a/docs/docs/services/ssm.rst +++ b/docs/docs/services/ssm.rst @@ -85,7 +85,6 @@ ssm - [ ] describe_instance_patch_states - [ ] describe_instance_patch_states_for_patch_group - [ ] describe_instance_patches -- [ ] describe_instance_properties - [ ] describe_inventory_deletions - [ ] describe_maintenance_window_execution_task_invocations - [ ] describe_maintenance_window_execution_tasks diff --git a/docs/docs/services/stepfunctions.rst b/docs/docs/services/stepfunctions.rst index 5c01dc0fdec..067ddedeed4 100644 --- a/docs/docs/services/stepfunctions.rst +++ b/docs/docs/services/stepfunctions.rst @@ -52,5 +52,4 @@ stepfunctions - [X] update_map_run - [X] update_state_machine - [ ] update_state_machine_alias -- [ ] validate_state_machine_definition diff --git a/moto/panorama/models.py b/moto/panorama/models.py index c32589ebca6..4b17eeb7f27 100644 --- a/moto/panorama/models.py +++ b/moto/panorama/models.py @@ -13,7 +13,7 @@ arn_formatter, deep_convert_datetime_to_isoformat, generate_package_id, - hash_device_name, + hash_name, ) from moto.utilities.paginator import paginate @@ -34,6 +34,12 @@ "limit_default": 123, "unique_attribute": "package_id", }, + "list_application_instances": { + "input_token": "next_token", + "limit_key": "max_results", + "limit_default": 123, + "unique_attribute": "application_instance_id", + }, } @@ -104,7 +110,7 @@ def __init__( "utf-8" ) self.arn = arn_formatter("device", self.name, self.account_id, self.region_name) - self.device_id = f"device-{hash_device_name(name)}" + self.device_id = f"device-{hash_name(name)}" self.iot_thing_name = "" self.alternate_softwares = [ @@ -253,7 +259,7 @@ def __init__( self.package_version = package_version self.output_package_name = f"{self.package_name}-{self.package_version}-{self.patch_version[:8]}-{self.name}" self.owner_account = account_id - self.package_id = f"package-{hash_device_name(package_name)}" + self.package_id = f"package-{hash_name(package_name)}" self.package_arn = arn_formatter( "package", self.package_id, account_id, region_name ) @@ -268,6 +274,81 @@ def response_listed(self) -> Dict[str, Any]: return package_response +class ApplicationInstance(BaseObject): + def __init__( + self, + account_id: str, + region_name: str, + default_runtime_context_device: str, + default_runtime_context_device_name: str, + description: str, + manifest_overrides_payload: Dict[str, str], + manifest_payload: Dict[str, str], + name: str, + runtime_role_arn: str, + tags: Dict[str, str], + ) -> None: + self.default_runtime_context_device = default_runtime_context_device + self.default_runtime_context_device_name = default_runtime_context_device_name + self.description = description + self.manifest_overrides_payload = manifest_overrides_payload + self.manifest_payload = manifest_payload + self.name = name + self.runtime_role_arn = runtime_role_arn + self.tags = tags + now = datetime.now(tzutc()) + self.created_time = now + self.last_updated_time = now + name = f"{self.name}-{self.created_time}" + self.application_instance_id = f"applicationInstance-{hash_name(name).lower()}" + self.arn = arn_formatter( + "application-instance", + self.application_instance_id, + account_id=account_id, + region_name=region_name, + ) + self.health_status = "RUNNING" + self.status = "DEPLOYMENT_SUCCEEDED" + self.status_description = "string" + self.runtime_context_states = [ + { + "DesiredState": "RUNNING", + "DeviceReportedStatus": "RUNNING", + "DeviceReportedTime": now, + "RuntimeContextName": "string", + }, + ] + + def add_new_runtime_context_states( + self, desired_state: str, device_reported_status: str + ) -> None: + now = datetime.now(tzutc()) + self.runtime_context_states.append( + { + "DesiredState": desired_state, + "DeviceReportedStatus": device_reported_status, + "DeviceReportedTime": now, + "RuntimeContextName": "string", + } + ) + + def response_object(self) -> Dict[str, Any]: + response_object = super().gen_response_object() + response_object = deep_convert_datetime_to_isoformat(response_object) + return response_object + + def response_listed(self) -> Dict[str, Any]: + package_response = self.response_object() + return package_response + + def response_created(self) -> Dict[str, str]: + return {"ApplicationInstanceId": self.application_instance_id} + + def response_describe(self) -> Dict[str, str]: + response_object = self.response_object() + return response_object + + class Node(BaseObject): def __init__( self, @@ -320,6 +401,7 @@ def __init__(self, region_name: str, account_id: str): self.devices_memory: Dict[str, Device] = {} self.node_from_template_memory: Dict[str, Node] = {} self.nodes_memory: Dict[str, Package] = {} + self.application_instances_memory: Dict[str, ApplicationInstance] = {} def provision_device( self, @@ -434,6 +516,74 @@ def list_nodes(self, category: str) -> List[Package]: ) return category_nodes + def create_application_instance( + self, + application_instance_id_to_replace: Optional[str], + default_runtime_context_device: str, + description: str, + manifest_overrides_payload: Dict[str, str], + manifest_payload: Dict[str, str], + name: str, + runtime_role_arn: str, + tags: Dict[str, str], + ) -> ApplicationInstance: + device = self.devices_memory.get(default_runtime_context_device) + if device is None: + raise ValidationError(f"Device {default_runtime_context_device} not found") + if ( + application_instance_id_to_replace + and application_instance_id_to_replace in self.application_instances_memory + ): + removed_application_instance = self.application_instances_memory[ + application_instance_id_to_replace + ] + removed_application_instance.status = "REMOVAL_SUCCEEDED" + removed_application_instance.add_new_runtime_context_states( + desired_state="REMOVED", device_reported_status="REMOVAL_IN_PROGRESS" + ) + + application_instance = ApplicationInstance( + account_id=self.account_id, + region_name=self.region_name, + default_runtime_context_device=default_runtime_context_device, + default_runtime_context_device_name=device.name, + description=description, + manifest_overrides_payload=manifest_overrides_payload, + manifest_payload=manifest_payload, + name=name, + runtime_role_arn=runtime_role_arn, + tags=tags, + ) + self.application_instances_memory[ + application_instance.application_instance_id + ] = application_instance + return application_instance + + def describe_application_instance( + self, application_instance_id: str + ) -> ApplicationInstance: + application_instance = self.application_instances_memory[ + application_instance_id + ] + return application_instance + + @paginate(pagination_model=PAGINATION_MODEL) + def list_application_instances( + self, + device_id: Optional[str], + status_filter: Optional[str], + ) -> List[ApplicationInstance]: + filtered_application_instances = filter( + lambda x: x.status == status_filter if status_filter else True, + filter( + lambda x: x.default_runtime_context_device == device_id + if device_id + else True, + self.application_instances_memory.values(), + ), + ) + return list(filtered_application_instances) + panorama_backends = BackendDict( PanoramaBackend, diff --git a/moto/panorama/responses.py b/moto/panorama/responses.py index a7cd19d8831..40ad464883c 100644 --- a/moto/panorama/responses.py +++ b/moto/panorama/responses.py @@ -108,3 +108,59 @@ def list_nodes(self) -> str: "NextToken": next_token, } ) + + def create_application_instance(self) -> str: + application_instance_id_to_replace = self._get_param( + "ApplicationInstanceIdToReplace" + ) + default_runtime_context_device = self._get_param("DefaultRuntimeContextDevice") + description = self._get_param("Description") + manifest_overrides_payload = self._get_param("ManifestOverridesPayload") + manifest_payload = self._get_param("ManifestPayload") + name = self._get_param("Name") + runtime_role_arn = self._get_param("RuntimeRoleArn") + tags = self._get_param("Tags") + application_instance = self.panorama_backend.create_application_instance( + application_instance_id_to_replace=application_instance_id_to_replace, + default_runtime_context_device=default_runtime_context_device, + description=description, + manifest_overrides_payload=manifest_overrides_payload, + manifest_payload=manifest_payload, + name=name, + runtime_role_arn=runtime_role_arn, + tags=tags, + ) + return json.dumps(application_instance.response_created()) + + def describe_application_instance(self) -> str: + application_instance_id = self._get_param("ApplicationInstanceId") + application_instance = self.panorama_backend.describe_application_instance( + application_instance_id=application_instance_id + ) + return json.dumps(application_instance.response_describe()) + + def describe_application_instance_details(self) -> str: + return self.describe_application_instance() + + def list_application_instances(self) -> str: + device_id = self._get_param("deviceId") + max_results = self._get_int_param("maxResults") + status_filter = self._get_param("statusFilter") + next_token = self._get_param("nextToken") + list_application_instances, next_token = ( + self.panorama_backend.list_application_instances( + device_id=device_id, + max_results=max_results, + status_filter=status_filter, + next_token=next_token, + ) + ) + return json.dumps( + { + "ApplicationInstances": [ + application_instance.response_describe() + for application_instance in list_application_instances + ], + "NextToken": next_token, + } + ) diff --git a/moto/panorama/urls.py b/moto/panorama/urls.py index cddfa4f4210..90b8ef565c3 100644 --- a/moto/panorama/urls.py +++ b/moto/panorama/urls.py @@ -11,4 +11,7 @@ "{0}/packages/template-job$": PanoramaResponse.dispatch, "{0}/packages/template-job/(?P[^/]+)$": PanoramaResponse.dispatch, "{0}/nodes$": PanoramaResponse.dispatch, + "{0}/application-instances$": PanoramaResponse.dispatch, + "{0}/application-instances/(?P[^/]+)$": PanoramaResponse.dispatch, + "{0}/application-instances/(?P[^/]+)/details$": PanoramaResponse.dispatch, } diff --git a/moto/panorama/utils.py b/moto/panorama/utils.py index d5c676d6323..0aa33875747 100644 --- a/moto/panorama/utils.py +++ b/moto/panorama/utils.py @@ -15,10 +15,10 @@ def deep_convert_datetime_to_isoformat(obj: Any) -> Any: return obj -def hash_device_name(name: str) -> str: +def hash_name(name: str) -> str: digest = hashlib.md5(name.encode("utf-8")).digest() - token = base64.b64encode(digest) - return token.decode("utf-8") + token = base64.urlsafe_b64encode(digest) + return token.decode("utf-8").rstrip("=") def generate_package_id(name: str) -> str: diff --git a/tests/test_panorama/test_application_instance.py b/tests/test_panorama/test_application_instance.py new file mode 100644 index 00000000000..711cea62c59 --- /dev/null +++ b/tests/test_panorama/test_application_instance.py @@ -0,0 +1,394 @@ +import datetime +from unittest import SkipTest +from unittest.mock import ANY + +import boto3 +from dateutil.tz import tzutc +from freezegun import freeze_time + +from moto import mock_aws, settings + +GIVEN_DEVICE_NAME = "not-a-device-name" +PROVISION_DEVICE_PARAMS = { + "Description": "not a device description", + "Name": GIVEN_DEVICE_NAME, + "NetworkingConfiguration": { + "Ethernet0": { + "ConnectionType": "STATIC_IP", + "StaticIpConnectionInfo": { + "DefaultGateway": "192.168.1.1", + "Dns": [ + "8.8.8.8", + ], + "IpAddress": "192.168.1.10", + "Mask": "255.255.255.0", + }, + }, + "Ethernet1": { + "ConnectionType": "dhcp", + }, + "Ntp": { + "NtpServers": [ + "0.pool.ntp.org", + "1.pool.ntp.org", + "0.fr.pool.ntp.org", + ] + }, + }, + "Tags": {"Key": "test-key", "Value": "test-value"}, +} + + +@mock_aws +def test_create_application_instance() -> None: + # Given + panorama_client = boto3.client("panorama") + response_device_creation = panorama_client.provision_device( + **PROVISION_DEVICE_PARAMS + ) + + # When + response = panorama_client.create_application_instance( + DefaultRuntimeContextDevice=response_device_creation["DeviceId"], + Description="not a description", + ManifestOverridesPayload={ + "PayloadData": "dumped_payload_data", + }, + ManifestPayload={ + "PayloadData": "dumped_manifest_payload_data", + }, + Name="not-a-name", + RuntimeRoleArn="not-an-arn", + Tags={ + "Key": "value", + }, + ) + # Then + assert isinstance(response["ApplicationInstanceId"], str) + assert response["ApplicationInstanceId"].startswith("applicationInstance-") + assert len(response["ApplicationInstanceId"]) == 42 + + +@mock_aws +def test_describe_application_instance() -> None: + # Given + panorama_client = boto3.client("panorama") + response_device_creation = panorama_client.provision_device( + **PROVISION_DEVICE_PARAMS + ) + given_application_instance_name = "not-a-name" + given_application_instance_arn = "not-an-arn" + response_created = panorama_client.create_application_instance( + DefaultRuntimeContextDevice=response_device_creation["DeviceId"], + Description="not a description", + ManifestOverridesPayload={ + "PayloadData": "dumped_payload_data", + }, + ManifestPayload={ + "PayloadData": "dumped_manifest_payload_data", + }, + Name=given_application_instance_name, + RuntimeRoleArn=given_application_instance_arn, + Tags={ + "Key": "value", + }, + ) + + # When + response = panorama_client.describe_application_instance( + ApplicationInstanceId=response_created["ApplicationInstanceId"] + ) + + # Then + assert ( + response["ApplicationInstanceId"] == response_created["ApplicationInstanceId"] + ) + assert response.get("ApplicationInstanceIdToReplace") is None + assert response["Arn"].startswith( + "arn:aws:panorama:eu-west-1:123456789012:application-instance/" + ) + assert response["Arn"].endswith(response_created["ApplicationInstanceId"]) + assert isinstance(response["CreatedTime"], datetime.datetime) + assert ( + response["DefaultRuntimeContextDevice"] == response_device_creation["DeviceId"] + ) + assert response["DefaultRuntimeContextDeviceName"] == "not-a-device-name" + assert response["Description"] == "not a description" + assert response["HealthStatus"] == "RUNNING" + assert isinstance(response["LastUpdatedTime"], datetime.datetime) + assert response["Name"] == given_application_instance_name + assert response["RuntimeRoleArn"] == given_application_instance_arn + assert response["Status"] == "DEPLOYMENT_SUCCEEDED" + + +@mock_aws +def test_create_application_instance_should_set_created_time() -> None: + # Given + if settings.TEST_SERVER_MODE: + raise SkipTest("Can't use ManagedState in ServerMode") + panorama_client = boto3.client("panorama") + response_device_creation = panorama_client.provision_device( + **PROVISION_DEVICE_PARAMS + ) + given_application_instance_name = "not-a-name" + given_application_instance_arn = "not-an-arn" + with freeze_time("2020-01-01 12:00:00"): + response_created = panorama_client.create_application_instance( + DefaultRuntimeContextDevice=response_device_creation["DeviceId"], + Description="not a description", + ManifestOverridesPayload={ + "PayloadData": "dumped_payload_data", + }, + ManifestPayload={ + "PayloadData": "dumped_manifest_payload_data", + }, + Name=given_application_instance_name, + RuntimeRoleArn=given_application_instance_arn, + Tags={ + "Key": "value", + }, + ) + + # When + response = panorama_client.describe_application_instance( + ApplicationInstanceId=response_created["ApplicationInstanceId"] + ) + + # Then + assert response["CreatedTime"] == datetime.datetime( + 2020, 1, 1, 12, 0, 0, tzinfo=tzutc() + ) + assert response["LastUpdatedTime"] == datetime.datetime( + 2020, 1, 1, 12, 0, 0, tzinfo=tzutc() + ) + + +@mock_aws +def test_describe_application_instance_details() -> None: + # Given + panorama_client = boto3.client("panorama") + given_application_instance_name = "not-a-name" + given_application_instance_arn = "not-an-arn" + response_device_creation = panorama_client.provision_device( + **PROVISION_DEVICE_PARAMS + ) + given_manifest_payload = { + "PayloadData": "dumped_manifest_payload_data", + } + given_manifest_overrides_payload = { + "PayloadData": "dumped_payload_data", + } + response_created = panorama_client.create_application_instance( + DefaultRuntimeContextDevice=response_device_creation["DeviceId"], + Description="not a description", + ManifestOverridesPayload=given_manifest_overrides_payload, + ManifestPayload=given_manifest_payload, + Name=given_application_instance_name, + RuntimeRoleArn=given_application_instance_arn, + Tags={ + "Key": "value", + }, + ) + + # When + response = panorama_client.describe_application_instance_details( + ApplicationInstanceId=response_created["ApplicationInstanceId"] + ) + + # Then + assert ( + response["ApplicationInstanceId"] == response_created["ApplicationInstanceId"] + ) + assert response.get("ApplicationInstanceIdToReplace") is None + assert isinstance(response["CreatedTime"], datetime.datetime) + assert ( + response["DefaultRuntimeContextDevice"] == response_device_creation["DeviceId"] + ) + assert response["Description"] == "not a description" + assert response["Name"] == given_application_instance_name + assert response["ManifestPayload"] == given_manifest_payload + assert response["ManifestOverridesPayload"] == given_manifest_overrides_payload + + +@mock_aws +def test_list_application_instances() -> None: + # Given + panorama_client = boto3.client("panorama") + given_application_instance_name = "not-a-name" + given_application_instance_arn = "not-an-arn" + given_device_name = "not-a-device-name" + response_device_creation = panorama_client.provision_device( + **PROVISION_DEVICE_PARAMS + ) + response_created_1 = panorama_client.create_application_instance( + DefaultRuntimeContextDevice=response_device_creation["DeviceId"], + Description="not a description", + ManifestOverridesPayload={ + "PayloadData": "dumped_payload_data", + }, + ManifestPayload={ + "PayloadData": "dumped_manifest_payload_data", + }, + Name=given_application_instance_name, + RuntimeRoleArn=given_application_instance_arn, + Tags={ + "Key": "value", + }, + ) + response_created_2 = panorama_client.create_application_instance( + ApplicationInstanceIdToReplace=response_created_1["ApplicationInstanceId"], + DefaultRuntimeContextDevice=response_device_creation["DeviceId"], + Description="not a description", + ManifestOverridesPayload={ + "PayloadData": "dumped_payload_data", + }, + ManifestPayload={ + "PayloadData": "dumped_manifest_payload_data", + }, + Name=given_application_instance_name, + RuntimeRoleArn=given_application_instance_arn, + Tags={ + "Key": "value", + }, + ) + + # When + response_1 = panorama_client.list_application_instances( + DeviceId=response_device_creation["DeviceId"], + MaxResults=1, + ) + response_2 = panorama_client.list_application_instances( + DeviceId=response_device_creation["DeviceId"], + MaxResults=1, + NextToken=response_1["NextToken"], + ) + + # Then + assert len(response_1["ApplicationInstances"]) == 1 + assert ( + response_1["ApplicationInstances"][0]["ApplicationInstanceId"] + == response_created_1["ApplicationInstanceId"] + ) + assert response_1["ApplicationInstances"][0]["Arn"].startswith( + "arn:aws:panorama:eu-west-1:123456789012:application-instance/" + ) + assert response_1["ApplicationInstances"][0]["Arn"].endswith( + response_created_1["ApplicationInstanceId"] + ) + assert isinstance( + response_1["ApplicationInstances"][0]["CreatedTime"], datetime.datetime + ) + assert ( + response_1["ApplicationInstances"][0]["DefaultRuntimeContextDevice"] + == response_device_creation["DeviceId"] + ) + assert ( + response_1["ApplicationInstances"][0]["DefaultRuntimeContextDeviceName"] + == given_device_name + ) + assert response_1["ApplicationInstances"][0]["Description"] == "not a description" + assert response_1["ApplicationInstances"][0]["HealthStatus"] == "RUNNING" + assert response_1["ApplicationInstances"][0]["Name"], response_created_1["Name"] + assert response_1["ApplicationInstances"][0]["RuntimeContextStates"] == [ + { + "DesiredState": "RUNNING", + "DeviceReportedStatus": "RUNNING", + "DeviceReportedTime": ANY, + "RuntimeContextName": "string", + }, + { + "DesiredState": "REMOVED", + "DeviceReportedStatus": "REMOVAL_IN_PROGRESS", + "DeviceReportedTime": ANY, + "RuntimeContextName": "string", + }, + ] + assert response_1["ApplicationInstances"][0]["Status"] == "REMOVAL_SUCCEEDED" + assert response_1["ApplicationInstances"][0]["StatusDescription"] == "string" + assert response_1["ApplicationInstances"][0]["Tags"] == {"Key": "value"} + assert response_1["NextToken"] is not None + + assert len(response_2["ApplicationInstances"]) == 1 + assert ( + response_2["ApplicationInstances"][0]["ApplicationInstanceId"] + == response_created_2["ApplicationInstanceId"] + ) + assert "NextToken" not in response_2 + assert response_2["ApplicationInstances"][0]["RuntimeContextStates"] == [ + { + "DesiredState": "RUNNING", + "DeviceReportedStatus": "RUNNING", + "DeviceReportedTime": ANY, + "RuntimeContextName": "string", + } + ] + + +@mock_aws +def test_list_application_should_return_only_filtered_results_if_status_filter_used() -> ( + None +): + # Given + panorama_client = boto3.client("panorama") + given_application_instance_name = "not-a-name" + given_application_instance_name_2 = "not-a-name-2" + given_application_instance_arn = "not-an-arn" + response_device_creation = panorama_client.provision_device( + **PROVISION_DEVICE_PARAMS + ) + response_created_1 = panorama_client.create_application_instance( + DefaultRuntimeContextDevice=response_device_creation["DeviceId"], + Description="not a description", + ManifestOverridesPayload={ + "PayloadData": "dumped_payload_data", + }, + ManifestPayload={ + "PayloadData": "dumped_manifest_payload_data", + }, + Name=given_application_instance_name, + RuntimeRoleArn=given_application_instance_arn, + Tags={ + "Key": "value", + }, + ) + response_created_2 = panorama_client.create_application_instance( + ApplicationInstanceIdToReplace=response_created_1["ApplicationInstanceId"], + DefaultRuntimeContextDevice=response_device_creation["DeviceId"], + Description="not a description", + ManifestOverridesPayload={ + "PayloadData": "dumped_payload_data", + }, + ManifestPayload={ + "PayloadData": "dumped_manifest_payload_data", + }, + Name=given_application_instance_name_2, + RuntimeRoleArn=given_application_instance_arn, + Tags={ + "Key": "value", + }, + ) + + # When + response_deployed = panorama_client.list_application_instances( + DeviceId=response_device_creation["DeviceId"], + MaxResults=123, + StatusFilter="DEPLOYMENT_SUCCEEDED", + ) + + response_removed = panorama_client.list_application_instances( + DeviceId=response_device_creation["DeviceId"], + MaxResults=123, + StatusFilter="REMOVAL_SUCCEEDED", + ) + + # Then + assert len(response_removed["ApplicationInstances"]) == 1 + assert ( + response_removed["ApplicationInstances"][0]["ApplicationInstanceId"] + == response_created_1["ApplicationInstanceId"] + ) + assert len(response_deployed["ApplicationInstances"]) == 1 + assert ( + response_deployed["ApplicationInstances"][0]["ApplicationInstanceId"] + == response_created_2["ApplicationInstanceId"] + ) diff --git a/tests/test_panorama/test_nodes.py b/tests/test_panorama/test_nodes.py index cc1bf637bad..bdaaf02ff29 100644 --- a/tests/test_panorama/test_nodes.py +++ b/tests/test_panorama/test_nodes.py @@ -139,9 +139,9 @@ def test_list_nodes() -> None: assert node["OwnerAccount"] == "123456789012" assert ( node["PackageArn"] - == "arn:aws:panorama:us-east-1:123456789012:package/package-+Y6+ycqU+ogoBTHuibMzGg==" + == "arn:aws:panorama:us-east-1:123456789012:package/package--Y6-ycqU-ogoBTHuibMzGg" ) - assert node["PackageId"] == "package-+Y6+ycqU+ogoBTHuibMzGg==" + assert node["PackageId"] == "package--Y6-ycqU-ogoBTHuibMzGg" assert node["PackageName"] == "not-an-output-package-name" assert node["PackageVersion"] == "1.0" assert ( @@ -154,9 +154,3 @@ def test_list_nodes() -> None: assert isinstance(response_page_2["Nodes"], list) assert len(response_page_2["Nodes"]) == 1 assert "NextToken" not in response_page_2 - - -""" -botocore.errorfactory.ValidationException: An error occurred (ValidationException) when calling the CreateNodeFromTemplateJob operation: {"reason":"FIELD_VALIDATION_FAILED","message":"The input fails to satisfy the constraints specified by an AWS service.","fields":[{"name":"TemplateParameters","message":"Unsupported template parameters: [param]"}]} -botocore.errorfactory.ValidationException: An error occurred (ValidationException) when calling the CreateNodeFromTemplateJob operation: {"Message":[ECMA 262 regex \"^([0-9]+)\\.([0-9]+)$\" does not match input string \"not-an-output-package-version\"]} -""" diff --git a/tests/test_panorama/test_panorama_device.py b/tests/test_panorama/test_panorama_device.py index 72b966fa18b..443b654ac8c 100644 --- a/tests/test_panorama/test_panorama_device.py +++ b/tests/test_panorama/test_panorama_device.py @@ -54,7 +54,7 @@ def test_provision_device() -> None: resp["Arn"] == "arn:aws:panorama:eu-west-1:123456789012:device/test-device-name" ) assert resp["Certificates"] == b"certificate" - assert resp["DeviceId"] == "device-RsozEWjZpeNe3SXHidX3mg==" + assert resp["DeviceId"] == "device-RsozEWjZpeNe3SXHidX3mg" assert resp["IotThingName"] == "" assert resp["Status"] == "AWAITING_PROVISIONING" @@ -129,7 +129,7 @@ def test_describe_device() -> None: assert resp["Description"] == "test device description" assert resp["DeviceAggregatedStatus"] == "ONLINE" assert resp["DeviceConnectionStatus"] == "ONLINE" - assert resp["DeviceId"] == "device-RsozEWjZpeNe3SXHidX3mg==" + assert resp["DeviceId"] == "device-RsozEWjZpeNe3SXHidX3mg" assert resp["LatestDeviceJob"] == {"JobType": "REBOOT", "Status": "COMPLETED"} assert resp["LatestSoftware"] == "6.2.1" assert resp["LeaseExpirationTime"] == datetime(2020, 1, 6, 12, 0, tzinfo=tzutc())