xref: /aosp_15_r20/external/aws-crt-java/codebuild/CanaryWrapper_Classes.py (revision 3c7ae9de214676c52d19f01067dc1a404272dc11)
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