Skip to content

Commit

Permalink
feat: Add telemetry opt-out env var (#212)
Browse files Browse the repository at this point in the history
Signed-off-by: Caden Marofke <marofke@amazon.com>
  • Loading branch information
marofke authored Mar 15, 2024
1 parent c5bbcd3 commit 4f270ba
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 22 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ You can download this package from:

See instructions in DEVELOPMENT.md

## Telemetry

This library collects telemetry data by default. Telemetry events contain non-personally-identifiable information that helps us understand how users interact with our software so we know what features our customers use, and/or what existing pain points are.

You can opt out of telemetry data collection by either:

1. Setting the environment variable: `DEADLINE_CLOUD_TELEMETRY_OPT_OUT=true`
2. Setting the config file: `deadline config set telemetry.opt_out true`

Note that setting the environment variable supersedes the config file setting.

# Build / Test / Release

## Setup Code Artifact
Expand Down
19 changes: 16 additions & 3 deletions src/deadline/client/api/_telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import atexit
import json
import logging
import os
import platform
import uuid
import random
Expand Down Expand Up @@ -58,7 +59,8 @@ class TelemetryClient:
lifetimes on the same machine.
Telemetry collection can be opted-out of by running:
'deadline config set "telemetry.opt_out" true'
'deadline config set "telemetry.opt_out" true' or setting the environment variable
'DEADLINE_CLOUD_TELEMETRY_OPT_OUT=true'
"""

# Used for backing off requests if we encounter errors from the service.
Expand All @@ -79,11 +81,22 @@ def __init__(
package_ver: str,
config: Optional[ConfigParser] = None,
):
self.telemetry_opted_out = config_file.str2bool(
config_file.get_setting("telemetry.opt_out", config=config)
# Environment variable supersedes config file setting.
env_var_value = os.environ.get("DEADLINE_CLOUD_TELEMETRY_OPT_OUT")
if env_var_value:
self.telemetry_opted_out = env_var_value in config_file._TRUE_VALUES
else:
self.telemetry_opted_out = config_file.str2bool(
config_file.get_setting("telemetry.opt_out", config=config)
)
logger.info(
"Deadline Cloud telemetry is " + "not enabled."
if self.telemetry_opted_out
else "enabled."
)
if self.telemetry_opted_out:
return

self.package_name = package_name
self.package_ver = ".".join(package_ver.split(".")[:3])
self.endpoint: str = self._get_prefixed_endpoint(
Expand Down
36 changes: 35 additions & 1 deletion test/unit/deadline_client/api/test_api_telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def fixture_telemetry_client(fresh_deadline_config):
)


def test_opt_out(fresh_deadline_config):
def test_opt_out_config(fresh_deadline_config):
"""Ensures the telemetry client doesn't fully initialize if the opt out config setting is set"""
# GIVEN
config.set_setting("defaults.aws_profile_name", "SomeRandomProfileName")
Expand All @@ -50,6 +50,40 @@ def test_opt_out(fresh_deadline_config):
client.record_error({}, str(type(Exception)))


@pytest.mark.parametrize(
"env_var_value",
[
pytest.param("true"),
pytest.param("1"),
pytest.param("yes"),
pytest.param("on"),
],
)
def test_opt_out_env_var(fresh_deadline_config, monkeypatch, env_var_value):
"""Ensures the telemetry client doesn't fully initialize if the opt out env var is set"""
# GIVEN
config.set_setting("defaults.aws_profile_name", "SomeRandomProfileName")
monkeypatch.setenv("DEADLINE_CLOUD_TELEMETRY_OPT_OUT", env_var_value)
config.set_setting(
"telemetry.opt_out", "false"
) # Ensure we ignore the config file if env var is set
# WHEN
client = TelemetryClient(
"test-library", "test-version", config=config.config_file.read_config()
)
# THEN
assert not hasattr(client, "endpoint")
assert not hasattr(client, "session_id")
assert not hasattr(client, "telemetry_id")
assert not hasattr(client, "system_metadata")
assert not hasattr(client, "event_queue")
assert not hasattr(client, "processing_thread")
# Ensure nothing blows up if we try recording telemetry after we've opted out
client.record_hashing_summary(SummaryStatistics(), from_gui=True)
client.record_upload_summary(SummaryStatistics(), from_gui=False)
client.record_error({}, str(type(Exception)))


def test_get_telemetry_identifier(telemetry_client):
"""Ensures that getting the local-user-id handles empty/malformed strings"""
# Confirm that we generate a new UUID if the setting doesn't exist, and write to config
Expand Down
26 changes: 18 additions & 8 deletions test/unit/deadline_client/api/test_job_bundle_submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,9 @@ def test_create_job_from_job_bundle(
"deadline.client.api._session.DeadlineClient._get_deadline_api_input_shape"
) as input_shape_mock:
input_shape_mock.return_value = {"template": ""}
with patch.object(api._session, "get_boto3_session") as session_mock:
with patch.object(api._session, "get_boto3_session") as session_mock, patch.object(
_submit_job_bundle.api, "get_deadline_cloud_library_telemetry_client"
):
session_mock().client("deadline").create_job.side_effect = [MOCK_CREATE_JOB_RESPONSE]
session_mock().client("deadline").get_job.return_value = MOCK_GET_JOB_RESPONSE

Expand Down Expand Up @@ -325,7 +327,9 @@ def test_create_job_from_job_bundle_error_missing_template(
"""
Test a job bundle with missing template.
"""
with patch.object(api._session, "get_boto3_session") as session_mock:
with patch.object(api._session, "get_boto3_session") as session_mock, patch.object(
_submit_job_bundle.api, "get_deadline_cloud_library_telemetry_client"
):
session_mock().client("deadline").create_job.side_effect = [MOCK_CREATE_JOB_RESPONSE]

config.set_setting("defaults.farm_id", MOCK_FARM_ID)
Expand Down Expand Up @@ -353,7 +357,9 @@ def test_create_job_from_job_bundle_error_duplicate_template(
"""
Test a job bundle with both a JSON and YAML template.
"""
with patch.object(api._session, "get_boto3_session") as session_mock:
with patch.object(api._session, "get_boto3_session") as session_mock, patch.object(
_submit_job_bundle.api, "get_deadline_cloud_library_telemetry_client"
):
session_mock().client("deadline").create_job.side_effect = [MOCK_CREATE_JOB_RESPONSE]

config.set_setting("defaults.farm_id", MOCK_FARM_ID)
Expand Down Expand Up @@ -385,7 +391,9 @@ def test_create_job_from_job_bundle_error_duplicate_parameters(
"""
Test a job bundle with an incorrect Amazon Deadline Cloud parameter
"""
with patch.object(api._session, "get_boto3_session") as session_mock:
with patch.object(api._session, "get_boto3_session") as session_mock, patch.object(
_submit_job_bundle.api, "get_deadline_cloud_library_telemetry_client"
):
session_mock().client("deadline").create_job.side_effect = [MOCK_CREATE_JOB_RESPONSE]

config.set_setting("defaults.farm_id", MOCK_FARM_ID)
Expand Down Expand Up @@ -448,7 +456,7 @@ def test_create_job_from_job_bundle_job_attachments(
) as mock_prepare_paths, patch.object(
S3AssetManager, "upload_assets"
) as mock_upload_assets, patch.object(
_submit_job_bundle.api, "get_telemetry_client"
_submit_job_bundle.api, "get_deadline_cloud_library_telemetry_client"
):
client_mock().get_queue.side_effect = [MOCK_GET_QUEUE_RESPONSE]
client_mock().create_job.side_effect = [MOCK_CREATE_JOB_RESPONSE]
Expand Down Expand Up @@ -565,7 +573,7 @@ def test_create_job_from_job_bundle_empty_job_attachments(
) as mock_prepare_paths, patch.object(
S3AssetManager, "upload_assets"
) as mock_upload_assets, patch.object(
_submit_job_bundle.api, "get_telemetry_client"
_submit_job_bundle.api, "get_deadline_cloud_library_telemetry_client"
):
client_mock().get_queue.side_effect = [MOCK_GET_QUEUE_RESPONSE]
client_mock().create_job.side_effect = [MOCK_CREATE_JOB_RESPONSE]
Expand Down Expand Up @@ -657,7 +665,9 @@ def test_create_job_from_job_bundle_with_empty_asset_references(
job_template_type, job_template = MOCK_JOB_TEMPLATE_CASES["MINIMAL_JSON"]
with patch(
"deadline.client.api._session.DeadlineClient._get_deadline_api_input_shape"
) as input_shape_mock:
) as input_shape_mock, patch.object(
_submit_job_bundle.api, "get_deadline_cloud_library_telemetry_client"
):
input_shape_mock.return_value = {}
with patch.object(api._session, "get_boto3_session") as session_mock:
session_mock().client("deadline").create_job.side_effect = [MOCK_CREATE_JOB_RESPONSE]
Expand Down Expand Up @@ -723,7 +733,7 @@ def test_create_job_from_job_bundle_with_single_asset_file(
) as mock_prepare_paths, patch.object(
S3AssetManager, "upload_assets"
) as mock_upload_assets, patch.object(
_submit_job_bundle.api, "get_telemetry_client"
_submit_job_bundle.api, "get_deadline_cloud_library_telemetry_client"
):
client_mock().create_job.side_effect = [MOCK_CREATE_JOB_RESPONSE]
client_mock().get_queue.side_effect = [MOCK_GET_QUEUE_RESPONSE]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ def test_create_job_from_job_bundle_with_all_asset_ref_variants(
) as mock_hash_assets, patch.object(
S3AssetManager, "upload_assets"
) as mock_upload_assets, patch.object(
_submit_job_bundle.api, "get_telemetry_client"
_submit_job_bundle.api, "get_deadline_cloud_library_telemetry_client"
):
client_mock().create_job.side_effect = [MOCK_CREATE_JOB_RESPONSE]
client_mock().get_queue.side_effect = [MOCK_GET_QUEUE_RESPONSE]
Expand Down
28 changes: 19 additions & 9 deletions test/unit/deadline_client/cli/test_cli_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def test_cli_bundle_submit(fresh_deadline_config, temp_job_bundle_dir):
), patch.object(
_submit_job_bundle, "_upload_attachments"
), patch.object(
bundle_group.api, "get_telemetry_client"
bundle_group.api, "get_deadline_cloud_library_telemetry_client"
):
get_boto3_client_mock().create_job.return_value = MOCK_CREATE_JOB_RESPONSE
get_boto3_client_mock().get_job.return_value = MOCK_GET_JOB_RESPONSE
Expand Down Expand Up @@ -158,7 +158,9 @@ def test_cli_bundle_explicit_parameters(fresh_deadline_config):
# Use a temporary directory for the job bundle
with patch(
"deadline.client.api._session.DeadlineClient._get_deadline_api_input_shape"
) as input_shape_mock:
) as input_shape_mock, patch.object(
bundle_group.api, "get_deadline_cloud_library_telemetry_client"
):
input_shape_mock.return_value = {}
with tempfile.TemporaryDirectory() as tmpdir, patch.object(
boto3, "Session"
Expand Down Expand Up @@ -209,7 +211,9 @@ def test_cli_bundle_priority_retries(fresh_deadline_config):
# Use a temporary directory for the job bundle
with patch(
"deadline.client.api._session.DeadlineClient._get_deadline_api_input_shape"
) as input_shape_mock:
) as input_shape_mock, patch.object(
bundle_group.api, "get_deadline_cloud_library_telemetry_client"
):
input_shape_mock.return_value = {}
with tempfile.TemporaryDirectory() as tmpdir, patch.object(
boto3, "Session"
Expand Down Expand Up @@ -264,7 +268,9 @@ def test_cli_bundle_job_name(fresh_deadline_config):
# Use a temporary directory for the job bundle
with patch(
"deadline.client.api._session.DeadlineClient._get_deadline_api_input_shape"
) as input_shape_mock:
) as input_shape_mock, patch.object(
bundle_group.api, "get_deadline_cloud_library_telemetry_client"
):
input_shape_mock.return_value = {}
with tempfile.TemporaryDirectory() as tmpdir, patch.object(
boto3, "Session"
Expand Down Expand Up @@ -361,7 +367,7 @@ def test_cli_bundle_asset_load_method(fresh_deadline_config, temp_job_bundle_dir
), patch.object(
_submit_job_bundle.api, "get_queue_user_boto3_session"
), patch.object(
bundle_group.api, "get_telemetry_client"
bundle_group.api, "get_deadline_cloud_library_telemetry_client"
):
bundle_boto3_client_mock().create_job.return_value = MOCK_CREATE_JOB_RESPONSE
bundle_boto3_client_mock().get_job.return_value = MOCK_GET_JOB_RESPONSE
Expand Down Expand Up @@ -413,7 +419,9 @@ def test_cli_bundle_job_parameter_from_cli(fresh_deadline_config):
# Use a temporary directory for the job bundle
with patch(
"deadline.client.api._session.DeadlineClient._get_deadline_api_input_shape"
) as input_shape_mock:
) as input_shape_mock, patch.object(
bundle_group.api, "get_deadline_cloud_library_telemetry_client"
):
input_shape_mock.return_value = {}
with tempfile.TemporaryDirectory() as tmpdir, patch.object(
boto3, "Session"
Expand Down Expand Up @@ -468,7 +476,9 @@ def test_cli_bundle_empty_job_parameter_from_cli(fresh_deadline_config):
# Use a temporary directory for the job bundle
with patch(
"deadline.client.api._session.DeadlineClient._get_deadline_api_input_shape"
) as input_shape_mock:
) as input_shape_mock, patch.object(
bundle_group.api, "get_deadline_cloud_library_telemetry_client"
):
input_shape_mock.return_value = {}
with tempfile.TemporaryDirectory() as tmpdir, patch.object(
boto3, "Session"
Expand Down Expand Up @@ -585,7 +595,7 @@ def test_cli_bundle_accept_upload_confirmation(fresh_deadline_config, temp_job_b
), patch.object(
_submit_job_bundle.api, "get_queue_user_boto3_session"
), patch.object(
bundle_group.api, "get_telemetry_client"
bundle_group.api, "get_deadline_cloud_library_telemetry_client"
):
get_boto3_client_mock().create_job.return_value = MOCK_CREATE_JOB_RESPONSE
get_boto3_client_mock().get_job.return_value = MOCK_GET_JOB_RESPONSE
Expand Down Expand Up @@ -661,7 +671,7 @@ def test_cli_bundle_reject_upload_confirmation(fresh_deadline_config, temp_job_b
), patch.object(
_submit_job_bundle.api, "get_queue_user_boto3_session"
), patch.object(
bundle_group.api, "get_telemetry_client"
bundle_group.api, "get_deadline_cloud_library_telemetry_client"
):
get_boto3_client_mock().get_queue.return_value = {
"displayName": "Test Queue",
Expand Down

0 comments on commit 4f270ba

Please sign in to comment.