1*3c7ae9deSAndroid Build Coastguard Worker# Contains all of the classes that are shared across both the Canary Wrapper and the Persistent Canary Wrapper scripts 2*3c7ae9deSAndroid Build Coastguard Worker# If a class can/is reused, then it should be in this file. 3*3c7ae9deSAndroid Build Coastguard Worker 4*3c7ae9deSAndroid Build Coastguard Worker# Needs to be installed prior to running 5*3c7ae9deSAndroid Build Coastguard Workerimport boto3 6*3c7ae9deSAndroid Build Coastguard Workerimport psutil 7*3c7ae9deSAndroid Build Coastguard Worker# Part of standard packages in Python 3.4+ 8*3c7ae9deSAndroid Build Coastguard Workerimport time 9*3c7ae9deSAndroid Build Coastguard Workerimport os 10*3c7ae9deSAndroid Build Coastguard Workerimport json 11*3c7ae9deSAndroid Build Coastguard Workerimport subprocess 12*3c7ae9deSAndroid Build Coastguard Workerimport zipfile 13*3c7ae9deSAndroid Build Coastguard Workerimport datetime 14*3c7ae9deSAndroid Build Coastguard Worker 15*3c7ae9deSAndroid Build Coastguard Worker# ================================================================================ 16*3c7ae9deSAndroid Build Coastguard Worker 17*3c7ae9deSAndroid Build Coastguard Worker# Class that holds metric data and has a few utility functions for getting that data in a format we can use for Cloudwatch 18*3c7ae9deSAndroid Build Coastguard Workerclass DataSnapshot_Metric(): 19*3c7ae9deSAndroid Build Coastguard Worker def __init__(self, metric_name, metric_function, metric_dimensions=[], 20*3c7ae9deSAndroid Build Coastguard Worker metric_unit="None", metric_alarm_threshold=None, metric_alarm_severity=6, 21*3c7ae9deSAndroid Build Coastguard Worker git_hash="", git_repo_name="", reports_to_skip=0, is_percent=False): 22*3c7ae9deSAndroid Build Coastguard Worker self.metric_name = metric_name 23*3c7ae9deSAndroid Build Coastguard Worker self.metric_function = metric_function 24*3c7ae9deSAndroid Build Coastguard Worker self.metric_dimensions = metric_dimensions 25*3c7ae9deSAndroid Build Coastguard Worker self.metric_unit = metric_unit 26*3c7ae9deSAndroid Build Coastguard Worker self.metric_alarm_threshold = metric_alarm_threshold 27*3c7ae9deSAndroid Build Coastguard Worker self.metric_alarm_name = self.metric_name + "-" + git_repo_name + "-" + git_hash 28*3c7ae9deSAndroid Build Coastguard Worker self.metric_alarm_description = 'Alarm for metric "' + self.metric_name + '" - git hash: ' + git_hash 29*3c7ae9deSAndroid Build Coastguard Worker self.metric_value = None 30*3c7ae9deSAndroid Build Coastguard Worker self.reports_to_skip = reports_to_skip 31*3c7ae9deSAndroid Build Coastguard Worker self.metric_alarm_severity = metric_alarm_severity 32*3c7ae9deSAndroid Build Coastguard Worker self.is_percent = is_percent 33*3c7ae9deSAndroid Build Coastguard Worker 34*3c7ae9deSAndroid Build Coastguard Worker # Gets the latest metric value from the metric_function callback 35*3c7ae9deSAndroid Build Coastguard Worker def get_metric_value(self, psutil_process : psutil.Process): 36*3c7ae9deSAndroid Build Coastguard Worker if not self.metric_function is None: 37*3c7ae9deSAndroid Build Coastguard Worker self.metric_value = self.metric_function(psutil_process) 38*3c7ae9deSAndroid Build Coastguard Worker return self.metric_value 39*3c7ae9deSAndroid Build Coastguard Worker 40*3c7ae9deSAndroid Build Coastguard Worker # Returns the data needed to send to Cloudwatch when posting metrics 41*3c7ae9deSAndroid Build Coastguard Worker def get_metric_cloudwatch_dictionary(self): 42*3c7ae9deSAndroid Build Coastguard Worker if (self.reports_to_skip > 0): 43*3c7ae9deSAndroid Build Coastguard Worker self.reports_to_skip -= 1 44*3c7ae9deSAndroid Build Coastguard Worker return None # skips sending to Cloudwatch 45*3c7ae9deSAndroid Build Coastguard Worker 46*3c7ae9deSAndroid Build Coastguard Worker if (self.metric_value == None): 47*3c7ae9deSAndroid Build Coastguard Worker return None # skips sending to Cloudwatch 48*3c7ae9deSAndroid Build Coastguard Worker 49*3c7ae9deSAndroid Build Coastguard Worker return { 50*3c7ae9deSAndroid Build Coastguard Worker "MetricName": self.metric_name, 51*3c7ae9deSAndroid Build Coastguard Worker "Dimensions": self.metric_dimensions, 52*3c7ae9deSAndroid Build Coastguard Worker "Value": self.metric_value, 53*3c7ae9deSAndroid Build Coastguard Worker "Unit": self.metric_unit 54*3c7ae9deSAndroid Build Coastguard Worker } 55*3c7ae9deSAndroid Build Coastguard Worker 56*3c7ae9deSAndroid Build Coastguard Workerclass DataSnapshot_Dashboard_Widget(): 57*3c7ae9deSAndroid Build Coastguard Worker def __init__(self, widget_name, metric_namespace, metric_dimension, cloudwatch_region="us-east-1", widget_period=60) -> None: 58*3c7ae9deSAndroid Build Coastguard Worker self.metric_list = [] 59*3c7ae9deSAndroid Build Coastguard Worker self.region = cloudwatch_region 60*3c7ae9deSAndroid Build Coastguard Worker self.widget_name = widget_name 61*3c7ae9deSAndroid Build Coastguard Worker self.metric_namespace = metric_namespace 62*3c7ae9deSAndroid Build Coastguard Worker self.metric_dimension = metric_dimension 63*3c7ae9deSAndroid Build Coastguard Worker self.widget_period = widget_period 64*3c7ae9deSAndroid Build Coastguard Worker 65*3c7ae9deSAndroid Build Coastguard Worker def add_metric_to_widget(self, new_metric_name): 66*3c7ae9deSAndroid Build Coastguard Worker try: 67*3c7ae9deSAndroid Build Coastguard Worker self.metric_list.append(new_metric_name) 68*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 69*3c7ae9deSAndroid Build Coastguard Worker print ("[DataSnapshot_Dashboard] ERROR - could not add metric to dashboard widget due to exception!") 70*3c7ae9deSAndroid Build Coastguard Worker print ("[DataSnapshot_Dashboard] Exception: " + str(e)) 71*3c7ae9deSAndroid Build Coastguard Worker 72*3c7ae9deSAndroid Build Coastguard Worker def remove_metric_from_widget(self, existing_metric_name): 73*3c7ae9deSAndroid Build Coastguard Worker try: 74*3c7ae9deSAndroid Build Coastguard Worker self.metric_list.remove(existing_metric_name) 75*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 76*3c7ae9deSAndroid Build Coastguard Worker print ("[DataSnapshot_Dashboard] ERROR - could not remove metric from dashboard widget due to exception!") 77*3c7ae9deSAndroid Build Coastguard Worker print ("[DataSnapshot_Dashboard] Exception: " + str(e)) 78*3c7ae9deSAndroid Build Coastguard Worker 79*3c7ae9deSAndroid Build Coastguard Worker def get_widget_dictionary(self): 80*3c7ae9deSAndroid Build Coastguard Worker metric_list_json = [] 81*3c7ae9deSAndroid Build Coastguard Worker for metric_name in self.metric_list: 82*3c7ae9deSAndroid Build Coastguard Worker metric_list_json.append([self.metric_namespace, metric_name, self.metric_dimension, metric_name]) 83*3c7ae9deSAndroid Build Coastguard Worker 84*3c7ae9deSAndroid Build Coastguard Worker return { 85*3c7ae9deSAndroid Build Coastguard Worker "type":"metric", 86*3c7ae9deSAndroid Build Coastguard Worker "properties" : { 87*3c7ae9deSAndroid Build Coastguard Worker "metrics" : metric_list_json, 88*3c7ae9deSAndroid Build Coastguard Worker "region": self.region, 89*3c7ae9deSAndroid Build Coastguard Worker "title": self.widget_name, 90*3c7ae9deSAndroid Build Coastguard Worker "period": self.widget_period, 91*3c7ae9deSAndroid Build Coastguard Worker }, 92*3c7ae9deSAndroid Build Coastguard Worker "width": 14, 93*3c7ae9deSAndroid Build Coastguard Worker "height": 10 94*3c7ae9deSAndroid Build Coastguard Worker } 95*3c7ae9deSAndroid Build Coastguard Worker 96*3c7ae9deSAndroid Build Coastguard Worker# ================================================================================ 97*3c7ae9deSAndroid Build Coastguard Worker 98*3c7ae9deSAndroid Build Coastguard Worker# Class that keeps track of the metrics registered, sets up Cloudwatch and S3, and sends periodic reports 99*3c7ae9deSAndroid Build Coastguard Worker# Is the backbone of the reporting operation 100*3c7ae9deSAndroid Build Coastguard Workerclass DataSnapshot(): 101*3c7ae9deSAndroid Build Coastguard Worker def __init__(self, 102*3c7ae9deSAndroid Build Coastguard Worker git_hash=None, 103*3c7ae9deSAndroid Build Coastguard Worker git_repo_name=None, 104*3c7ae9deSAndroid Build Coastguard Worker git_hash_as_namespace=False, 105*3c7ae9deSAndroid Build Coastguard Worker git_fixed_namespace_text="mqtt5_canary", 106*3c7ae9deSAndroid Build Coastguard Worker datetime_string=None, 107*3c7ae9deSAndroid Build Coastguard Worker output_log_filepath=None, 108*3c7ae9deSAndroid Build Coastguard Worker output_to_console=True, 109*3c7ae9deSAndroid Build Coastguard Worker cloudwatch_region="us-east-1", 110*3c7ae9deSAndroid Build Coastguard Worker cloudwatch_make_dashboard=False, 111*3c7ae9deSAndroid Build Coastguard Worker cloudwatch_teardown_alarms_on_complete=True, 112*3c7ae9deSAndroid Build Coastguard Worker cloudwatch_teardown_dashboard_on_complete=True, 113*3c7ae9deSAndroid Build Coastguard Worker s3_bucket_name="canary-wrapper-bucket", 114*3c7ae9deSAndroid Build Coastguard Worker s3_bucket_upload_on_complete=True, 115*3c7ae9deSAndroid Build Coastguard Worker lambda_name="CanarySendEmailLambda", 116*3c7ae9deSAndroid Build Coastguard Worker metric_frequency=None): 117*3c7ae9deSAndroid Build Coastguard Worker 118*3c7ae9deSAndroid Build Coastguard Worker # Setting initial values 119*3c7ae9deSAndroid Build Coastguard Worker # ================== 120*3c7ae9deSAndroid Build Coastguard Worker self.first_metric_call = True 121*3c7ae9deSAndroid Build Coastguard Worker self.metrics = [] 122*3c7ae9deSAndroid Build Coastguard Worker self.metrics_numbers = [] 123*3c7ae9deSAndroid Build Coastguard Worker self.metric_report_number = 0 124*3c7ae9deSAndroid Build Coastguard Worker self.metric_report_non_zero_count = 4 125*3c7ae9deSAndroid Build Coastguard Worker 126*3c7ae9deSAndroid Build Coastguard Worker # Needed so we can initialize Cloudwatch alarms, etc, outside of the init function 127*3c7ae9deSAndroid Build Coastguard Worker # but before we start sending data. 128*3c7ae9deSAndroid Build Coastguard Worker # This boolean tracks whether we have done the post-initialization prior to sending the first report. 129*3c7ae9deSAndroid Build Coastguard Worker self.perform_final_initialization = True 130*3c7ae9deSAndroid Build Coastguard Worker 131*3c7ae9deSAndroid Build Coastguard Worker # Watched by the thread creating the snapshot. Will cause the thread(s) to abort and return an error. 132*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error = False 133*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error_reason = "" 134*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error_due_to_credentials = False 135*3c7ae9deSAndroid Build Coastguard Worker 136*3c7ae9deSAndroid Build Coastguard Worker self.git_hash = None 137*3c7ae9deSAndroid Build Coastguard Worker self.git_repo_name = None 138*3c7ae9deSAndroid Build Coastguard Worker self.git_hash_as_namespace = git_hash_as_namespace 139*3c7ae9deSAndroid Build Coastguard Worker self.git_fixed_namespace_text = git_fixed_namespace_text 140*3c7ae9deSAndroid Build Coastguard Worker self.git_metric_namespace = None 141*3c7ae9deSAndroid Build Coastguard Worker 142*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_region = cloudwatch_region 143*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_client = None 144*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_make_dashboard = cloudwatch_make_dashboard 145*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_teardown_alarms_on_complete = cloudwatch_teardown_alarms_on_complete 146*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_teardown_dashboard_on_complete = cloudwatch_teardown_dashboard_on_complete 147*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_dashboard_name = "" 148*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_dashboard_widgets = [] 149*3c7ae9deSAndroid Build Coastguard Worker 150*3c7ae9deSAndroid Build Coastguard Worker self.s3_bucket_name = s3_bucket_name 151*3c7ae9deSAndroid Build Coastguard Worker self.s3_client = None 152*3c7ae9deSAndroid Build Coastguard Worker self.s3_bucket_upload_on_complete = s3_bucket_upload_on_complete 153*3c7ae9deSAndroid Build Coastguard Worker 154*3c7ae9deSAndroid Build Coastguard Worker self.output_to_file_filepath = output_log_filepath 155*3c7ae9deSAndroid Build Coastguard Worker self.output_to_file = False 156*3c7ae9deSAndroid Build Coastguard Worker self.output_file = None 157*3c7ae9deSAndroid Build Coastguard Worker self.output_to_console = output_to_console 158*3c7ae9deSAndroid Build Coastguard Worker 159*3c7ae9deSAndroid Build Coastguard Worker self.lambda_client = None 160*3c7ae9deSAndroid Build Coastguard Worker self.lambda_name = lambda_name 161*3c7ae9deSAndroid Build Coastguard Worker 162*3c7ae9deSAndroid Build Coastguard Worker self.datetime_string = datetime_string 163*3c7ae9deSAndroid Build Coastguard Worker self.metric_frequency = metric_frequency 164*3c7ae9deSAndroid Build Coastguard Worker # ================== 165*3c7ae9deSAndroid Build Coastguard Worker 166*3c7ae9deSAndroid Build Coastguard Worker # Check for valid credentials 167*3c7ae9deSAndroid Build Coastguard Worker # ================== 168*3c7ae9deSAndroid Build Coastguard Worker try: 169*3c7ae9deSAndroid Build Coastguard Worker tmp_sts_client = boto3.client('sts') 170*3c7ae9deSAndroid Build Coastguard Worker tmp_sts_client.get_caller_identity() 171*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 172*3c7ae9deSAndroid Build Coastguard Worker print ("[DataSnapshot] ERROR - AWS credentials are NOT valid!") 173*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error = True 174*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error_reason = "AWS credentials are NOT valid!" 175*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error_due_to_credentials = True 176*3c7ae9deSAndroid Build Coastguard Worker return 177*3c7ae9deSAndroid Build Coastguard Worker # ================== 178*3c7ae9deSAndroid Build Coastguard Worker 179*3c7ae9deSAndroid Build Coastguard Worker # Git related stuff 180*3c7ae9deSAndroid Build Coastguard Worker # ================== 181*3c7ae9deSAndroid Build Coastguard Worker if (git_hash == None or git_repo_name == None): 182*3c7ae9deSAndroid Build Coastguard Worker print("[DataSnapshot] ERROR - a Git hash and repository name are REQUIRED for the canary wrapper to run!") 183*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error = True 184*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error_reason = "No Git hash and repository passed!" 185*3c7ae9deSAndroid Build Coastguard Worker return 186*3c7ae9deSAndroid Build Coastguard Worker 187*3c7ae9deSAndroid Build Coastguard Worker self.git_hash = git_hash 188*3c7ae9deSAndroid Build Coastguard Worker self.git_repo_name = git_repo_name 189*3c7ae9deSAndroid Build Coastguard Worker 190*3c7ae9deSAndroid Build Coastguard Worker if (self.git_hash_as_namespace == False): 191*3c7ae9deSAndroid Build Coastguard Worker self.git_metric_namespace = self.git_fixed_namespace_text 192*3c7ae9deSAndroid Build Coastguard Worker else: 193*3c7ae9deSAndroid Build Coastguard Worker if (self.datetime_string == None): 194*3c7ae9deSAndroid Build Coastguard Worker git_namespace_prepend_text = self.git_repo_name + "-" + self.git_hash 195*3c7ae9deSAndroid Build Coastguard Worker else: 196*3c7ae9deSAndroid Build Coastguard Worker git_namespace_prepend_text = self.git_repo_name + "/" + self.datetime_string + "-" + self.git_hash 197*3c7ae9deSAndroid Build Coastguard Worker self.git_metric_namespace = git_namespace_prepend_text 198*3c7ae9deSAndroid Build Coastguard Worker # ================== 199*3c7ae9deSAndroid Build Coastguard Worker 200*3c7ae9deSAndroid Build Coastguard Worker # Cloudwatch related stuff 201*3c7ae9deSAndroid Build Coastguard Worker # ================== 202*3c7ae9deSAndroid Build Coastguard Worker try: 203*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_client = boto3.client('cloudwatch', self.cloudwatch_region) 204*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_dashboard_name = self.git_metric_namespace 205*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 206*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] ERROR - could not make Cloudwatch client due to exception!") 207*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Exception: " + str(e)) 208*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_client = None 209*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error = True 210*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error_reason = "Could not make Cloudwatch client!" 211*3c7ae9deSAndroid Build Coastguard Worker return 212*3c7ae9deSAndroid Build Coastguard Worker # ================== 213*3c7ae9deSAndroid Build Coastguard Worker 214*3c7ae9deSAndroid Build Coastguard Worker # S3 related stuff 215*3c7ae9deSAndroid Build Coastguard Worker # ================== 216*3c7ae9deSAndroid Build Coastguard Worker try: 217*3c7ae9deSAndroid Build Coastguard Worker self.s3_client = boto3.client("s3") 218*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 219*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] ERROR - could not make S3 client due to exception!") 220*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Exception: " + str(e)) 221*3c7ae9deSAndroid Build Coastguard Worker self.s3_client = None 222*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error = True 223*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error_reason = "Could not make S3 client!" 224*3c7ae9deSAndroid Build Coastguard Worker return 225*3c7ae9deSAndroid Build Coastguard Worker # ================== 226*3c7ae9deSAndroid Build Coastguard Worker 227*3c7ae9deSAndroid Build Coastguard Worker # Lambda related stuff 228*3c7ae9deSAndroid Build Coastguard Worker # ================== 229*3c7ae9deSAndroid Build Coastguard Worker try: 230*3c7ae9deSAndroid Build Coastguard Worker self.lambda_client = boto3.client("lambda", self.cloudwatch_region) 231*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 232*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] ERROR - could not make Lambda client due to exception!") 233*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Exception: " + str(e)) 234*3c7ae9deSAndroid Build Coastguard Worker self.lambda_client = None 235*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error = True 236*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error_reason = "Could not make Lambda client!" 237*3c7ae9deSAndroid Build Coastguard Worker return 238*3c7ae9deSAndroid Build Coastguard Worker # ================== 239*3c7ae9deSAndroid Build Coastguard Worker 240*3c7ae9deSAndroid Build Coastguard Worker # File output (logs) related stuff 241*3c7ae9deSAndroid Build Coastguard Worker # ================== 242*3c7ae9deSAndroid Build Coastguard Worker if (not output_log_filepath is None): 243*3c7ae9deSAndroid Build Coastguard Worker self.output_to_file = True 244*3c7ae9deSAndroid Build Coastguard Worker self.output_file = open(self.output_to_file_filepath, "w") 245*3c7ae9deSAndroid Build Coastguard Worker else: 246*3c7ae9deSAndroid Build Coastguard Worker self.output_to_file = False 247*3c7ae9deSAndroid Build Coastguard Worker self.output_file = None 248*3c7ae9deSAndroid Build Coastguard Worker # ================== 249*3c7ae9deSAndroid Build Coastguard Worker 250*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Data snapshot created!") 251*3c7ae9deSAndroid Build Coastguard Worker 252*3c7ae9deSAndroid Build Coastguard Worker # Cleans the class - closing any files, removing alarms, and sending data to S3. 253*3c7ae9deSAndroid Build Coastguard Worker # Should be called at the end when you are totally finished shadowing metrics 254*3c7ae9deSAndroid Build Coastguard Worker def cleanup(self, error_occurred=False): 255*3c7ae9deSAndroid Build Coastguard Worker if (self.s3_bucket_upload_on_complete == True): 256*3c7ae9deSAndroid Build Coastguard Worker self.export_result_to_s3_bucket(copy_output_log=True, log_is_error=error_occurred) 257*3c7ae9deSAndroid Build Coastguard Worker 258*3c7ae9deSAndroid Build Coastguard Worker self._cleanup_cloudwatch_alarms() 259*3c7ae9deSAndroid Build Coastguard Worker if (self.cloudwatch_make_dashboard == True): 260*3c7ae9deSAndroid Build Coastguard Worker self._cleanup_cloudwatch_dashboard() 261*3c7ae9deSAndroid Build Coastguard Worker 262*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Data snapshot cleaned!") 263*3c7ae9deSAndroid Build Coastguard Worker 264*3c7ae9deSAndroid Build Coastguard Worker if (self.output_file is not None): 265*3c7ae9deSAndroid Build Coastguard Worker self.output_file.close() 266*3c7ae9deSAndroid Build Coastguard Worker self.output_file = None 267*3c7ae9deSAndroid Build Coastguard Worker 268*3c7ae9deSAndroid Build Coastguard Worker # Utility function for printing messages 269*3c7ae9deSAndroid Build Coastguard Worker def print_message(self, message): 270*3c7ae9deSAndroid Build Coastguard Worker if self.output_to_file == True: 271*3c7ae9deSAndroid Build Coastguard Worker self.output_file.write(message + "\n") 272*3c7ae9deSAndroid Build Coastguard Worker if self.output_to_console == True: 273*3c7ae9deSAndroid Build Coastguard Worker print(message, flush=True) 274*3c7ae9deSAndroid Build Coastguard Worker 275*3c7ae9deSAndroid Build Coastguard Worker # Utility function - adds the metric alarms to Cloudwatch. We do run this right before the first 276*3c7ae9deSAndroid Build Coastguard Worker # collection of metrics so we can register metrics before we initialize Cloudwatch 277*3c7ae9deSAndroid Build Coastguard Worker def _init_cloudwatch_pre_first_run(self): 278*3c7ae9deSAndroid Build Coastguard Worker for metric in self.metrics: 279*3c7ae9deSAndroid Build Coastguard Worker if (not metric.metric_alarm_threshold is None): 280*3c7ae9deSAndroid Build Coastguard Worker self._add_cloudwatch_metric_alarm(metric) 281*3c7ae9deSAndroid Build Coastguard Worker 282*3c7ae9deSAndroid Build Coastguard Worker if (self.cloudwatch_make_dashboard == True): 283*3c7ae9deSAndroid Build Coastguard Worker self._init_cloudwatch_pre_first_run_dashboard() 284*3c7ae9deSAndroid Build Coastguard Worker 285*3c7ae9deSAndroid Build Coastguard Worker # Utility function - adds the Cloudwatch Dashboard for the currently running data snapshot 286*3c7ae9deSAndroid Build Coastguard Worker def _init_cloudwatch_pre_first_run_dashboard(self): 287*3c7ae9deSAndroid Build Coastguard Worker try: 288*3c7ae9deSAndroid Build Coastguard Worker # Remove the old dashboard if it exists before adding a new one 289*3c7ae9deSAndroid Build Coastguard Worker self._cleanup_cloudwatch_dashboard() 290*3c7ae9deSAndroid Build Coastguard Worker 291*3c7ae9deSAndroid Build Coastguard Worker new_dashboard_widgets_array = [] 292*3c7ae9deSAndroid Build Coastguard Worker for widget in self.cloudwatch_dashboard_widgets: 293*3c7ae9deSAndroid Build Coastguard Worker new_dashboard_widgets_array.append(widget.get_widget_dictionary()) 294*3c7ae9deSAndroid Build Coastguard Worker 295*3c7ae9deSAndroid Build Coastguard Worker new_dashboard_body = { 296*3c7ae9deSAndroid Build Coastguard Worker "start": "-PT1H", 297*3c7ae9deSAndroid Build Coastguard Worker "widgets": new_dashboard_widgets_array, 298*3c7ae9deSAndroid Build Coastguard Worker } 299*3c7ae9deSAndroid Build Coastguard Worker new_dashboard_body_json = json.dumps(new_dashboard_body) 300*3c7ae9deSAndroid Build Coastguard Worker 301*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_client.put_dashboard( 302*3c7ae9deSAndroid Build Coastguard Worker DashboardName=self.cloudwatch_dashboard_name, 303*3c7ae9deSAndroid Build Coastguard Worker DashboardBody= new_dashboard_body_json) 304*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Added Cloudwatch dashboard successfully") 305*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 306*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] ERROR - Cloudwatch client could not make dashboard due to exception!") 307*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Exception: " + str(e)) 308*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error = True 309*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error_reason = "Cloudwatch client could not make dashboard due to exception" 310*3c7ae9deSAndroid Build Coastguard Worker return 311*3c7ae9deSAndroid Build Coastguard Worker 312*3c7ae9deSAndroid Build Coastguard Worker # Utility function - The function that adds each individual metric alarm. 313*3c7ae9deSAndroid Build Coastguard Worker def _add_cloudwatch_metric_alarm(self, metric): 314*3c7ae9deSAndroid Build Coastguard Worker if self.cloudwatch_client is None: 315*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] ERROR - Cloudwatch client not setup. Cannot register alarm") 316*3c7ae9deSAndroid Build Coastguard Worker return 317*3c7ae9deSAndroid Build Coastguard Worker 318*3c7ae9deSAndroid Build Coastguard Worker try: 319*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_client.put_metric_alarm( 320*3c7ae9deSAndroid Build Coastguard Worker AlarmName=metric.metric_alarm_name, 321*3c7ae9deSAndroid Build Coastguard Worker AlarmDescription=metric.metric_alarm_description, 322*3c7ae9deSAndroid Build Coastguard Worker MetricName=metric.metric_name, 323*3c7ae9deSAndroid Build Coastguard Worker Namespace=self.git_metric_namespace, 324*3c7ae9deSAndroid Build Coastguard Worker Statistic="Maximum", 325*3c7ae9deSAndroid Build Coastguard Worker Dimensions=metric.metric_dimensions, 326*3c7ae9deSAndroid Build Coastguard Worker Period=60, # How long (in seconds) is an evaluation period? 327*3c7ae9deSAndroid Build Coastguard Worker EvaluationPeriods=120, # How many periods does it need to be invalid for? 328*3c7ae9deSAndroid Build Coastguard Worker DatapointsToAlarm=1, # How many data points need to be invalid? 329*3c7ae9deSAndroid Build Coastguard Worker Threshold=metric.metric_alarm_threshold, 330*3c7ae9deSAndroid Build Coastguard Worker ComparisonOperator="GreaterThanOrEqualToThreshold", 331*3c7ae9deSAndroid Build Coastguard Worker ) 332*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 333*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] ERROR - could not register alarm for metric due to exception: " + metric.metric_name) 334*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Exception: " + str(e)) 335*3c7ae9deSAndroid Build Coastguard Worker 336*3c7ae9deSAndroid Build Coastguard Worker # Utility function - removes all the Cloudwatch alarms for the metrics 337*3c7ae9deSAndroid Build Coastguard Worker def _cleanup_cloudwatch_alarms(self): 338*3c7ae9deSAndroid Build Coastguard Worker if (self.cloudwatch_teardown_alarms_on_complete == True): 339*3c7ae9deSAndroid Build Coastguard Worker try: 340*3c7ae9deSAndroid Build Coastguard Worker for metric in self.metrics: 341*3c7ae9deSAndroid Build Coastguard Worker if (not metric.metric_alarm_threshold is None): 342*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_client.delete_alarms(AlarmNames=[metric.metric_alarm_name]) 343*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 344*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] ERROR - could not delete alarms due to exception!") 345*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Exception: " + str(e)) 346*3c7ae9deSAndroid Build Coastguard Worker 347*3c7ae9deSAndroid Build Coastguard Worker # Utility function - removes all Cloudwatch dashboards created 348*3c7ae9deSAndroid Build Coastguard Worker def _cleanup_cloudwatch_dashboard(self): 349*3c7ae9deSAndroid Build Coastguard Worker if (self.cloudwatch_teardown_dashboard_on_complete == True): 350*3c7ae9deSAndroid Build Coastguard Worker try: 351*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_client.delete_dashboards(DashboardNames=[self.cloudwatch_dashboard_name]) 352*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Cloudwatch Dashboards deleted successfully!") 353*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 354*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] ERROR - dashboard cleaning function failed due to exception!") 355*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Exception: " + str(e)) 356*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error = True 357*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error_reason = "Cloudwatch dashboard cleaning function failed due to exception" 358*3c7ae9deSAndroid Build Coastguard Worker return 359*3c7ae9deSAndroid Build Coastguard Worker 360*3c7ae9deSAndroid Build Coastguard Worker # Returns the results of the metric alarms. Will return a list containing tuples with the following structure: 361*3c7ae9deSAndroid Build Coastguard Worker # [Boolean (False = the alarm is in the ALARM state), String (Name of the alarm that is in the ALARM state), int (severity of alarm)] 362*3c7ae9deSAndroid Build Coastguard Worker # Currently this function will only return a list of failed alarms, so if the returned list is empty, then it means all 363*3c7ae9deSAndroid Build Coastguard Worker # alarms did not get to the ALARM state in Cloudwatch for the registered metrics 364*3c7ae9deSAndroid Build Coastguard Worker def get_cloudwatch_alarm_results(self): 365*3c7ae9deSAndroid Build Coastguard Worker return self._check_cloudwatch_alarm_states() 366*3c7ae9deSAndroid Build Coastguard Worker 367*3c7ae9deSAndroid Build Coastguard Worker # Utility function - collects the metric alarm results and returns them in a list. 368*3c7ae9deSAndroid Build Coastguard Worker def _check_cloudwatch_alarm_states(self): 369*3c7ae9deSAndroid Build Coastguard Worker return_result_list = [] 370*3c7ae9deSAndroid Build Coastguard Worker 371*3c7ae9deSAndroid Build Coastguard Worker tmp = None 372*3c7ae9deSAndroid Build Coastguard Worker for metric in self.metrics: 373*3c7ae9deSAndroid Build Coastguard Worker tmp = self._check_cloudwatch_alarm_state_metric(metric) 374*3c7ae9deSAndroid Build Coastguard Worker if (tmp[1] != None): 375*3c7ae9deSAndroid Build Coastguard Worker # Do not cut a ticket for the "Alive_Alarm" that we use to check if the Canary is running 376*3c7ae9deSAndroid Build Coastguard Worker if ("Alive_Alarm" in tmp[1] == False): 377*3c7ae9deSAndroid Build Coastguard Worker if (tmp[0] != True): 378*3c7ae9deSAndroid Build Coastguard Worker return_result_list.append(tmp) 379*3c7ae9deSAndroid Build Coastguard Worker 380*3c7ae9deSAndroid Build Coastguard Worker return return_result_list 381*3c7ae9deSAndroid Build Coastguard Worker 382*3c7ae9deSAndroid Build Coastguard Worker # Utility function - checks each individual alarm and returns a tuple with the following format: 383*3c7ae9deSAndroid Build Coastguard Worker # [Boolean (False if the alarm is in the ALARM state, otherwise it is true), String (name of the alarm), Int (severity of alarm)] 384*3c7ae9deSAndroid Build Coastguard Worker def _check_cloudwatch_alarm_state_metric(self, metric): 385*3c7ae9deSAndroid Build Coastguard Worker alarms_response = self.cloudwatch_client.describe_alarms_for_metric( 386*3c7ae9deSAndroid Build Coastguard Worker MetricName=metric.metric_name, 387*3c7ae9deSAndroid Build Coastguard Worker Namespace=self.git_metric_namespace, 388*3c7ae9deSAndroid Build Coastguard Worker Dimensions=metric.metric_dimensions) 389*3c7ae9deSAndroid Build Coastguard Worker 390*3c7ae9deSAndroid Build Coastguard Worker return_result = [True, None, metric.metric_alarm_severity] 391*3c7ae9deSAndroid Build Coastguard Worker 392*3c7ae9deSAndroid Build Coastguard Worker for metric_alarm_dict in alarms_response["MetricAlarms"]: 393*3c7ae9deSAndroid Build Coastguard Worker if metric_alarm_dict["StateValue"] == "ALARM": 394*3c7ae9deSAndroid Build Coastguard Worker return_result[0] = False 395*3c7ae9deSAndroid Build Coastguard Worker return_result[1] = metric_alarm_dict["AlarmName"] 396*3c7ae9deSAndroid Build Coastguard Worker break 397*3c7ae9deSAndroid Build Coastguard Worker 398*3c7ae9deSAndroid Build Coastguard Worker return return_result 399*3c7ae9deSAndroid Build Coastguard Worker 400*3c7ae9deSAndroid Build Coastguard Worker # Exports a file with the same name as the commit Git hash to an S3 bucket in a folder with the Git repo name. 401*3c7ae9deSAndroid Build Coastguard Worker # By default, this file will only contain the Git hash. 402*3c7ae9deSAndroid Build Coastguard Worker # If copy_output_log is true, then the output log will be copied into this file, which may be useful for debugging. 403*3c7ae9deSAndroid Build Coastguard Worker def export_result_to_s3_bucket(self, copy_output_log=False, log_is_error=False): 404*3c7ae9deSAndroid Build Coastguard Worker if (self.s3_client is None): 405*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] ERROR - No S3 client initialized! Cannot send log to S3") 406*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error = True 407*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error_reason = "S3 client not initialized and therefore cannot send log to S3" 408*3c7ae9deSAndroid Build Coastguard Worker return 409*3c7ae9deSAndroid Build Coastguard Worker 410*3c7ae9deSAndroid Build Coastguard Worker s3_file = open(self.git_hash + ".log", "w") 411*3c7ae9deSAndroid Build Coastguard Worker s3_file.write(self.git_hash) 412*3c7ae9deSAndroid Build Coastguard Worker 413*3c7ae9deSAndroid Build Coastguard Worker # Might be useful for debugging? 414*3c7ae9deSAndroid Build Coastguard Worker if (copy_output_log == True and self.output_to_file == True): 415*3c7ae9deSAndroid Build Coastguard Worker # Are we still writing? If so, then we need to close the file first so everything is written to it 416*3c7ae9deSAndroid Build Coastguard Worker is_output_file_open_previously = False 417*3c7ae9deSAndroid Build Coastguard Worker if (self.output_file != None): 418*3c7ae9deSAndroid Build Coastguard Worker self.output_file.close() 419*3c7ae9deSAndroid Build Coastguard Worker is_output_file_open_previously = True 420*3c7ae9deSAndroid Build Coastguard Worker self.output_file = open(self.output_to_file_filepath, "r") 421*3c7ae9deSAndroid Build Coastguard Worker 422*3c7ae9deSAndroid Build Coastguard Worker s3_file.write("\n\nOUTPUT LOG\n") 423*3c7ae9deSAndroid Build Coastguard Worker s3_file.write("==========================================================================================\n") 424*3c7ae9deSAndroid Build Coastguard Worker output_file_lines = self.output_file.readlines() 425*3c7ae9deSAndroid Build Coastguard Worker for line in output_file_lines: 426*3c7ae9deSAndroid Build Coastguard Worker s3_file.write(line) 427*3c7ae9deSAndroid Build Coastguard Worker 428*3c7ae9deSAndroid Build Coastguard Worker self.output_file.close() 429*3c7ae9deSAndroid Build Coastguard Worker 430*3c7ae9deSAndroid Build Coastguard Worker # If we were writing to the output previously, then we need to open in RW mode so we can continue to write to it 431*3c7ae9deSAndroid Build Coastguard Worker if (is_output_file_open_previously == True): 432*3c7ae9deSAndroid Build Coastguard Worker self.output_to_file = open(self.output_to_file_filepath, "a") 433*3c7ae9deSAndroid Build Coastguard Worker 434*3c7ae9deSAndroid Build Coastguard Worker s3_file.close() 435*3c7ae9deSAndroid Build Coastguard Worker 436*3c7ae9deSAndroid Build Coastguard Worker # Upload to S3 437*3c7ae9deSAndroid Build Coastguard Worker try: 438*3c7ae9deSAndroid Build Coastguard Worker if (log_is_error == False): 439*3c7ae9deSAndroid Build Coastguard Worker if (self.datetime_string == None): 440*3c7ae9deSAndroid Build Coastguard Worker self.s3_client.upload_file(self.git_hash + ".log", self.s3_bucket_name, self.git_repo_name + "/" + self.git_hash + ".log") 441*3c7ae9deSAndroid Build Coastguard Worker else: 442*3c7ae9deSAndroid Build Coastguard Worker self.s3_client.upload_file(self.git_hash + ".log", self.s3_bucket_name, self.git_repo_name + "/" + self.datetime_string + "/" + self.git_hash + ".log") 443*3c7ae9deSAndroid Build Coastguard Worker else: 444*3c7ae9deSAndroid Build Coastguard Worker if (self.datetime_string == None): 445*3c7ae9deSAndroid Build Coastguard Worker self.s3_client.upload_file(self.git_hash + ".log", self.s3_bucket_name, self.git_repo_name + "/Failed_Logs/" + self.git_hash + ".log") 446*3c7ae9deSAndroid Build Coastguard Worker else: 447*3c7ae9deSAndroid Build Coastguard Worker self.s3_client.upload_file(self.git_hash + ".log", self.s3_bucket_name, self.git_repo_name + "/Failed_Logs/" + self.datetime_string + "/" + self.git_hash + ".log") 448*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Uploaded to S3!") 449*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 450*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] ERROR - could not upload to S3 due to exception!") 451*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Exception: " + str(e)) 452*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error = True 453*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error_reason = "S3 client had exception and therefore could not upload log!" 454*3c7ae9deSAndroid Build Coastguard Worker os.remove(self.git_hash + ".log") 455*3c7ae9deSAndroid Build Coastguard Worker return 456*3c7ae9deSAndroid Build Coastguard Worker 457*3c7ae9deSAndroid Build Coastguard Worker # Delete the file when finished 458*3c7ae9deSAndroid Build Coastguard Worker os.remove(self.git_hash + ".log") 459*3c7ae9deSAndroid Build Coastguard Worker 460*3c7ae9deSAndroid Build Coastguard Worker # Sends an email via a special lambda. The payload has to contain a message and a subject 461*3c7ae9deSAndroid Build Coastguard Worker # * (REQUIRED) message is the message you want to send in the body of the email 462*3c7ae9deSAndroid Build Coastguard Worker # * (REQUIRED) subject is the subject that the email will be sent with 463*3c7ae9deSAndroid Build Coastguard Worker def lambda_send_email(self, message, subject): 464*3c7ae9deSAndroid Build Coastguard Worker 465*3c7ae9deSAndroid Build Coastguard Worker payload = {"Message":message, "Subject":subject} 466*3c7ae9deSAndroid Build Coastguard Worker payload_string = json.dumps(payload) 467*3c7ae9deSAndroid Build Coastguard Worker 468*3c7ae9deSAndroid Build Coastguard Worker try: 469*3c7ae9deSAndroid Build Coastguard Worker self.lambda_client.invoke( 470*3c7ae9deSAndroid Build Coastguard Worker FunctionName=self.lambda_name, 471*3c7ae9deSAndroid Build Coastguard Worker InvocationType="Event", 472*3c7ae9deSAndroid Build Coastguard Worker ClientContext="MQTT Wrapper Script", 473*3c7ae9deSAndroid Build Coastguard Worker Payload=payload_string 474*3c7ae9deSAndroid Build Coastguard Worker ) 475*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 476*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] ERROR - could not send email via Lambda due to exception!") 477*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Exception: " + str(e)) 478*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error = True 479*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error_reason = "Lambda email function had an exception!" 480*3c7ae9deSAndroid Build Coastguard Worker return 481*3c7ae9deSAndroid Build Coastguard Worker 482*3c7ae9deSAndroid Build Coastguard Worker # Registers a metric to be polled by the Snapshot. 483*3c7ae9deSAndroid Build Coastguard Worker # * (REQUIRED) new_metric_name is the name of the metric. Cloudwatch will use this name 484*3c7ae9deSAndroid Build Coastguard Worker # * (REQUIRED) new_metric_function is expected to be a pointer to a Python function and will not work if you pass a value/object 485*3c7ae9deSAndroid Build Coastguard Worker # * (OPTIONAL) new_metric_unit is the metric unit. There is a list of possible metric unit types on the Boto3 documentation for Cloudwatch 486*3c7ae9deSAndroid Build Coastguard Worker # * (OPTIONAL) new_metric_alarm_threshold is the value that the metric has to exceed in order to be registered as an alarm 487*3c7ae9deSAndroid Build Coastguard Worker # * (OPTIONAL) new_reports_to_skip is the number of reports this metric will return nothing, but will get it's value. 488*3c7ae9deSAndroid Build Coastguard Worker # * Useful for CPU calculations that require deltas 489*3c7ae9deSAndroid Build Coastguard Worker # * (OPTIONAL) new_metric_alarm_severity is the severity of the ticket if this alarm is triggered. A severity of 6+ means no ticket. 490*3c7ae9deSAndroid Build Coastguard Worker # * (OPTIONAL) is_percent whether or not to display the metric as a percent when printing it (default=false) 491*3c7ae9deSAndroid Build Coastguard Worker def register_metric(self, new_metric_name, new_metric_function, new_metric_unit="None", 492*3c7ae9deSAndroid Build Coastguard Worker new_metric_alarm_threshold=None, new_metric_reports_to_skip=0, new_metric_alarm_severity=6, is_percent=False): 493*3c7ae9deSAndroid Build Coastguard Worker 494*3c7ae9deSAndroid Build Coastguard Worker new_metric_dimensions = [] 495*3c7ae9deSAndroid Build Coastguard Worker 496*3c7ae9deSAndroid Build Coastguard Worker if (self.git_hash_as_namespace == False): 497*3c7ae9deSAndroid Build Coastguard Worker git_namespace_prepend_text = self.git_repo_name + "-" + self.git_hash 498*3c7ae9deSAndroid Build Coastguard Worker new_metric_dimensions.append( 499*3c7ae9deSAndroid Build Coastguard Worker {"Name": git_namespace_prepend_text, "Value": new_metric_name}) 500*3c7ae9deSAndroid Build Coastguard Worker else: 501*3c7ae9deSAndroid Build Coastguard Worker new_metric_dimensions.append( 502*3c7ae9deSAndroid Build Coastguard Worker {"Name": "System_Metrics", "Value": new_metric_name}) 503*3c7ae9deSAndroid Build Coastguard Worker 504*3c7ae9deSAndroid Build Coastguard Worker new_metric = DataSnapshot_Metric( 505*3c7ae9deSAndroid Build Coastguard Worker metric_name=new_metric_name, 506*3c7ae9deSAndroid Build Coastguard Worker metric_function=new_metric_function, 507*3c7ae9deSAndroid Build Coastguard Worker metric_dimensions=new_metric_dimensions, 508*3c7ae9deSAndroid Build Coastguard Worker metric_unit=new_metric_unit, 509*3c7ae9deSAndroid Build Coastguard Worker metric_alarm_threshold=new_metric_alarm_threshold, 510*3c7ae9deSAndroid Build Coastguard Worker metric_alarm_severity=new_metric_alarm_severity, 511*3c7ae9deSAndroid Build Coastguard Worker git_hash=self.git_hash, 512*3c7ae9deSAndroid Build Coastguard Worker git_repo_name=self.git_repo_name, 513*3c7ae9deSAndroid Build Coastguard Worker reports_to_skip=new_metric_reports_to_skip, 514*3c7ae9deSAndroid Build Coastguard Worker is_percent=is_percent 515*3c7ae9deSAndroid Build Coastguard Worker ) 516*3c7ae9deSAndroid Build Coastguard Worker self.metrics.append(new_metric) 517*3c7ae9deSAndroid Build Coastguard Worker # append an empty list so we can track it's metrics over time 518*3c7ae9deSAndroid Build Coastguard Worker self.metrics_numbers.append([]) 519*3c7ae9deSAndroid Build Coastguard Worker 520*3c7ae9deSAndroid Build Coastguard Worker def register_dashboard_widget(self, new_widget_name, metrics_to_add=[], new_widget_period=60): 521*3c7ae9deSAndroid Build Coastguard Worker 522*3c7ae9deSAndroid Build Coastguard Worker # We need to know what metric dimension to get the metric(s) from 523*3c7ae9deSAndroid Build Coastguard Worker metric_dimension_string = "" 524*3c7ae9deSAndroid Build Coastguard Worker if (self.git_hash_as_namespace == False): 525*3c7ae9deSAndroid Build Coastguard Worker metric_dimension_string = self.git_repo_name + "-" + self.git_hash 526*3c7ae9deSAndroid Build Coastguard Worker else: 527*3c7ae9deSAndroid Build Coastguard Worker metric_dimension_string = "System_Metrics" 528*3c7ae9deSAndroid Build Coastguard Worker 529*3c7ae9deSAndroid Build Coastguard Worker widget = self._find_cloudwatch_widget(name=new_widget_name) 530*3c7ae9deSAndroid Build Coastguard Worker if (widget == None): 531*3c7ae9deSAndroid Build Coastguard Worker widget = DataSnapshot_Dashboard_Widget( 532*3c7ae9deSAndroid Build Coastguard Worker widget_name=new_widget_name, metric_namespace=self.git_metric_namespace, 533*3c7ae9deSAndroid Build Coastguard Worker metric_dimension=metric_dimension_string, 534*3c7ae9deSAndroid Build Coastguard Worker cloudwatch_region=self.cloudwatch_region, 535*3c7ae9deSAndroid Build Coastguard Worker widget_period=new_widget_period) 536*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_dashboard_widgets.append(widget) 537*3c7ae9deSAndroid Build Coastguard Worker 538*3c7ae9deSAndroid Build Coastguard Worker for metric in metrics_to_add: 539*3c7ae9deSAndroid Build Coastguard Worker self.register_metric_to_dashboard_widget(widget_name=new_widget_name, metric_name=metric) 540*3c7ae9deSAndroid Build Coastguard Worker 541*3c7ae9deSAndroid Build Coastguard Worker def register_metric_to_dashboard_widget(self, widget_name, metric_name, widget=None): 542*3c7ae9deSAndroid Build Coastguard Worker if widget is None: 543*3c7ae9deSAndroid Build Coastguard Worker widget = self._find_cloudwatch_widget(name=widget_name) 544*3c7ae9deSAndroid Build Coastguard Worker if widget is None: 545*3c7ae9deSAndroid Build Coastguard Worker print ("[DataSnapshot] ERROR - could not find widget with name: " + widget_name, flush=True) 546*3c7ae9deSAndroid Build Coastguard Worker return 547*3c7ae9deSAndroid Build Coastguard Worker 548*3c7ae9deSAndroid Build Coastguard Worker # Adjust metric name so it has the git hash, repo, etc 549*3c7ae9deSAndroid Build Coastguard Worker metric_name_formatted = metric_name 550*3c7ae9deSAndroid Build Coastguard Worker 551*3c7ae9deSAndroid Build Coastguard Worker widget.add_metric_to_widget(new_metric_name=metric_name_formatted) 552*3c7ae9deSAndroid Build Coastguard Worker return 553*3c7ae9deSAndroid Build Coastguard Worker 554*3c7ae9deSAndroid Build Coastguard Worker def remove_metric_from_dashboard_widget(self, widget_name, metric_name, widget=None): 555*3c7ae9deSAndroid Build Coastguard Worker if widget is None: 556*3c7ae9deSAndroid Build Coastguard Worker widget = self._find_cloudwatch_widget(name=widget_name) 557*3c7ae9deSAndroid Build Coastguard Worker if widget is None: 558*3c7ae9deSAndroid Build Coastguard Worker print ("[DataSnapshot] ERROR - could not find widget with name: " + widget_name, flush=True) 559*3c7ae9deSAndroid Build Coastguard Worker return 560*3c7ae9deSAndroid Build Coastguard Worker widget.remove_metric_from_widget(existing_metric_name=metric_name) 561*3c7ae9deSAndroid Build Coastguard Worker return 562*3c7ae9deSAndroid Build Coastguard Worker 563*3c7ae9deSAndroid Build Coastguard Worker def _find_cloudwatch_widget(self, name): 564*3c7ae9deSAndroid Build Coastguard Worker result = None 565*3c7ae9deSAndroid Build Coastguard Worker for widget in self.cloudwatch_dashboard_widgets: 566*3c7ae9deSAndroid Build Coastguard Worker if widget.widget_name == name: 567*3c7ae9deSAndroid Build Coastguard Worker return widget 568*3c7ae9deSAndroid Build Coastguard Worker return result 569*3c7ae9deSAndroid Build Coastguard Worker 570*3c7ae9deSAndroid Build Coastguard Worker # Prints the metrics to the console 571*3c7ae9deSAndroid Build Coastguard Worker def export_metrics_console(self): 572*3c7ae9deSAndroid Build Coastguard Worker datetime_now = datetime.datetime.now() 573*3c7ae9deSAndroid Build Coastguard Worker datetime_string = datetime_now.strftime("%d-%m-%Y/%H:%M:%S") 574*3c7ae9deSAndroid Build Coastguard Worker 575*3c7ae9deSAndroid Build Coastguard Worker self.print_message("\n[DataSnapshot] Metric report: " + str(self.metric_report_number) + " (" + datetime_string + ")") 576*3c7ae9deSAndroid Build Coastguard Worker for metric in self.metrics: 577*3c7ae9deSAndroid Build Coastguard Worker if (metric.is_percent == True): 578*3c7ae9deSAndroid Build Coastguard Worker self.print_message(" " + metric.metric_name + 579*3c7ae9deSAndroid Build Coastguard Worker " - value: " + str(metric.metric_value) + "%") 580*3c7ae9deSAndroid Build Coastguard Worker else: 581*3c7ae9deSAndroid Build Coastguard Worker self.print_message(" " + metric.metric_name + 582*3c7ae9deSAndroid Build Coastguard Worker " - value: " + str(metric.metric_value)) 583*3c7ae9deSAndroid Build Coastguard Worker self.print_message("") 584*3c7ae9deSAndroid Build Coastguard Worker 585*3c7ae9deSAndroid Build Coastguard Worker # Sends all registered metrics to Cloudwatch. 586*3c7ae9deSAndroid Build Coastguard Worker # Does NOT need to called on loop. Call post_metrics on loop to send all the metrics as expected. 587*3c7ae9deSAndroid Build Coastguard Worker # This is just the Cloudwatch part of that loop. 588*3c7ae9deSAndroid Build Coastguard Worker def export_metrics_cloudwatch(self): 589*3c7ae9deSAndroid Build Coastguard Worker if (self.cloudwatch_client == None): 590*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Error - cannot export Cloudwatch metrics! Cloudwatch was not initialized.") 591*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error = True 592*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error_reason = "Could not export Cloudwatch metrics due to no Cloudwatch client initialized!" 593*3c7ae9deSAndroid Build Coastguard Worker return 594*3c7ae9deSAndroid Build Coastguard Worker 595*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Preparing to send to Cloudwatch...") 596*3c7ae9deSAndroid Build Coastguard Worker metrics_data = [] 597*3c7ae9deSAndroid Build Coastguard Worker metric_data_tmp = None 598*3c7ae9deSAndroid Build Coastguard Worker for metric in self.metrics: 599*3c7ae9deSAndroid Build Coastguard Worker metric_data_tmp = metric.get_metric_cloudwatch_dictionary() 600*3c7ae9deSAndroid Build Coastguard Worker if (not metric_data_tmp is None): 601*3c7ae9deSAndroid Build Coastguard Worker metrics_data.append(metric_data_tmp) 602*3c7ae9deSAndroid Build Coastguard Worker 603*3c7ae9deSAndroid Build Coastguard Worker if (len(metrics_data) == 0): 604*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] INFO - no metric data to send. Skipping...") 605*3c7ae9deSAndroid Build Coastguard Worker return 606*3c7ae9deSAndroid Build Coastguard Worker 607*3c7ae9deSAndroid Build Coastguard Worker try: 608*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_client.put_metric_data( 609*3c7ae9deSAndroid Build Coastguard Worker Namespace=self.git_metric_namespace, 610*3c7ae9deSAndroid Build Coastguard Worker MetricData=metrics_data) 611*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Metrics sent to Cloudwatch.") 612*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 613*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Error - something when wrong posting cloudwatch metrics!") 614*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[DataSnapshot] Exception: " + str(e)) 615*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error = True 616*3c7ae9deSAndroid Build Coastguard Worker self.abort_due_to_internal_error_reason = "Could not export Cloudwatch metrics due to exception in Cloudwatch client!" 617*3c7ae9deSAndroid Build Coastguard Worker return 618*3c7ae9deSAndroid Build Coastguard Worker 619*3c7ae9deSAndroid Build Coastguard Worker # Call this at a set interval to post the metrics to Cloudwatch, etc. 620*3c7ae9deSAndroid Build Coastguard Worker # This is the function you want to call repeatedly after you have everything setup. 621*3c7ae9deSAndroid Build Coastguard Worker def post_metrics(self, psutil_process : psutil.Process): 622*3c7ae9deSAndroid Build Coastguard Worker if (self.perform_final_initialization == True): 623*3c7ae9deSAndroid Build Coastguard Worker self.perform_final_initialization = False 624*3c7ae9deSAndroid Build Coastguard Worker self._init_cloudwatch_pre_first_run() 625*3c7ae9deSAndroid Build Coastguard Worker 626*3c7ae9deSAndroid Build Coastguard Worker # Update the metric values internally 627*3c7ae9deSAndroid Build Coastguard Worker for i in range(0, len(self.metrics)): 628*3c7ae9deSAndroid Build Coastguard Worker metric_value = self.metrics[i].get_metric_value(psutil_process) 629*3c7ae9deSAndroid Build Coastguard Worker self.metrics_numbers[i].insert(0, metric_value) 630*3c7ae9deSAndroid Build Coastguard Worker 631*3c7ae9deSAndroid Build Coastguard Worker # Only keep the last metric_report_non_zero_count results 632*3c7ae9deSAndroid Build Coastguard Worker if (len(self.metrics_numbers[i]) > self.metric_report_non_zero_count): 633*3c7ae9deSAndroid Build Coastguard Worker amount_to_delete = len(self.metrics_numbers[i]) - self.metric_report_non_zero_count 634*3c7ae9deSAndroid Build Coastguard Worker del self.metrics_numbers[i][-amount_to_delete:] 635*3c7ae9deSAndroid Build Coastguard Worker # If we have metric_report_non_zero_count amount of metrics, make sure there is at least one 636*3c7ae9deSAndroid Build Coastguard Worker # non-zero. If it is all zero, then print a log so we can easily find it 637*3c7ae9deSAndroid Build Coastguard Worker if (len(self.metrics_numbers[i]) == self.metric_report_non_zero_count): 638*3c7ae9deSAndroid Build Coastguard Worker non_zero_found = False 639*3c7ae9deSAndroid Build Coastguard Worker for j in range(0, len(self.metrics_numbers[i])): 640*3c7ae9deSAndroid Build Coastguard Worker if (self.metrics_numbers[i][j] != 0.0 and self.metrics_numbers[i][j] != None): 641*3c7ae9deSAndroid Build Coastguard Worker non_zero_found = True 642*3c7ae9deSAndroid Build Coastguard Worker break 643*3c7ae9deSAndroid Build Coastguard Worker if (non_zero_found == False): 644*3c7ae9deSAndroid Build Coastguard Worker self.print_message("\n[DataSnapshot] METRIC ZERO ERROR!") 645*3c7ae9deSAndroid Build Coastguard Worker self.print_message(f"[DataSnapshot] Metric index {i} has been zero for last {self.metric_report_non_zero_count} reports!") 646*3c7ae9deSAndroid Build Coastguard Worker self.print_message("\n") 647*3c7ae9deSAndroid Build Coastguard Worker 648*3c7ae9deSAndroid Build Coastguard Worker self.metric_report_number += 1 649*3c7ae9deSAndroid Build Coastguard Worker 650*3c7ae9deSAndroid Build Coastguard Worker self.export_metrics_console() 651*3c7ae9deSAndroid Build Coastguard Worker self.export_metrics_cloudwatch() 652*3c7ae9deSAndroid Build Coastguard Worker 653*3c7ae9deSAndroid Build Coastguard Worker def output_diagnosis_information(self, dependencies_list): 654*3c7ae9deSAndroid Build Coastguard Worker 655*3c7ae9deSAndroid Build Coastguard Worker # Print general diagnosis information 656*3c7ae9deSAndroid Build Coastguard Worker self.print_message("\n========== Canary Wrapper diagnosis information ==========") 657*3c7ae9deSAndroid Build Coastguard Worker self.print_message("\nRunning Canary for repository: " + self.git_repo_name) 658*3c7ae9deSAndroid Build Coastguard Worker self.print_message("\t Commit hash: " + self.git_hash) 659*3c7ae9deSAndroid Build Coastguard Worker 660*3c7ae9deSAndroid Build Coastguard Worker if not dependencies_list == "": 661*3c7ae9deSAndroid Build Coastguard Worker self.print_message("\nDependencies:") 662*3c7ae9deSAndroid Build Coastguard Worker dependencies_list = dependencies_list.split(";") 663*3c7ae9deSAndroid Build Coastguard Worker dependencies_list_found_hash = False 664*3c7ae9deSAndroid Build Coastguard Worker for i in range(0, len(dependencies_list)): 665*3c7ae9deSAndroid Build Coastguard Worker # There's probably a better way to do this... 666*3c7ae9deSAndroid Build Coastguard Worker if (dependencies_list_found_hash == True): 667*3c7ae9deSAndroid Build Coastguard Worker dependencies_list_found_hash = False 668*3c7ae9deSAndroid Build Coastguard Worker continue 669*3c7ae9deSAndroid Build Coastguard Worker self.print_message("* " + dependencies_list[i]) 670*3c7ae9deSAndroid Build Coastguard Worker if (i+1 < len(dependencies_list)): 671*3c7ae9deSAndroid Build Coastguard Worker self.print_message("\t Commit hash: " + dependencies_list[i+1]) 672*3c7ae9deSAndroid Build Coastguard Worker dependencies_list_found_hash = True 673*3c7ae9deSAndroid Build Coastguard Worker else: 674*3c7ae9deSAndroid Build Coastguard Worker self.print_message("\t Commit hash: Unknown") 675*3c7ae9deSAndroid Build Coastguard Worker 676*3c7ae9deSAndroid Build Coastguard Worker if (self.metric_frequency != None): 677*3c7ae9deSAndroid Build Coastguard Worker self.print_message("\nMetric Snapshot Frequency: " + str(self.metric_frequency) + " seconds") 678*3c7ae9deSAndroid Build Coastguard Worker self.print_message("\nMetrics:") 679*3c7ae9deSAndroid Build Coastguard Worker for metric in self.metrics: 680*3c7ae9deSAndroid Build Coastguard Worker self.print_message("* " + metric.metric_name) 681*3c7ae9deSAndroid Build Coastguard Worker if metric.metric_alarm_threshold is not None: 682*3c7ae9deSAndroid Build Coastguard Worker self.print_message("\t Alarm Threshold: " + str(metric.metric_alarm_threshold)) 683*3c7ae9deSAndroid Build Coastguard Worker self.print_message("\t Alarm Severity: " + str(metric.metric_alarm_severity)) 684*3c7ae9deSAndroid Build Coastguard Worker else: 685*3c7ae9deSAndroid Build Coastguard Worker self.print_message("\t No alarm set for metric.") 686*3c7ae9deSAndroid Build Coastguard Worker 687*3c7ae9deSAndroid Build Coastguard Worker self.print_message("\n") 688*3c7ae9deSAndroid Build Coastguard Worker self.print_message("==========================================================") 689*3c7ae9deSAndroid Build Coastguard Worker self.print_message("\n") 690*3c7ae9deSAndroid Build Coastguard Worker 691*3c7ae9deSAndroid Build Coastguard Worker# ================================================================================ 692*3c7ae9deSAndroid Build Coastguard Worker 693*3c7ae9deSAndroid Build Coastguard Workerclass SnapshotMonitor(): 694*3c7ae9deSAndroid Build Coastguard Worker def __init__(self, wrapper_data_snapshot, wrapper_metrics_wait_time) -> None: 695*3c7ae9deSAndroid Build Coastguard Worker 696*3c7ae9deSAndroid Build Coastguard Worker self.data_snapshot = wrapper_data_snapshot 697*3c7ae9deSAndroid Build Coastguard Worker self.had_internal_error = False 698*3c7ae9deSAndroid Build Coastguard Worker self.error_due_to_credentials = False 699*3c7ae9deSAndroid Build Coastguard Worker self.internal_error_reason = "" 700*3c7ae9deSAndroid Build Coastguard Worker self.error_due_to_alarm = False 701*3c7ae9deSAndroid Build Coastguard Worker 702*3c7ae9deSAndroid Build Coastguard Worker self.can_cut_ticket = False 703*3c7ae9deSAndroid Build Coastguard Worker self.has_cut_ticket = False 704*3c7ae9deSAndroid Build Coastguard Worker 705*3c7ae9deSAndroid Build Coastguard Worker # A list of all the alarms triggered in the last check, cached for later 706*3c7ae9deSAndroid Build Coastguard Worker # NOTE - this is only the alarm names! Not the severity. This just makes it easier to process 707*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_current_alarms_triggered = [] 708*3c7ae9deSAndroid Build Coastguard Worker 709*3c7ae9deSAndroid Build Coastguard Worker # Check for errors 710*3c7ae9deSAndroid Build Coastguard Worker if (self.data_snapshot.abort_due_to_internal_error == True): 711*3c7ae9deSAndroid Build Coastguard Worker self.had_internal_error = True 712*3c7ae9deSAndroid Build Coastguard Worker self.internal_error_reason = "Could not initialize DataSnapshot. Likely credentials are not setup!" 713*3c7ae9deSAndroid Build Coastguard Worker if (self.data_snapshot.abort_due_to_internal_error_due_to_credentials == True): 714*3c7ae9deSAndroid Build Coastguard Worker self.error_due_to_credentials = True 715*3c7ae9deSAndroid Build Coastguard Worker self.data_snapshot.cleanup() 716*3c7ae9deSAndroid Build Coastguard Worker return 717*3c7ae9deSAndroid Build Coastguard Worker 718*3c7ae9deSAndroid Build Coastguard Worker # How long to wait before posting a metric 719*3c7ae9deSAndroid Build Coastguard Worker self.metric_post_timer = 0 720*3c7ae9deSAndroid Build Coastguard Worker self.metric_post_timer_time = wrapper_metrics_wait_time 721*3c7ae9deSAndroid Build Coastguard Worker 722*3c7ae9deSAndroid Build Coastguard Worker 723*3c7ae9deSAndroid Build Coastguard Worker def register_metric(self, new_metric_name, new_metric_function, new_metric_unit="None", new_metric_alarm_threshold=None, 724*3c7ae9deSAndroid Build Coastguard Worker new_metric_reports_to_skip=0, new_metric_alarm_severity=6): 725*3c7ae9deSAndroid Build Coastguard Worker 726*3c7ae9deSAndroid Build Coastguard Worker try: 727*3c7ae9deSAndroid Build Coastguard Worker self.data_snapshot.register_metric( 728*3c7ae9deSAndroid Build Coastguard Worker new_metric_name=new_metric_name, 729*3c7ae9deSAndroid Build Coastguard Worker new_metric_function=new_metric_function, 730*3c7ae9deSAndroid Build Coastguard Worker new_metric_unit=new_metric_unit, 731*3c7ae9deSAndroid Build Coastguard Worker new_metric_alarm_threshold=new_metric_alarm_threshold, 732*3c7ae9deSAndroid Build Coastguard Worker new_metric_reports_to_skip=new_metric_reports_to_skip, 733*3c7ae9deSAndroid Build Coastguard Worker new_metric_alarm_severity=new_metric_alarm_severity) 734*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 735*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[SnaptshotMonitor] ERROR - could not register metric in data snapshot due to exception!") 736*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[SnaptshotMonitor] Exception: " + str(e)) 737*3c7ae9deSAndroid Build Coastguard Worker self.had_internal_error = True 738*3c7ae9deSAndroid Build Coastguard Worker self.internal_error_reason = "Could not register metric in data snapshot due to exception" 739*3c7ae9deSAndroid Build Coastguard Worker return 740*3c7ae9deSAndroid Build Coastguard Worker 741*3c7ae9deSAndroid Build Coastguard Worker def register_dashboard_widget(self, new_widget_name, metrics_to_add=[], widget_period=60): 742*3c7ae9deSAndroid Build Coastguard Worker self.data_snapshot.register_dashboard_widget(new_widget_name=new_widget_name, metrics_to_add=metrics_to_add, new_widget_period=widget_period) 743*3c7ae9deSAndroid Build Coastguard Worker 744*3c7ae9deSAndroid Build Coastguard Worker def output_diagnosis_information(self, dependencies=""): 745*3c7ae9deSAndroid Build Coastguard Worker self.data_snapshot.output_diagnosis_information(dependencies_list=dependencies) 746*3c7ae9deSAndroid Build Coastguard Worker 747*3c7ae9deSAndroid Build Coastguard Worker def check_alarms_for_new_alarms(self, triggered_alarms): 748*3c7ae9deSAndroid Build Coastguard Worker 749*3c7ae9deSAndroid Build Coastguard Worker if len(triggered_alarms) > 0: 750*3c7ae9deSAndroid Build Coastguard Worker self.data_snapshot.print_message( 751*3c7ae9deSAndroid Build Coastguard Worker "WARNING - One or more alarms are in state of ALARM") 752*3c7ae9deSAndroid Build Coastguard Worker 753*3c7ae9deSAndroid Build Coastguard Worker old_alarms_still_active = [] 754*3c7ae9deSAndroid Build Coastguard Worker new_alarms = [] 755*3c7ae9deSAndroid Build Coastguard Worker new_alarms_highest_severity = 6 756*3c7ae9deSAndroid Build Coastguard Worker new_alarm_found = True 757*3c7ae9deSAndroid Build Coastguard Worker new_alarm_ticket_description = "Canary has metrics in ALARM state!\n\nMetrics in alarm:\n" 758*3c7ae9deSAndroid Build Coastguard Worker 759*3c7ae9deSAndroid Build Coastguard Worker for triggered_alarm in triggered_alarms: 760*3c7ae9deSAndroid Build Coastguard Worker new_alarm_found = True 761*3c7ae9deSAndroid Build Coastguard Worker 762*3c7ae9deSAndroid Build Coastguard Worker # Is this a new alarm? 763*3c7ae9deSAndroid Build Coastguard Worker for old_alarm_name in self.cloudwatch_current_alarms_triggered: 764*3c7ae9deSAndroid Build Coastguard Worker if (old_alarm_name == triggered_alarm[1]): 765*3c7ae9deSAndroid Build Coastguard Worker new_alarm_found = False 766*3c7ae9deSAndroid Build Coastguard Worker old_alarms_still_active.append(triggered_alarm[1]) 767*3c7ae9deSAndroid Build Coastguard Worker 768*3c7ae9deSAndroid Build Coastguard Worker new_alarm_ticket_description += "* (STILL IN ALARM) " + triggered_alarm[1] + "\n" 769*3c7ae9deSAndroid Build Coastguard Worker new_alarm_ticket_description += "\tSeverity: " + str(triggered_alarm[2]) 770*3c7ae9deSAndroid Build Coastguard Worker new_alarm_ticket_description += "\n" 771*3c7ae9deSAndroid Build Coastguard Worker break 772*3c7ae9deSAndroid Build Coastguard Worker 773*3c7ae9deSAndroid Build Coastguard Worker # If it is a new alarm, then add it to our list so we can cut a new ticket 774*3c7ae9deSAndroid Build Coastguard Worker if (new_alarm_found == True): 775*3c7ae9deSAndroid Build Coastguard Worker self.data_snapshot.print_message(' (NEW) Alarm with name "' + triggered_alarm[1] + '" is in the ALARM state!') 776*3c7ae9deSAndroid Build Coastguard Worker new_alarms.append(triggered_alarm[1]) 777*3c7ae9deSAndroid Build Coastguard Worker if (triggered_alarm[2] < new_alarms_highest_severity): 778*3c7ae9deSAndroid Build Coastguard Worker new_alarms_highest_severity = triggered_alarm[2] 779*3c7ae9deSAndroid Build Coastguard Worker new_alarm_ticket_description += "* " + triggered_alarm[1] + "\n" 780*3c7ae9deSAndroid Build Coastguard Worker new_alarm_ticket_description += "\tSeverity: " + str(triggered_alarm[2]) 781*3c7ae9deSAndroid Build Coastguard Worker new_alarm_ticket_description += "\n" 782*3c7ae9deSAndroid Build Coastguard Worker 783*3c7ae9deSAndroid Build Coastguard Worker 784*3c7ae9deSAndroid Build Coastguard Worker if len(new_alarms) > 0: 785*3c7ae9deSAndroid Build Coastguard Worker if (self.can_cut_ticket == True): 786*3c7ae9deSAndroid Build Coastguard Worker cut_ticket_using_cloudwatch( 787*3c7ae9deSAndroid Build Coastguard Worker git_repo_name=self.data_snapshot.git_repo_name, 788*3c7ae9deSAndroid Build Coastguard Worker git_hash=self.data_snapshot.git_hash, 789*3c7ae9deSAndroid Build Coastguard Worker git_hash_as_namespace=False, 790*3c7ae9deSAndroid Build Coastguard Worker git_fixed_namespace_text=self.data_snapshot.git_fixed_namespace_text, 791*3c7ae9deSAndroid Build Coastguard Worker cloudwatch_region="us-east-1", 792*3c7ae9deSAndroid Build Coastguard Worker ticket_description="New metric(s) went into alarm for the Canary! Metrics in alarm: " + str(new_alarms), 793*3c7ae9deSAndroid Build Coastguard Worker ticket_reason="New metric(s) went into alarm", 794*3c7ae9deSAndroid Build Coastguard Worker ticket_allow_duplicates=True, 795*3c7ae9deSAndroid Build Coastguard Worker ticket_category="AWS", 796*3c7ae9deSAndroid Build Coastguard Worker ticket_item="IoT SDK for CPP", 797*3c7ae9deSAndroid Build Coastguard Worker ticket_group="AWS IoT Device SDK", 798*3c7ae9deSAndroid Build Coastguard Worker ticket_type="SDKs and Tools", 799*3c7ae9deSAndroid Build Coastguard Worker ticket_severity=4) 800*3c7ae9deSAndroid Build Coastguard Worker self.has_cut_ticket = True 801*3c7ae9deSAndroid Build Coastguard Worker 802*3c7ae9deSAndroid Build Coastguard Worker # Cache the new alarms and the old alarms 803*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_current_alarms_triggered = old_alarms_still_active + new_alarms 804*3c7ae9deSAndroid Build Coastguard Worker 805*3c7ae9deSAndroid Build Coastguard Worker else: 806*3c7ae9deSAndroid Build Coastguard Worker self.cloudwatch_current_alarms_triggered.clear() 807*3c7ae9deSAndroid Build Coastguard Worker 808*3c7ae9deSAndroid Build Coastguard Worker 809*3c7ae9deSAndroid Build Coastguard Worker def monitor_loop_function(self, psutil_process : psutil.Process, time_passed=30): 810*3c7ae9deSAndroid Build Coastguard Worker # Check for internal errors 811*3c7ae9deSAndroid Build Coastguard Worker if (self.data_snapshot.abort_due_to_internal_error == True): 812*3c7ae9deSAndroid Build Coastguard Worker self.had_internal_error = True 813*3c7ae9deSAndroid Build Coastguard Worker self.internal_error_reason = "Data Snapshot internal error: " + self.data_snapshot.abort_due_to_internal_error_reason 814*3c7ae9deSAndroid Build Coastguard Worker return 815*3c7ae9deSAndroid Build Coastguard Worker 816*3c7ae9deSAndroid Build Coastguard Worker try: 817*3c7ae9deSAndroid Build Coastguard Worker # Poll the metric alarms 818*3c7ae9deSAndroid Build Coastguard Worker if (self.had_internal_error == False): 819*3c7ae9deSAndroid Build Coastguard Worker # Get a report of all the alarms that might have been set to an alarm state 820*3c7ae9deSAndroid Build Coastguard Worker triggered_alarms = self.data_snapshot.get_cloudwatch_alarm_results() 821*3c7ae9deSAndroid Build Coastguard Worker self.check_alarms_for_new_alarms(triggered_alarms) 822*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 823*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[SnaptshotMonitor] ERROR - exception occurred checking metric alarms!") 824*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[SnaptshotMonitor] (Likely session credentials expired)") 825*3c7ae9deSAndroid Build Coastguard Worker self.had_internal_error = True 826*3c7ae9deSAndroid Build Coastguard Worker self.internal_error_reason = "Exception occurred checking metric alarms! Likely session credentials expired" 827*3c7ae9deSAndroid Build Coastguard Worker return 828*3c7ae9deSAndroid Build Coastguard Worker 829*3c7ae9deSAndroid Build Coastguard Worker if (self.metric_post_timer <= 0): 830*3c7ae9deSAndroid Build Coastguard Worker if (self.had_internal_error == False): 831*3c7ae9deSAndroid Build Coastguard Worker try: 832*3c7ae9deSAndroid Build Coastguard Worker self.data_snapshot.post_metrics(psutil_process) 833*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 834*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[SnaptshotMonitor] ERROR - exception occurred posting metrics!") 835*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[SnaptshotMonitor] (Likely session credentials expired)") 836*3c7ae9deSAndroid Build Coastguard Worker 837*3c7ae9deSAndroid Build Coastguard Worker print (e, flush=True) 838*3c7ae9deSAndroid Build Coastguard Worker 839*3c7ae9deSAndroid Build Coastguard Worker self.had_internal_error = True 840*3c7ae9deSAndroid Build Coastguard Worker self.internal_error_reason = "Exception occurred posting metrics! Likely session credentials expired" 841*3c7ae9deSAndroid Build Coastguard Worker return 842*3c7ae9deSAndroid Build Coastguard Worker 843*3c7ae9deSAndroid Build Coastguard Worker # reset the timer 844*3c7ae9deSAndroid Build Coastguard Worker self.metric_post_timer += self.metric_post_timer_time 845*3c7ae9deSAndroid Build Coastguard Worker 846*3c7ae9deSAndroid Build Coastguard Worker # Gather and post the metrics 847*3c7ae9deSAndroid Build Coastguard Worker self.metric_post_timer -= time_passed 848*3c7ae9deSAndroid Build Coastguard Worker 849*3c7ae9deSAndroid Build Coastguard Worker 850*3c7ae9deSAndroid Build Coastguard Worker def send_email(self, email_body, email_subject_text_append=None): 851*3c7ae9deSAndroid Build Coastguard Worker if (email_subject_text_append != None): 852*3c7ae9deSAndroid Build Coastguard Worker self.data_snapshot.lambda_send_email(email_body, "Canary: " + self.data_snapshot.git_repo_name + ":" + self.data_snapshot.git_hash + " - " + email_subject_text_append) 853*3c7ae9deSAndroid Build Coastguard Worker else: 854*3c7ae9deSAndroid Build Coastguard Worker self.data_snapshot.lambda_send_email(email_body, "Canary: " + self.data_snapshot.git_repo_name + ":" + self.data_snapshot.git_hash) 855*3c7ae9deSAndroid Build Coastguard Worker 856*3c7ae9deSAndroid Build Coastguard Worker 857*3c7ae9deSAndroid Build Coastguard Worker def stop_monitoring(self): 858*3c7ae9deSAndroid Build Coastguard Worker # Stub - just added for consistency 859*3c7ae9deSAndroid Build Coastguard Worker pass 860*3c7ae9deSAndroid Build Coastguard Worker 861*3c7ae9deSAndroid Build Coastguard Worker 862*3c7ae9deSAndroid Build Coastguard Worker def start_monitoring(self): 863*3c7ae9deSAndroid Build Coastguard Worker # Stub - just added for consistency 864*3c7ae9deSAndroid Build Coastguard Worker pass 865*3c7ae9deSAndroid Build Coastguard Worker 866*3c7ae9deSAndroid Build Coastguard Worker 867*3c7ae9deSAndroid Build Coastguard Worker def restart_monitoring(self): 868*3c7ae9deSAndroid Build Coastguard Worker # Stub - just added for consistency 869*3c7ae9deSAndroid Build Coastguard Worker pass 870*3c7ae9deSAndroid Build Coastguard Worker 871*3c7ae9deSAndroid Build Coastguard Worker 872*3c7ae9deSAndroid Build Coastguard Worker def cleanup_monitor(self, error_occurred=False): 873*3c7ae9deSAndroid Build Coastguard Worker self.data_snapshot.cleanup(error_occurred=error_occurred) 874*3c7ae9deSAndroid Build Coastguard Worker 875*3c7ae9deSAndroid Build Coastguard Worker def print_message(self, message): 876*3c7ae9deSAndroid Build Coastguard Worker if (self.data_snapshot != None): 877*3c7ae9deSAndroid Build Coastguard Worker self.data_snapshot.print_message(message) 878*3c7ae9deSAndroid Build Coastguard Worker else: 879*3c7ae9deSAndroid Build Coastguard Worker print(message, flush=True) 880*3c7ae9deSAndroid Build Coastguard Worker 881*3c7ae9deSAndroid Build Coastguard Worker# ================================================================================ 882*3c7ae9deSAndroid Build Coastguard Worker 883*3c7ae9deSAndroid Build Coastguard Workerclass ApplicationMonitor(): 884*3c7ae9deSAndroid Build Coastguard Worker def __init__(self, wrapper_application_path, wrapper_application_arguments, wrapper_application_restart_on_finish=True, data_snapshot=None) -> None: 885*3c7ae9deSAndroid Build Coastguard Worker self.application_process = None 886*3c7ae9deSAndroid Build Coastguard Worker self.application_process_psutil = None 887*3c7ae9deSAndroid Build Coastguard Worker self.error_has_occurred = False 888*3c7ae9deSAndroid Build Coastguard Worker self.error_due_to_credentials = False 889*3c7ae9deSAndroid Build Coastguard Worker self.error_reason = "" 890*3c7ae9deSAndroid Build Coastguard Worker self.error_code = 0 891*3c7ae9deSAndroid Build Coastguard Worker self.wrapper_application_path = wrapper_application_path 892*3c7ae9deSAndroid Build Coastguard Worker self.wrapper_application_arguments = wrapper_application_arguments 893*3c7ae9deSAndroid Build Coastguard Worker self.wrapper_application_restart_on_finish = wrapper_application_restart_on_finish 894*3c7ae9deSAndroid Build Coastguard Worker self.data_snapshot=data_snapshot 895*3c7ae9deSAndroid Build Coastguard Worker 896*3c7ae9deSAndroid Build Coastguard Worker self.stdout_file_path = "Canary_Stdout_File.txt" 897*3c7ae9deSAndroid Build Coastguard Worker 898*3c7ae9deSAndroid Build Coastguard Worker def start_monitoring(self): 899*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[ApplicationMonitor] Starting to monitor application...") 900*3c7ae9deSAndroid Build Coastguard Worker 901*3c7ae9deSAndroid Build Coastguard Worker if (self.application_process == None): 902*3c7ae9deSAndroid Build Coastguard Worker try: 903*3c7ae9deSAndroid Build Coastguard Worker canary_command = self.wrapper_application_path + " " + self.wrapper_application_arguments 904*3c7ae9deSAndroid Build Coastguard Worker self.application_process = subprocess.Popen(canary_command + " | tee " + self.stdout_file_path, shell=True) 905*3c7ae9deSAndroid Build Coastguard Worker self.application_process_psutil = psutil.Process(self.application_process.pid) 906*3c7ae9deSAndroid Build Coastguard Worker self.print_message ("[ApplicationMonitor] Application started...") 907*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 908*3c7ae9deSAndroid Build Coastguard Worker self.print_message ("[ApplicationMonitor] ERROR - Could not launch Canary/Application due to exception!") 909*3c7ae9deSAndroid Build Coastguard Worker self.print_message ("[ApplicationMonitor] Exception: " + str(e)) 910*3c7ae9deSAndroid Build Coastguard Worker self.error_has_occurred = True 911*3c7ae9deSAndroid Build Coastguard Worker self.error_reason = "Could not launch Canary/Application due to exception" 912*3c7ae9deSAndroid Build Coastguard Worker self.error_code = 1 913*3c7ae9deSAndroid Build Coastguard Worker return 914*3c7ae9deSAndroid Build Coastguard Worker else: 915*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[ApplicationMonitor] ERROR - Monitor already has an application process! Cannot monitor two applications with one monitor class!") 916*3c7ae9deSAndroid Build Coastguard Worker 917*3c7ae9deSAndroid Build Coastguard Worker def restart_monitoring(self): 918*3c7ae9deSAndroid Build Coastguard Worker self.print_message ("[ApplicationMonitor] Restarting monitor application...") 919*3c7ae9deSAndroid Build Coastguard Worker 920*3c7ae9deSAndroid Build Coastguard Worker if (self.application_process != None): 921*3c7ae9deSAndroid Build Coastguard Worker try: 922*3c7ae9deSAndroid Build Coastguard Worker self.stop_monitoring() 923*3c7ae9deSAndroid Build Coastguard Worker self.start_monitoring() 924*3c7ae9deSAndroid Build Coastguard Worker self.print_message("\n[ApplicationMonitor] Restarted monitor application!") 925*3c7ae9deSAndroid Build Coastguard Worker self.print_message("================================================================================") 926*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 927*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[ApplicationMonitor] ERROR - Could not restart Canary/Application due to exception!") 928*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[ApplicationMonitor] Exception: " + str(e)) 929*3c7ae9deSAndroid Build Coastguard Worker self.error_has_occurred = True 930*3c7ae9deSAndroid Build Coastguard Worker self.error_reason = "Could not restart Canary/Application due to exception" 931*3c7ae9deSAndroid Build Coastguard Worker self.error_code = 1 932*3c7ae9deSAndroid Build Coastguard Worker return 933*3c7ae9deSAndroid Build Coastguard Worker else: 934*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[ApplicationMonitor] ERROR - Application process restart called but process is/was not running!") 935*3c7ae9deSAndroid Build Coastguard Worker self.error_has_occurred = True 936*3c7ae9deSAndroid Build Coastguard Worker self.error_reason = "Could not restart Canary/Application due to application process not being started initially" 937*3c7ae9deSAndroid Build Coastguard Worker self.error_code = 1 938*3c7ae9deSAndroid Build Coastguard Worker return 939*3c7ae9deSAndroid Build Coastguard Worker 940*3c7ae9deSAndroid Build Coastguard Worker 941*3c7ae9deSAndroid Build Coastguard Worker def stop_monitoring(self): 942*3c7ae9deSAndroid Build Coastguard Worker self.print_message ("[ApplicationMonitor] Stopping monitor application...") 943*3c7ae9deSAndroid Build Coastguard Worker if (not self.application_process == None): 944*3c7ae9deSAndroid Build Coastguard Worker self.application_process.terminate() 945*3c7ae9deSAndroid Build Coastguard Worker self.application_process.wait() 946*3c7ae9deSAndroid Build Coastguard Worker self.print_message ("[ApplicationMonitor] Stopped monitor application!") 947*3c7ae9deSAndroid Build Coastguard Worker self.application_process = None 948*3c7ae9deSAndroid Build Coastguard Worker self.print_stdout() 949*3c7ae9deSAndroid Build Coastguard Worker else: 950*3c7ae9deSAndroid Build Coastguard Worker self.print_message ("[ApplicationMonitor] ERROR - cannot stop monitor application because no process is found!") 951*3c7ae9deSAndroid Build Coastguard Worker 952*3c7ae9deSAndroid Build Coastguard Worker def print_stdout(self): 953*3c7ae9deSAndroid Build Coastguard Worker # Print the STDOUT file 954*3c7ae9deSAndroid Build Coastguard Worker if (os.path.isfile(self.stdout_file_path)): 955*3c7ae9deSAndroid Build Coastguard Worker self.print_message("Just finished Application STDOUT: ") 956*3c7ae9deSAndroid Build Coastguard Worker with open(self.stdout_file_path, "r") as stdout_file: 957*3c7ae9deSAndroid Build Coastguard Worker self.print_message(stdout_file.read()) 958*3c7ae9deSAndroid Build Coastguard Worker os.remove(self.stdout_file_path) 959*3c7ae9deSAndroid Build Coastguard Worker 960*3c7ae9deSAndroid Build Coastguard Worker def monitor_loop_function(self, time_passed=30): 961*3c7ae9deSAndroid Build Coastguard Worker if (self.application_process != None): 962*3c7ae9deSAndroid Build Coastguard Worker 963*3c7ae9deSAndroid Build Coastguard Worker application_process_return_code = None 964*3c7ae9deSAndroid Build Coastguard Worker try: 965*3c7ae9deSAndroid Build Coastguard Worker application_process_return_code = self.application_process.poll() 966*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 967*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[ApplicationMonitor] ERROR - exception occurred while trying to poll application status!") 968*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[ApplicationMonitor] Exception: " + str(e)) 969*3c7ae9deSAndroid Build Coastguard Worker self.error_has_occurred = True 970*3c7ae9deSAndroid Build Coastguard Worker self.error_reason = "Exception when polling application status" 971*3c7ae9deSAndroid Build Coastguard Worker self.error_code = 1 972*3c7ae9deSAndroid Build Coastguard Worker return 973*3c7ae9deSAndroid Build Coastguard Worker 974*3c7ae9deSAndroid Build Coastguard Worker # If it is not none, then the application finished 975*3c7ae9deSAndroid Build Coastguard Worker if (application_process_return_code != None): 976*3c7ae9deSAndroid Build Coastguard Worker 977*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[ApplicationMonitor] Monitor application has stopped! Processing result...") 978*3c7ae9deSAndroid Build Coastguard Worker 979*3c7ae9deSAndroid Build Coastguard Worker if (application_process_return_code != 0): 980*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[ApplicationMonitor] ERROR - Something Crashed in Canary/Application!") 981*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[ApplicationMonitor] Error code: " + str(application_process_return_code)) 982*3c7ae9deSAndroid Build Coastguard Worker 983*3c7ae9deSAndroid Build Coastguard Worker self.error_has_occurred = True 984*3c7ae9deSAndroid Build Coastguard Worker self.error_reason = "Canary application crashed!" 985*3c7ae9deSAndroid Build Coastguard Worker self.error_code = application_process_return_code 986*3c7ae9deSAndroid Build Coastguard Worker else: 987*3c7ae9deSAndroid Build Coastguard Worker # Should we restart? 988*3c7ae9deSAndroid Build Coastguard Worker if (self.wrapper_application_restart_on_finish == True): 989*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[ApplicationMonitor] NOTE - Canary finished running and is restarting...") 990*3c7ae9deSAndroid Build Coastguard Worker self.restart_monitoring() 991*3c7ae9deSAndroid Build Coastguard Worker else: 992*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[ApplicationMonitor] Monitor application has stopped and monitor is not supposed to restart... Finishing...") 993*3c7ae9deSAndroid Build Coastguard Worker self.error_has_occurred = True 994*3c7ae9deSAndroid Build Coastguard Worker self.error_reason = "Canary Application Finished" 995*3c7ae9deSAndroid Build Coastguard Worker self.error_code = 0 996*3c7ae9deSAndroid Build Coastguard Worker else: 997*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[ApplicationMonitor] Monitor application is still running...") 998*3c7ae9deSAndroid Build Coastguard Worker 999*3c7ae9deSAndroid Build Coastguard Worker def cleanup_monitor(self, error_occurred=False): 1000*3c7ae9deSAndroid Build Coastguard Worker pass 1001*3c7ae9deSAndroid Build Coastguard Worker 1002*3c7ae9deSAndroid Build Coastguard Worker def print_message(self, message): 1003*3c7ae9deSAndroid Build Coastguard Worker if (self.data_snapshot != None): 1004*3c7ae9deSAndroid Build Coastguard Worker self.data_snapshot.print_message(message) 1005*3c7ae9deSAndroid Build Coastguard Worker else: 1006*3c7ae9deSAndroid Build Coastguard Worker print(message, flush=True) 1007*3c7ae9deSAndroid Build Coastguard Worker 1008*3c7ae9deSAndroid Build Coastguard Worker# ================================================================================ 1009*3c7ae9deSAndroid Build Coastguard Worker 1010*3c7ae9deSAndroid Build Coastguard Workerclass S3Monitor(): 1011*3c7ae9deSAndroid Build Coastguard Worker 1012*3c7ae9deSAndroid Build Coastguard Worker def __init__(self, s3_bucket_name, s3_file_name, s3_file_name_in_zip, canary_local_application_path, data_snapshot) -> None: 1013*3c7ae9deSAndroid Build Coastguard Worker self.s3_client = None 1014*3c7ae9deSAndroid Build Coastguard Worker self.s3_current_object_version_id = None 1015*3c7ae9deSAndroid Build Coastguard Worker self.s3_current_object_last_modified = None 1016*3c7ae9deSAndroid Build Coastguard Worker self.s3_bucket_name = s3_bucket_name 1017*3c7ae9deSAndroid Build Coastguard Worker self.s3_file_name = s3_file_name 1018*3c7ae9deSAndroid Build Coastguard Worker self.s3_file_name_only_path, self.s3_file_name_only_extension = os.path.splitext(s3_file_name) 1019*3c7ae9deSAndroid Build Coastguard Worker self.data_snapshot = data_snapshot 1020*3c7ae9deSAndroid Build Coastguard Worker 1021*3c7ae9deSAndroid Build Coastguard Worker self.canary_local_application_path = canary_local_application_path 1022*3c7ae9deSAndroid Build Coastguard Worker 1023*3c7ae9deSAndroid Build Coastguard Worker self.s3_file_name_in_zip = s3_file_name_in_zip 1024*3c7ae9deSAndroid Build Coastguard Worker self.s3_file_name_in_zip_only_path = None 1025*3c7ae9deSAndroid Build Coastguard Worker self.s3_file_name_in_zip_only_extension = None 1026*3c7ae9deSAndroid Build Coastguard Worker if (self.s3_file_name_in_zip != None): 1027*3c7ae9deSAndroid Build Coastguard Worker self.s3_file_name_in_zip_only_path, self.s3_file_name_in_zip_only_extension = os.path.splitext(s3_file_name_in_zip) 1028*3c7ae9deSAndroid Build Coastguard Worker 1029*3c7ae9deSAndroid Build Coastguard Worker self.s3_file_needs_replacing = False 1030*3c7ae9deSAndroid Build Coastguard Worker 1031*3c7ae9deSAndroid Build Coastguard Worker self.had_internal_error = False 1032*3c7ae9deSAndroid Build Coastguard Worker self.error_due_to_credentials = False 1033*3c7ae9deSAndroid Build Coastguard Worker self.internal_error_reason = "" 1034*3c7ae9deSAndroid Build Coastguard Worker 1035*3c7ae9deSAndroid Build Coastguard Worker # Check for valid credentials 1036*3c7ae9deSAndroid Build Coastguard Worker # ================== 1037*3c7ae9deSAndroid Build Coastguard Worker try: 1038*3c7ae9deSAndroid Build Coastguard Worker tmp_sts_client = boto3.client('sts') 1039*3c7ae9deSAndroid Build Coastguard Worker tmp_sts_client.get_caller_identity() 1040*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 1041*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[S3Monitor] ERROR - (S3 Check) AWS credentials are NOT valid!") 1042*3c7ae9deSAndroid Build Coastguard Worker self.had_internal_error = True 1043*3c7ae9deSAndroid Build Coastguard Worker self.error_due_to_credentials = True 1044*3c7ae9deSAndroid Build Coastguard Worker self.internal_error_reason = "AWS credentials are NOT valid!" 1045*3c7ae9deSAndroid Build Coastguard Worker return 1046*3c7ae9deSAndroid Build Coastguard Worker # ================== 1047*3c7ae9deSAndroid Build Coastguard Worker 1048*3c7ae9deSAndroid Build Coastguard Worker try: 1049*3c7ae9deSAndroid Build Coastguard Worker self.s3_client = boto3.client("s3") 1050*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 1051*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[S3Monitor] ERROR - (S3 Check) Could not make S3 client") 1052*3c7ae9deSAndroid Build Coastguard Worker self.had_internal_error = True 1053*3c7ae9deSAndroid Build Coastguard Worker self.internal_error_reason = "Could not make S3 client for S3 Monitor" 1054*3c7ae9deSAndroid Build Coastguard Worker return 1055*3c7ae9deSAndroid Build Coastguard Worker 1056*3c7ae9deSAndroid Build Coastguard Worker 1057*3c7ae9deSAndroid Build Coastguard Worker def check_for_file_change(self): 1058*3c7ae9deSAndroid Build Coastguard Worker try: 1059*3c7ae9deSAndroid Build Coastguard Worker version_check_response = self.s3_client.list_object_versions( 1060*3c7ae9deSAndroid Build Coastguard Worker Bucket=self.s3_bucket_name, 1061*3c7ae9deSAndroid Build Coastguard Worker Prefix=self.s3_file_name_only_path) 1062*3c7ae9deSAndroid Build Coastguard Worker if "Versions" in version_check_response: 1063*3c7ae9deSAndroid Build Coastguard Worker for version in version_check_response["Versions"]: 1064*3c7ae9deSAndroid Build Coastguard Worker if (version["IsLatest"] == True): 1065*3c7ae9deSAndroid Build Coastguard Worker if (version["VersionId"] != self.s3_current_object_version_id or 1066*3c7ae9deSAndroid Build Coastguard Worker version["LastModified"] != self.s3_current_object_last_modified): 1067*3c7ae9deSAndroid Build Coastguard Worker 1068*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[S3Monitor] Found new version of Canary/Application in S3!") 1069*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[S3Monitor] Changing running Canary/Application to new one...") 1070*3c7ae9deSAndroid Build Coastguard Worker 1071*3c7ae9deSAndroid Build Coastguard Worker # Will be checked by thread to trigger replacing the file 1072*3c7ae9deSAndroid Build Coastguard Worker self.s3_file_needs_replacing = True 1073*3c7ae9deSAndroid Build Coastguard Worker 1074*3c7ae9deSAndroid Build Coastguard Worker self.s3_current_object_version_id = version["VersionId"] 1075*3c7ae9deSAndroid Build Coastguard Worker self.s3_current_object_last_modified = version["LastModified"] 1076*3c7ae9deSAndroid Build Coastguard Worker return 1077*3c7ae9deSAndroid Build Coastguard Worker 1078*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 1079*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[S3Monitor] ERROR - Could not check for new version of file in S3 due to exception!") 1080*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[S3Monitor] Exception: " + str(e)) 1081*3c7ae9deSAndroid Build Coastguard Worker self.had_internal_error = True 1082*3c7ae9deSAndroid Build Coastguard Worker self.internal_error_reason = "Could not check for S3 file due to exception in S3 client" 1083*3c7ae9deSAndroid Build Coastguard Worker 1084*3c7ae9deSAndroid Build Coastguard Worker 1085*3c7ae9deSAndroid Build Coastguard Worker def replace_current_file_for_new_file(self): 1086*3c7ae9deSAndroid Build Coastguard Worker try: 1087*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[S3Monitor] Making directory...") 1088*3c7ae9deSAndroid Build Coastguard Worker if not os.path.exists("tmp"): 1089*3c7ae9deSAndroid Build Coastguard Worker os.makedirs("tmp") 1090*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 1091*3c7ae9deSAndroid Build Coastguard Worker self.print_message ("[S3Monitor] ERROR - could not make tmp directory to place S3 file into!") 1092*3c7ae9deSAndroid Build Coastguard Worker self.had_internal_error = True 1093*3c7ae9deSAndroid Build Coastguard Worker self.internal_error_reason = "Could not make TMP folder for S3 file download" 1094*3c7ae9deSAndroid Build Coastguard Worker return 1095*3c7ae9deSAndroid Build Coastguard Worker 1096*3c7ae9deSAndroid Build Coastguard Worker # Download the file 1097*3c7ae9deSAndroid Build Coastguard Worker new_file_path = "tmp/new_file" + self.s3_file_name_only_extension 1098*3c7ae9deSAndroid Build Coastguard Worker try: 1099*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[S3Monitor] Downloading file...") 1100*3c7ae9deSAndroid Build Coastguard Worker s3_resource = boto3.resource("s3") 1101*3c7ae9deSAndroid Build Coastguard Worker s3_resource.meta.client.download_file(self.s3_bucket_name, self.s3_file_name, new_file_path) 1102*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 1103*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[S3Monitor] ERROR - could not download latest S3 file into TMP folder!") 1104*3c7ae9deSAndroid Build Coastguard Worker self.had_internal_error = True 1105*3c7ae9deSAndroid Build Coastguard Worker self.internal_error_reason = "Could not download latest S3 file into TMP folder" 1106*3c7ae9deSAndroid Build Coastguard Worker return 1107*3c7ae9deSAndroid Build Coastguard Worker 1108*3c7ae9deSAndroid Build Coastguard Worker # Is it a zip file? 1109*3c7ae9deSAndroid Build Coastguard Worker if (self.s3_file_name_in_zip != None): 1110*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[S3Monitor] New file is zip file. Unzipping...") 1111*3c7ae9deSAndroid Build Coastguard Worker # Unzip it! 1112*3c7ae9deSAndroid Build Coastguard Worker with zipfile.ZipFile(new_file_path, 'r') as zip_file: 1113*3c7ae9deSAndroid Build Coastguard Worker zip_file.extractall("tmp/new_file_zip") 1114*3c7ae9deSAndroid Build Coastguard Worker new_file_path = "tmp/new_file_zip/" + self.s3_file_name_in_zip_only_path + self.s3_file_name_in_zip_only_extension 1115*3c7ae9deSAndroid Build Coastguard Worker 1116*3c7ae9deSAndroid Build Coastguard Worker try: 1117*3c7ae9deSAndroid Build Coastguard Worker # is there a file already present there? 1118*3c7ae9deSAndroid Build Coastguard Worker if os.path.exists(self.canary_local_application_path) == True: 1119*3c7ae9deSAndroid Build Coastguard Worker os.remove(self.canary_local_application_path) 1120*3c7ae9deSAndroid Build Coastguard Worker 1121*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[S3Monitor] Moving file...") 1122*3c7ae9deSAndroid Build Coastguard Worker os.replace(new_file_path, self.canary_local_application_path) 1123*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[S3Monitor] Getting execution rights...") 1124*3c7ae9deSAndroid Build Coastguard Worker os.system("chmod u+x " + self.canary_local_application_path) 1125*3c7ae9deSAndroid Build Coastguard Worker 1126*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 1127*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[S3Monitor] ERROR - could not move file into local application path due to exception!") 1128*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[S3Monitor] Exception: " + str(e)) 1129*3c7ae9deSAndroid Build Coastguard Worker self.had_internal_error = True 1130*3c7ae9deSAndroid Build Coastguard Worker self.internal_error_reason = "Could not move file into local application path" 1131*3c7ae9deSAndroid Build Coastguard Worker return 1132*3c7ae9deSAndroid Build Coastguard Worker 1133*3c7ae9deSAndroid Build Coastguard Worker self.print_message("[S3Monitor] New file downloaded and moved into correct location!") 1134*3c7ae9deSAndroid Build Coastguard Worker self.s3_file_needs_replacing = False 1135*3c7ae9deSAndroid Build Coastguard Worker 1136*3c7ae9deSAndroid Build Coastguard Worker 1137*3c7ae9deSAndroid Build Coastguard Worker def stop_monitoring(self): 1138*3c7ae9deSAndroid Build Coastguard Worker # Stub - just added for consistency 1139*3c7ae9deSAndroid Build Coastguard Worker pass 1140*3c7ae9deSAndroid Build Coastguard Worker 1141*3c7ae9deSAndroid Build Coastguard Worker 1142*3c7ae9deSAndroid Build Coastguard Worker def start_monitoring(self): 1143*3c7ae9deSAndroid Build Coastguard Worker # Stub - just added for consistency 1144*3c7ae9deSAndroid Build Coastguard Worker pass 1145*3c7ae9deSAndroid Build Coastguard Worker 1146*3c7ae9deSAndroid Build Coastguard Worker 1147*3c7ae9deSAndroid Build Coastguard Worker def restart_monitoring(self): 1148*3c7ae9deSAndroid Build Coastguard Worker # Stub - just added for consistency 1149*3c7ae9deSAndroid Build Coastguard Worker pass 1150*3c7ae9deSAndroid Build Coastguard Worker 1151*3c7ae9deSAndroid Build Coastguard Worker 1152*3c7ae9deSAndroid Build Coastguard Worker def cleanup_monitor(self): 1153*3c7ae9deSAndroid Build Coastguard Worker # Stub - just added for consistency 1154*3c7ae9deSAndroid Build Coastguard Worker pass 1155*3c7ae9deSAndroid Build Coastguard Worker 1156*3c7ae9deSAndroid Build Coastguard Worker def monitor_loop_function(self, time_passed=30): 1157*3c7ae9deSAndroid Build Coastguard Worker self.check_for_file_change() 1158*3c7ae9deSAndroid Build Coastguard Worker 1159*3c7ae9deSAndroid Build Coastguard Worker def print_message(self, message): 1160*3c7ae9deSAndroid Build Coastguard Worker if (self.data_snapshot != None): 1161*3c7ae9deSAndroid Build Coastguard Worker self.data_snapshot.print_message(message) 1162*3c7ae9deSAndroid Build Coastguard Worker else: 1163*3c7ae9deSAndroid Build Coastguard Worker print(message, flush=True) 1164*3c7ae9deSAndroid Build Coastguard Worker 1165*3c7ae9deSAndroid Build Coastguard Worker# ================================================================================ 1166*3c7ae9deSAndroid Build Coastguard Worker 1167*3c7ae9deSAndroid Build Coastguard Worker 1168*3c7ae9deSAndroid Build Coastguard Worker# Cuts a ticket to SIM using a temporary Cloudwatch metric that is quickly created, triggered, and destroyed. 1169*3c7ae9deSAndroid Build Coastguard Worker# Can be called in any thread - creates its own Cloudwatch client and any data it needs is passed in. 1170*3c7ae9deSAndroid Build Coastguard Worker# 1171*3c7ae9deSAndroid Build Coastguard Worker# See (https://w.amazon.com/bin/view/CloudWatchAlarms/Internal/CloudWatchAlarmsSIMTicketing) for more details 1172*3c7ae9deSAndroid Build Coastguard Worker# on how the alarm is sent using Cloudwatch. 1173*3c7ae9deSAndroid Build Coastguard Workerdef cut_ticket_using_cloudwatch( 1174*3c7ae9deSAndroid Build Coastguard Worker ticket_description="Description here!", 1175*3c7ae9deSAndroid Build Coastguard Worker ticket_reason="Reason here!", 1176*3c7ae9deSAndroid Build Coastguard Worker ticket_severity=5, 1177*3c7ae9deSAndroid Build Coastguard Worker ticket_category="AWS", 1178*3c7ae9deSAndroid Build Coastguard Worker ticket_type="SDKs and Tools", 1179*3c7ae9deSAndroid Build Coastguard Worker ticket_item="IoT SDK for CPP", 1180*3c7ae9deSAndroid Build Coastguard Worker ticket_group="AWS IoT Device SDK", 1181*3c7ae9deSAndroid Build Coastguard Worker ticket_allow_duplicates=False, 1182*3c7ae9deSAndroid Build Coastguard Worker git_repo_name="REPO NAME", 1183*3c7ae9deSAndroid Build Coastguard Worker git_hash="HASH", 1184*3c7ae9deSAndroid Build Coastguard Worker git_hash_as_namespace=False, 1185*3c7ae9deSAndroid Build Coastguard Worker git_fixed_namespace_text="mqtt5_canary", 1186*3c7ae9deSAndroid Build Coastguard Worker cloudwatch_region="us-east-1"): 1187*3c7ae9deSAndroid Build Coastguard Worker 1188*3c7ae9deSAndroid Build Coastguard Worker git_metric_namespace = "" 1189*3c7ae9deSAndroid Build Coastguard Worker if (git_hash_as_namespace == False): 1190*3c7ae9deSAndroid Build Coastguard Worker git_metric_namespace = git_fixed_namespace_text 1191*3c7ae9deSAndroid Build Coastguard Worker else: 1192*3c7ae9deSAndroid Build Coastguard Worker git_namespace_prepend_text = git_repo_name + "-" + git_hash 1193*3c7ae9deSAndroid Build Coastguard Worker git_metric_namespace = git_namespace_prepend_text 1194*3c7ae9deSAndroid Build Coastguard Worker 1195*3c7ae9deSAndroid Build Coastguard Worker try: 1196*3c7ae9deSAndroid Build Coastguard Worker cloudwatch_client = boto3.client('cloudwatch', cloudwatch_region) 1197*3c7ae9deSAndroid Build Coastguard Worker ticket_alarm_name = git_repo_name + "-" + git_hash + "-AUTO-TICKET" 1198*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 1199*3c7ae9deSAndroid Build Coastguard Worker print ("ERROR - could not create Cloudwatch client to make ticket metric alarm due to exception!") 1200*3c7ae9deSAndroid Build Coastguard Worker print ("Exception: " + str(e), flush=True) 1201*3c7ae9deSAndroid Build Coastguard Worker return 1202*3c7ae9deSAndroid Build Coastguard Worker 1203*3c7ae9deSAndroid Build Coastguard Worker new_metric_dimensions = [] 1204*3c7ae9deSAndroid Build Coastguard Worker if (git_hash_as_namespace == False): 1205*3c7ae9deSAndroid Build Coastguard Worker git_namespace_prepend_text = git_repo_name + "-" + git_hash 1206*3c7ae9deSAndroid Build Coastguard Worker new_metric_dimensions.append( 1207*3c7ae9deSAndroid Build Coastguard Worker {"Name": git_namespace_prepend_text, "Value": ticket_alarm_name}) 1208*3c7ae9deSAndroid Build Coastguard Worker else: 1209*3c7ae9deSAndroid Build Coastguard Worker new_metric_dimensions.append( 1210*3c7ae9deSAndroid Build Coastguard Worker {"Name": "System_Metrics", "Value": ticket_alarm_name}) 1211*3c7ae9deSAndroid Build Coastguard Worker 1212*3c7ae9deSAndroid Build Coastguard Worker ticket_arn = f"arn:aws:cloudwatch::cwa-internal:ticket:{ticket_severity}:{ticket_category}:{ticket_type}:{ticket_item}:{ticket_group}:" 1213*3c7ae9deSAndroid Build Coastguard Worker if (ticket_allow_duplicates == True): 1214*3c7ae9deSAndroid Build Coastguard Worker # use "DO-NOT-DEDUPE" so we can run the same commit again and it will cut another ticket. 1215*3c7ae9deSAndroid Build Coastguard Worker ticket_arn += "DO-NOT-DEDUPE" 1216*3c7ae9deSAndroid Build Coastguard Worker # In the ticket ARN, all spaces need to be replaced with + 1217*3c7ae9deSAndroid Build Coastguard Worker ticket_arn = ticket_arn.replace(" ", "+") 1218*3c7ae9deSAndroid Build Coastguard Worker 1219*3c7ae9deSAndroid Build Coastguard Worker ticket_alarm_description = f"AUTO CUT CANARY WRAPPER TICKET\n\nREASON: {ticket_reason}\n\nDESCRIPTION: {ticket_description}\n\n" 1220*3c7ae9deSAndroid Build Coastguard Worker 1221*3c7ae9deSAndroid Build Coastguard Worker # Register a metric alarm so it can auto-cut a ticket for us 1222*3c7ae9deSAndroid Build Coastguard Worker try: 1223*3c7ae9deSAndroid Build Coastguard Worker cloudwatch_client.put_metric_alarm( 1224*3c7ae9deSAndroid Build Coastguard Worker AlarmName=ticket_alarm_name, 1225*3c7ae9deSAndroid Build Coastguard Worker AlarmDescription=ticket_alarm_description, 1226*3c7ae9deSAndroid Build Coastguard Worker MetricName=ticket_alarm_name, 1227*3c7ae9deSAndroid Build Coastguard Worker Namespace=git_metric_namespace, 1228*3c7ae9deSAndroid Build Coastguard Worker Statistic="Maximum", 1229*3c7ae9deSAndroid Build Coastguard Worker Dimensions=new_metric_dimensions, 1230*3c7ae9deSAndroid Build Coastguard Worker Period=60, # How long (in seconds) is an evaluation period? 1231*3c7ae9deSAndroid Build Coastguard Worker EvaluationPeriods=1, # How many periods does it need to be invalid for? 1232*3c7ae9deSAndroid Build Coastguard Worker DatapointsToAlarm=1, # How many data points need to be invalid? 1233*3c7ae9deSAndroid Build Coastguard Worker Threshold=1, 1234*3c7ae9deSAndroid Build Coastguard Worker ComparisonOperator="GreaterThanOrEqualToThreshold", 1235*3c7ae9deSAndroid Build Coastguard Worker # The data above does not really matter - it just needs to be valid input data. 1236*3c7ae9deSAndroid Build Coastguard Worker # This is the part that tells Cloudwatch to cut the ticket 1237*3c7ae9deSAndroid Build Coastguard Worker AlarmActions=[ticket_arn] 1238*3c7ae9deSAndroid Build Coastguard Worker ) 1239*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 1240*3c7ae9deSAndroid Build Coastguard Worker print ("ERROR - could not create ticket metric alarm due to exception!") 1241*3c7ae9deSAndroid Build Coastguard Worker print ("Exception: " + str(e), flush=True) 1242*3c7ae9deSAndroid Build Coastguard Worker return 1243*3c7ae9deSAndroid Build Coastguard Worker 1244*3c7ae9deSAndroid Build Coastguard Worker # Trigger the alarm so it cuts the ticket 1245*3c7ae9deSAndroid Build Coastguard Worker try: 1246*3c7ae9deSAndroid Build Coastguard Worker cloudwatch_client.set_alarm_state( 1247*3c7ae9deSAndroid Build Coastguard Worker AlarmName=ticket_alarm_name, 1248*3c7ae9deSAndroid Build Coastguard Worker StateValue="ALARM", 1249*3c7ae9deSAndroid Build Coastguard Worker StateReason="AUTO TICKET CUT") 1250*3c7ae9deSAndroid Build Coastguard Worker except Exception as e: 1251*3c7ae9deSAndroid Build Coastguard Worker print ("ERROR - could not cut ticket due to exception!") 1252*3c7ae9deSAndroid Build Coastguard Worker print ("Exception: " + str(e), flush=True) 1253*3c7ae9deSAndroid Build Coastguard Worker return 1254*3c7ae9deSAndroid Build Coastguard Worker 1255*3c7ae9deSAndroid Build Coastguard Worker print("Waiting for ticket metric to trigger...", flush=True) 1256*3c7ae9deSAndroid Build Coastguard Worker # Wait a little bit (2 seconds)... 1257*3c7ae9deSAndroid Build Coastguard Worker time.sleep(2) 1258*3c7ae9deSAndroid Build Coastguard Worker 1259*3c7ae9deSAndroid Build Coastguard Worker # Remove the metric 1260*3c7ae9deSAndroid Build Coastguard Worker print("Removing ticket metric...", flush=True) 1261*3c7ae9deSAndroid Build Coastguard Worker cloudwatch_client.delete_alarms(AlarmNames=[ticket_alarm_name]) 1262*3c7ae9deSAndroid Build Coastguard Worker 1263*3c7ae9deSAndroid Build Coastguard Worker print ("Finished cutting ticket via Cloudwatch!", flush=True) 1264*3c7ae9deSAndroid Build Coastguard Worker return 1265*3c7ae9deSAndroid Build Coastguard Worker 1266*3c7ae9deSAndroid Build Coastguard Worker# A helper function that gets the majority of the ticket information from the arguments result from argparser. 1267*3c7ae9deSAndroid Build Coastguard Workerdef cut_ticket_using_cloudwatch_from_args( 1268*3c7ae9deSAndroid Build Coastguard Worker ticket_description="", 1269*3c7ae9deSAndroid Build Coastguard Worker ticket_reason="", 1270*3c7ae9deSAndroid Build Coastguard Worker ticket_severity=6, 1271*3c7ae9deSAndroid Build Coastguard Worker arguments=None): 1272*3c7ae9deSAndroid Build Coastguard Worker 1273*3c7ae9deSAndroid Build Coastguard Worker # Do not cut a ticket for a severity of 6+ 1274*3c7ae9deSAndroid Build Coastguard Worker if (ticket_severity >= 6): 1275*3c7ae9deSAndroid Build Coastguard Worker return 1276*3c7ae9deSAndroid Build Coastguard Worker 1277*3c7ae9deSAndroid Build Coastguard Worker cut_ticket_using_cloudwatch( 1278*3c7ae9deSAndroid Build Coastguard Worker ticket_description=ticket_description, 1279*3c7ae9deSAndroid Build Coastguard Worker ticket_reason=ticket_reason, 1280*3c7ae9deSAndroid Build Coastguard Worker ticket_severity=ticket_severity, 1281*3c7ae9deSAndroid Build Coastguard Worker ticket_category=arguments.ticket_category, 1282*3c7ae9deSAndroid Build Coastguard Worker ticket_type=arguments.ticket_type, 1283*3c7ae9deSAndroid Build Coastguard Worker ticket_item=arguments.ticket_item, 1284*3c7ae9deSAndroid Build Coastguard Worker ticket_group=arguments.ticket_group, 1285*3c7ae9deSAndroid Build Coastguard Worker ticket_allow_duplicates=False, 1286*3c7ae9deSAndroid Build Coastguard Worker git_repo_name=arguments.git_repo_name, 1287*3c7ae9deSAndroid Build Coastguard Worker git_hash=arguments.git_hash, 1288*3c7ae9deSAndroid Build Coastguard Worker git_hash_as_namespace=arguments.git_hash_as_namespace) 1289