diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index cce81fbd3..601125050 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -4039,7 +4039,7 @@ - [ ] update_flow - [x] update_flow_entitlement - [ ] update_flow_media_stream -- [ ] update_flow_output +- [x] update_flow_output - [X] update_flow_source diff --git a/docs/docs/services/mediaconnect.rst b/docs/docs/services/mediaconnect.rst index f90c8d40b..3609f1614 100644 --- a/docs/docs/services/mediaconnect.rst +++ b/docs/docs/services/mediaconnect.rst @@ -53,6 +53,6 @@ mediaconnect - [ ] update_flow - [x] update_flow_entitlement - [ ] update_flow_media_stream -- [ ] update_flow_output +- [x] update_flow_output - [X] update_flow_source diff --git a/moto/mediaconnect/models.py b/moto/mediaconnect/models.py index ffc4a243b..6e78adcd1 100644 --- a/moto/mediaconnect/models.py +++ b/moto/mediaconnect/models.py @@ -107,6 +107,12 @@ class MediaConnectBackend(BaseBackend): for index, output in enumerate(flow.outputs or []): if output.get("protocol") in ["srt-listener", "zixi-pull"]: output["listenerAddress"] = f"{index}.0.0.0" + output_id = random.uuid4().hex + arn = ( + f"arn:aws:mediaconnect:{self.region_name}" + f":{self.account_id}:output:{output_id}:{output['name']}" + ) + output["outputArn"] = arn for _, entitlement in enumerate(flow.entitlements): entitlement_id = random.uuid4().hex @@ -253,6 +259,55 @@ class MediaConnectBackend(BaseBackend): ) return flow_arn, output_name + def update_flow_output( + self, + flow_arn, + output_arn, + cidr_allow_list, + description, + destination, + encryption, + max_latency, + media_stream_output_configuration, + min_latency, + port, + protocol, + remote_id, + sender_control_port, + sender_ip_address, + smoothing_latency, + stream_id, + vpc_interface_attachment, + ): + if flow_arn not in self._flows: + raise NotFoundException( + message="flow with arn={} not found".format(flow_arn) + ) + flow = self._flows[flow_arn] + for output in flow.outputs: + if output["outputArn"] == output_arn: + output["cidrAllowList"] = cidr_allow_list + output["description"] = description + output["destination"] = destination + output["encryption"] = encryption + output["maxLatency"] = max_latency + output[ + "mediaStreamOutputConfiguration" + ] = media_stream_output_configuration + output["minLatency"] = min_latency + output["port"] = port + output["protocol"] = protocol + output["remoteId"] = remote_id + output["senderControlPort"] = sender_control_port + output["senderIpAddress"] = sender_ip_address + output["smoothingLatency"] = smoothing_latency + output["streamId"] = stream_id + output["vpcInterfaceAttachment"] = vpc_interface_attachment + return flow_arn, output + raise NotFoundException( + message="output with arn={} not found".format(output_arn) + ) + def add_flow_sources(self, flow_arn, sources): if flow_arn not in self._flows: raise NotFoundException( diff --git a/moto/mediaconnect/responses.py b/moto/mediaconnect/responses.py index 7e1687f15..cf0f76642 100644 --- a/moto/mediaconnect/responses.py +++ b/moto/mediaconnect/responses.py @@ -113,6 +113,47 @@ class MediaConnectResponse(BaseResponse): ) return json.dumps(dict(flow_arn=flow_arn, output_name=output_name)) + def update_flow_output(self): + flow_arn = unquote(self._get_param("flowArn")) + output_arn = unquote(self._get_param("outputArn")) + cidr_allow_list = self._get_param("cidrAllowList") + description = self._get_param("description") + destination = self._get_param("destination") + encryption = self._get_param("encryption") + max_latency = self._get_param("maxLatency") + media_stream_output_configuration = self._get_param( + "mediaStreamOutputConfiguration" + ) + min_latency = self._get_param("minLatency") + port = self._get_param("port") + protocol = self._get_param("protocol") + remote_id = self._get_param("remoteId") + sender_control_port = self._get_param("senderControlPort") + sender_ip_address = self._get_param("senderIpAddress") + smoothing_latency = self._get_param("smoothingLatency") + stream_id = self._get_param("streamId") + vpc_interface_attachment = self._get_param("vpcInterfaceAttachment") + flow_arn, output = self.mediaconnect_backend.update_flow_output( + flow_arn=flow_arn, + output_arn=output_arn, + cidr_allow_list=cidr_allow_list, + description=description, + destination=destination, + encryption=encryption, + max_latency=max_latency, + media_stream_output_configuration=media_stream_output_configuration, + min_latency=min_latency, + port=port, + protocol=protocol, + remote_id=remote_id, + sender_control_port=sender_control_port, + sender_ip_address=sender_ip_address, + smoothing_latency=smoothing_latency, + stream_id=stream_id, + vpc_interface_attachment=vpc_interface_attachment, + ) + return json.dumps(dict(flowArn=flow_arn, output=output)) + def add_flow_sources(self): flow_arn = unquote(self._get_param("flowArn")) sources = self._get_param("sources") diff --git a/tests/test_mediaconnect/test_mediaconnect.py b/tests/test_mediaconnect/test_mediaconnect.py index 962245aab..3f01ecdd4 100644 --- a/tests/test_mediaconnect/test_mediaconnect.py +++ b/tests/test_mediaconnect/test_mediaconnect.py @@ -87,7 +87,7 @@ def test_create_flow_succeeds(): ) response["Flow"]["Name"].should.equal("test-Flow-1") response["Flow"]["Status"].should.equal("STANDBY") - response["Flow"]["Outputs"][0].should.equal({"Name": "Output-1"}) + response["Flow"]["Outputs"][0]["Name"].should.equal("Output-1") response["Flow"]["Outputs"][1]["ListenerAddress"].should.equal("1.0.0.0") response["Flow"]["Outputs"][2]["ListenerAddress"].should.equal("2.0.0.0") response["Flow"]["Source"]["IngestIp"].should.equal("127.0.0.0") @@ -344,6 +344,38 @@ def test_add_flow_outputs_fails(): err["Message"].should.equal("flow with arn=unknown-flow not found") +@mock_mediaconnect +def test_update_flow_output_succeeds(): + client = boto3.client("mediaconnect", region_name=region) + channel_config = _create_flow_config("test-Flow-1") + + create_response = client.create_flow(**channel_config) + create_response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) + create_response["Flow"]["Status"].should.equal("STANDBY") + flow_arn = create_response["Flow"]["FlowArn"] + output_arn = create_response["Flow"]["Outputs"][0]["OutputArn"] + + update_response = client.update_flow_output( + FlowArn=flow_arn, OutputArn=output_arn, Description="new description" + ) + update_response["Output"]["Description"].should.equal("new description") + + +@mock_mediaconnect +def test_update_flow_output_fails(): + client = boto3.client("mediaconnect", region_name=region) + flow_arn = "unknown-flow" + with pytest.raises(ClientError) as err: + client.update_flow_output( + FlowArn=flow_arn, + OutputArn="some-arn", + Description="new description", + ) + err = err.value.response["Error"] + err["Code"].should.equal("NotFoundException") + err["Message"].should.equal("flow with arn=unknown-flow not found") + + @mock_mediaconnect def test_remove_flow_output_fails(): client = boto3.client("mediaconnect", region_name=region)