Skip to content

Commit

Permalink
fix: Use boto SSL for telemetry requests, add opt out settings in UI (#…
Browse files Browse the repository at this point in the history
…230)

Signed-off-by: Caden Marofke <marofke@amazon.com>
  • Loading branch information
marofke committed Mar 24, 2024
1 parent b3853c2 commit b678086
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 66 deletions.
97 changes: 67 additions & 30 deletions src/deadline/client/api/_telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
import platform
import uuid
import random
import ssl
import time

from botocore.httpsession import get_cert_path
from configparser import ConfigParser
from dataclasses import asdict, dataclass, field
from datetime import datetime
Expand Down Expand Up @@ -88,7 +90,33 @@ def __init__(
package_ver: str,
config: Optional[ConfigParser] = None,
):
# Environment variable supersedes config file setting.
self._initialized: bool = False
self.package_name = package_name
self.package_ver = ".".join(package_ver.split(".")[:3])

# Need to set up a valid SSL context so requests can make it through
self._urllib3_context = ssl.SSLContext(ssl.PROTOCOL_TLS)
self._urllib3_context.verify_mode = ssl.CERT_REQUIRED
self._urllib3_context.check_hostname = True
self._urllib3_context.load_default_certs()
self._urllib3_context.load_verify_locations(cafile=get_cert_path(True))

# IDs for this session
self.session_id: str = str(uuid.uuid4())
self.telemetry_id: str = self._get_telemetry_identifier(config=config)
# If a different base package is provided, include info from this library as supplementary info
if package_name != "deadline-cloud-library":
self._common_details["deadline-cloud-version"] = version
self._system_metadata = self._get_system_metadata(config=config)
self.set_opt_out(config=config)
self.initialize(config=config)

def set_opt_out(self, config: Optional[ConfigParser] = None) -> None:
"""
Checks whether telemetry has been opted out by checking the DEADLINE_CLOUD_TELEMETRY_OPT_OUT
environment variable and the 'telemetry.opt_out' config file setting.
Note the environment variable supersedes the 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
Expand All @@ -97,29 +125,43 @@ def __init__(
config_file.get_setting("telemetry.opt_out", config=config)
)
logger.info(
"Deadline Cloud telemetry is " + "not enabled."
if self.telemetry_opted_out
else "enabled."
"Deadline Cloud telemetry is "
+ ("not enabled." if self.telemetry_opted_out else "enabled.")
)

def initialize(self, config: Optional[ConfigParser] = None) -> None:
"""
Starts up the telemetry background thread after getting settings from the boto3 client.
Note that if this is called before boto3 is successfully configured / initialized,
an error can be raised. In that case we silently fail and don't mark the client as
initialized.
"""
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(
f"{get_deadline_endpoint_url(config=config)}/2023-10-12/telemetry",
TelemetryClient.ENDPOINT_PREFIX,
)
try:
self.endpoint: str = self._get_prefixed_endpoint(
f"{get_deadline_endpoint_url(config=config)}/2023-10-12/telemetry",
TelemetryClient.ENDPOINT_PREFIX,
)

# IDs for this session
self.session_id: str = str(uuid.uuid4())
self.telemetry_id: str = self._get_telemetry_identifier(config=config)
# If a different base package is provided, include info from this library as supplementary info
if package_name != "deadline-cloud-library":
self._common_details["deadline-cloud-version"] = version
self._system_metadata = self._get_system_metadata(config=config)
user_id, _ = get_user_and_identity_store_id(config=config)
if user_id:
self._system_metadata["user_id"] = user_id

monitor_id: Optional[str] = get_monitor_id(config=config)
if monitor_id:
self._system_metadata["monitor_id"] = monitor_id

self._initialized = True
self._start_threads()
except Exception:
# Silently swallow any exceptions
return

self._start_threads()
@property
def is_initialized(self) -> bool:
return self._initialized

def _get_prefixed_endpoint(self, endpoint: str, prefix: str) -> str:
"""Insert the prefix right after 'https://'"""
Expand Down Expand Up @@ -163,14 +205,6 @@ def _get_system_metadata(self, config: Optional[ConfigParser]) -> Dict[str, Any]
"osVersion": platform_info.release,
}

user_id, _ = get_user_and_identity_store_id(config=config)
if user_id:
metadata["user_id"] = user_id

monitor_id: Optional[str] = get_monitor_id(config=config)
if monitor_id:
metadata["monitor_id"] = monitor_id

return metadata

def _exit_cleanly(self):
Expand All @@ -182,7 +216,7 @@ def _send_request(self, req: request.Request) -> None:
success = False
while not success:
try:
with request.urlopen(req):
with request.urlopen(req, context=self._urllib3_context):
logger.debug("Successfully sent telemetry.")
success = True
except error.HTTPError as httpe:
Expand Down Expand Up @@ -232,13 +266,14 @@ def _process_event_queue_thread(self):
try:
logger.debug("Sending telemetry data: %s", request_body)
self._send_request(req)
except Exception:
# Silently swallow any kind of uncaught exception and stop sending telemetry
except Exception as exc:
# Swallow any kind of uncaught exception and stop sending telemetry
logger.debug(f"Error received from service. {str(exc)}")
return
self.event_queue.task_done()

def _put_telemetry_record(self, event: TelemetryEvent) -> None:
if self.telemetry_opted_out:
if not self._initialized or self.telemetry_opted_out:
return
try:
self.event_queue.put_nowait(event)
Expand Down Expand Up @@ -303,6 +338,8 @@ def get_telemetry_client(
package_ver=package_ver,
config=config,
)
elif not __cached_telemetry_client.is_initialized:
__cached_telemetry_client.initialize(config=config)

return __cached_telemetry_client

Expand Down
4 changes: 4 additions & 0 deletions src/deadline/client/ui/dialogs/deadline_config_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,9 @@ def _build_general_settings_ui(self, group, layout):
self.auto_accept = self._init_checkbox_setting(
group, layout, "settings.auto_accept", "Auto Accept Confirmation Prompts"
)
self.telemetry_opt_out = self._init_checkbox_setting(
group, layout, "telemetry.opt_out", "Telemetry Opt Out"
)

self._conflict_resolution_options = [option.name for option in FileConflictResolution]
self.conflict_resolution_box = self._init_combobox_setting(
Expand Down Expand Up @@ -561,6 +564,7 @@ def apply(self) -> bool:
for setting_name, value in self.changes.items():
config_file.set_setting(setting_name, value, self.config)
root.setLevel(config_file.get_setting("settings.log_level"))
api.get_deadline_cloud_library_telemetry_client().set_opt_out(config=self.config)

# Only update self.changes_were_applied if false. We don't want to invalidate that a change has
# occurred if the user repeatedly hits "Apply" or hits "Apply" and then "Save".
Expand Down
Loading

0 comments on commit b678086

Please sign in to comment.