def test_create_streaming_dataflow_job_when_job_does_not_already_exist(
            self):
        """Test that attempting to deploy an update to a Dataflow job when a job with the name of service does not
        already exist results in the job deployment being retried with `update` set to `False`.
        """
        with tempfile.TemporaryDirectory() as temporary_directory:
            octue_configuration_path = self._create_octue_configuration_file(
                OCTUE_CONFIGURATION, temporary_directory)
            deployer = DataflowDeployer(octue_configuration_path)

            with patch(
                    "octue.cloud.deployment.google.dataflow.pipeline.Topic",
                    return_value=Mock(
                        path="projects/my-project/topics/my-topic"),
            ):
                with patch(
                        "octue.cloud.deployment.google.dataflow.pipeline.DataflowRunner.run_pipeline",
                        side_effect=[ValueError, None],
                ) as mock_runner:
                    deployer.create_streaming_dataflow_job(
                        image_uri="my-image-uri", update=True)

            # Check that the first attempt was to update the service.
            first_attempt_options = mock_runner.mock_calls[0].kwargs[
                "options"].get_all_options()
            self.assertTrue(first_attempt_options["update"])

            # Check that the second attempt was to create the service.
            second_attempt_options = mock_runner.mock_calls[1].kwargs[
                "options"].get_all_options()
            self.assertFalse(second_attempt_options["update"])
    def test_dataflow_prime_enabled_if_machine_type_not_specified(self):
        """Test that Dataflow Prime is enabled if the machine type is not specified in the octue configuration."""
        with tempfile.TemporaryDirectory() as temporary_directory:
            octue_configuration_with_no_machine_type = copy.deepcopy(
                OCTUE_CONFIGURATION)
            del octue_configuration_with_no_machine_type["services"][0][
                "machine_type"]

            octue_configuration_path = self._create_octue_configuration_file(
                octue_configuration_with_no_machine_type,
                temporary_directory,
            )

            deployer = DataflowDeployer(octue_configuration_path)

            with patch(
                    "octue.cloud.deployment.google.dataflow.pipeline.Topic",
                    return_value=Mock(
                        path="projects/my-project/topics/my-topic"),
            ):
                with patch(
                        "octue.cloud.deployment.google.dataflow.pipeline.DataflowRunner.run_pipeline"
                ) as mock_run:
                    deployer.create_streaming_dataflow_job(
                        image_uri="my-image-uri")

        self.assertEqual(
            mock_run.call_args.kwargs["options"].get_all_options()
            ["dataflow_service_options"],
            ["enable_prime"],
        )
    def test_updating_streaming_dataflow_job(self):
        """Test updating an existing streaming dataflow job."""
        with tempfile.TemporaryDirectory() as temporary_directory:
            octue_configuration_path = self._create_octue_configuration_file(
                OCTUE_CONFIGURATION, temporary_directory)
            deployer = DataflowDeployer(octue_configuration_path)

            with patch(
                    "octue.cloud.deployment.google.dataflow.pipeline.Topic",
                    return_value=Mock(
                        path="projects/my-project/topics/my-topic"),
            ):
                with patch(
                        "octue.cloud.deployment.google.dataflow.pipeline.DataflowRunner.run_pipeline"
                ) as mock_run:
                    deployer.create_streaming_dataflow_job(
                        image_uri="my-image-uri", update=True)

            options = mock_run.call_args.kwargs["options"].get_all_options()
            self.assertTrue(options["update"])
            self.assertTrue(options["streaming"])
            self.assertIsNone(options["dataflow_service_options"])
            self.assertEqual(options["project"], SERVICE["project_name"])
            self.assertEqual(options["job_name"], SERVICE["name"])
            self.assertEqual(options["temp_location"],
                             DEFAULT_DATAFLOW_TEMPORARY_FILES_LOCATION)
            self.assertEqual(options["region"], SERVICE["region"])
            self.assertEqual(options["sdk_container_image"], "my-image-uri")
            self.assertEqual(options["setup_file"], DEFAULT_SETUP_FILE_PATH)
    def test_generate_cloud_build_configuration(self):
        """Test that a correct Google Cloud Build configuration is generated from the given `octue.yaml` file."""
        with tempfile.TemporaryDirectory() as temporary_directory:
            octue_configuration_path = self._create_octue_configuration_file(
                OCTUE_CONFIGURATION, temporary_directory)
            deployer = DataflowDeployer(octue_configuration_path)

        deployer._generate_cloud_build_configuration()
        self.assertEqual(deployer.generated_cloud_build_configuration,
                         EXPECTED_CLOUD_BUILD_CONFIGURATION)
    def test_deployment_error_raised_if_dataflow_job_already_exists(self):
        """Test that a deployment error is raised if a Dataflow job already exists with the same name as the service."""
        with tempfile.TemporaryDirectory() as temporary_directory:
            octue_configuration_path = self._create_octue_configuration_file(
                OCTUE_CONFIGURATION, temporary_directory)
            deployer = DataflowDeployer(octue_configuration_path)

            with patch(
                    "octue.cloud.deployment.google.dataflow.pipeline.Topic",
                    return_value=Mock(
                        path="projects/my-project/topics/my-topic"),
            ):
                with patch(
                        "octue.cloud.deployment.google.dataflow.pipeline.DataflowRunner.run_pipeline",
                        side_effect=DataflowJobAlreadyExistsError(),
                ):
                    with self.assertRaises(DeploymentError):
                        deployer.create_streaming_dataflow_job(
                            image_uri="my-image-uri", update=True)
Example #6
0
def dataflow(service_config, no_cache, update, dataflow_job_only, image_uri):
    """Deploy a python app to Google Dataflow as an Octue service or digital twin."""
    if bool(importlib.util.find_spec("apache_beam")):
        # Import the Dataflow deployer only if the `apache-beam` package is available (due to installing `octue` with
        # the `dataflow` extras option).
        from octue.cloud.deployment.google.dataflow.deployer import DataflowDeployer
    else:
        raise ImportWarning(
            "To use this CLI command, you must install `octue` with the `dataflow` option e.g. "
            "`pip install octue[dataflow]`")

    deployer = DataflowDeployer(service_config)

    if dataflow_job_only:
        deployer.create_streaming_dataflow_job(image_uri=image_uri,
                                               update=update)
        return

    deployer.deploy(no_cache=no_cache, update=update)
    def test_deploy_with_cloud_build_file_provided(self):
        """Test deploying to Dataflow with a `cloudbuild.yaml` path provided in the `octue.yaml` file"""
        with tempfile.TemporaryDirectory() as temporary_directory:
            octue_configuration_path = self._create_octue_configuration_file(
                OCTUE_CONFIGURATION_WITH_CLOUD_BUILD_PATH,
                temporary_directory,
            )

            deployer = DataflowDeployer(octue_configuration_path,
                                        image_uri_template="blah")

            with patch("subprocess.run",
                       return_value=Mock(returncode=0)) as mock_run:
                mock_build_id = "my-build-id"

                with patch(
                        "json.loads",
                        return_value={
                            "metadata": {
                                "build": {
                                    "images": [deployer.image_uri_template],
                                    "id": mock_build_id
                                }
                            },
                            "status": "SUCCESS",
                        },
                ):
                    with patch(
                            "octue.cloud.deployment.google.dataflow.pipeline.DataflowRunner"
                    ):
                        deployer.deploy()

            # Test the build trigger creation request.
            self.assertEqual(
                mock_run.call_args_list[0].args[0],
                EXPECTED_BUILD_TRIGGER_CREATION_COMMAND + [
                    f"--build-config={OCTUE_CONFIGURATION_WITH_CLOUD_BUILD_PATH['services'][0]['cloud_build_configuration_path']}"
                ],
            )

            # Test the build trigger run request.
            self.assertEqual(
                mock_run.call_args_list[1].args[0],
                [
                    "gcloud",
                    f"--project={OCTUE_CONFIGURATION_WITH_CLOUD_BUILD_PATH['services'][0]['project_name']}",
                    "--format=json",
                    "beta",
                    "builds",
                    "triggers",
                    "run",
                    OCTUE_CONFIGURATION_WITH_CLOUD_BUILD_PATH["services"][0]
                    ["name"],
                    "--branch=my-branch",
                ],
            )

            # Test waiting for the build trigger run to complete.
            self.assertEqual(
                mock_run.call_args_list[2].args[0],
                [
                    "gcloud",
                    f'--project={SERVICE["project_name"]}',
                    "--format=json",
                    "builds",
                    "describe",
                    mock_build_id,
                ],
            )
    def test_deploy(self):
        """Test that the build trigger creation and run are requested correctly."""
        with tempfile.TemporaryDirectory() as temporary_directory:
            octue_configuration_path = self._create_octue_configuration_file(
                OCTUE_CONFIGURATION, temporary_directory)
            deployer = DataflowDeployer(octue_configuration_path)

            with patch("subprocess.run",
                       return_value=Mock(returncode=0)) as mock_run:
                mock_build_id = "my-build-id"

                with patch(
                        "json.loads",
                        return_value={
                            "metadata": {
                                "build": {
                                    "images": [deployer.image_uri_template],
                                    "id": mock_build_id
                                }
                            },
                            "status": "SUCCESS",
                        },
                ):
                    temporary_file = tempfile.NamedTemporaryFile(delete=False)

                    with patch("tempfile.NamedTemporaryFile",
                               return_value=temporary_file):
                        deployer.deploy()

            # Test the build trigger creation request.
            self.assertEqual(
                mock_run.call_args_list[0].args[0],
                EXPECTED_BUILD_TRIGGER_CREATION_COMMAND +
                [f"--inline-config={temporary_file.name}"],
            )

            # Test the build trigger run request.
            self.assertEqual(
                mock_run.call_args_list[1].args[0],
                [
                    "gcloud",
                    f"--project={SERVICE['project_name']}",
                    "--format=json",
                    "beta",
                    "builds",
                    "triggers",
                    "run",
                    SERVICE["name"],
                    "--branch=my-branch",
                ],
            )

            # Test waiting for the build trigger run to complete.
            self.assertEqual(
                mock_run.call_args_list[2].args[0],
                [
                    "gcloud",
                    f'--project={SERVICE["project_name"]}',
                    "--format=json",
                    "builds",
                    "describe",
                    mock_build_id,
                ],
            )