summaryrefslogtreecommitdiff
path: root/gae/webapp/src/utils
diff options
context:
space:
mode:
Diffstat (limited to 'gae/webapp/src/utils')
-rw-r--r--gae/webapp/src/utils/__init__.py0
-rw-r--r--gae/webapp/src/utils/datetime_util.py39
-rw-r--r--gae/webapp/src/utils/email_util.py295
-rw-r--r--gae/webapp/src/utils/logger.py54
-rw-r--r--gae/webapp/src/utils/model_util.py56
-rw-r--r--gae/webapp/src/utils/model_util_test.py170
6 files changed, 0 insertions, 614 deletions
diff --git a/gae/webapp/src/utils/__init__.py b/gae/webapp/src/utils/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/gae/webapp/src/utils/__init__.py
+++ /dev/null
diff --git a/gae/webapp/src/utils/datetime_util.py b/gae/webapp/src/utils/datetime_util.py
deleted file mode 100644
index a1cff67..0000000
--- a/gae/webapp/src/utils/datetime_util.py
+++ /dev/null
@@ -1,39 +0,0 @@
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import logging
-import pytz
-
-
-def GetTimeWithTimezone(dt, timezone="US/Pacific"):
- """Converts timezone of datetime.datetime() instance.
-
- Args:
- dt: datetime.datetime() instance.
- timezone: a string representing timezone listed in TZ database.
-
- Returns:
- datetime.datetime() instance with the given timezone.
- """
- if not dt:
- return None
- utc_time = dt.replace(tzinfo=pytz.utc)
- try:
- converted_time = utc_time.astimezone(pytz.timezone(timezone))
- except pytz.UnknownTimeZoneError as e:
- logging.exception(e)
- converted_time = dt
- return converted_time
diff --git a/gae/webapp/src/utils/email_util.py b/gae/webapp/src/utils/email_util.py
deleted file mode 100644
index 2ef795e..0000000
--- a/gae/webapp/src/utils/email_util.py
+++ /dev/null
@@ -1,295 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import datetime
-import logging
-import re
-
-from google.appengine.api import app_identity
-from google.appengine.api import mail
-
-from webapp.src import vtslab_status as Status
-from webapp.src.proto import model
-from webapp.src.utils import datetime_util
-
-SENDER_ADDRESS = "noreply@{}.appspotmail.com"
-
-SEND_NOTIFICATION_FOOTER = (
- "You are receiving this email because you are "
- "listed as an owner, or an administrator of the "
- "lab {}.\nIf you received this email by mistake, "
- "please send an email to VTS Lab infra development "
- "team. Thank you.")
-
-SEND_DEVICE_NOTIFICATION_TITLE = ("[VTS lab] Devices not responding in lab {} "
- "({})")
-SEND_DEVICE_NOTIFICATION_HEADER = "Devices in lab {} are not responding."
-
-SEND_JOB_NOTIFICATION_TITLE = ("[VTS lab] Job error has been occurred in "
- "lab {} ({})")
-SEND_JOB_NOTIFICATION_HEADER = ("Jobs in lab {} have been completed "
- "unexpectedly.")
-SEND_SCHEDULE_SUSPENSION_NOTIFICATION_TITLE = (
- "[VTS lab] A job schedule has been {}. ({})")
-SEND_SCHEDULE_SUSPENSION_NOTIFICATION_HEADER = ("The below job schedule has "
- "been {}.")
-SEND_SCHEDULE_SUSPENSION_NOTIFICATION_FOOTER = (
- "You are receiving this email because one or more labs which you are "
- "listed as an owner or an administrator are affected.\nIf you received "
- "this email by mistake, please send an email to VTS Lab infra development "
- "team. Thank you.")
-
-
-def send_device_notification(devices):
- """Sends notification for not responding devices.
-
- Args:
- devices: a dict containing lab and host information of no-response
- devices.
- """
- for lab in devices:
- email_message = mail.EmailMessage()
- email_message.sender = SENDER_ADDRESS.format(
- app_identity.get_application_id())
- try:
- email_message.to = verify_recipient_address(
- devices[lab]["_recipients"])
- except ValueError as e:
- logging.error(e)
- continue
- email_message.subject = SEND_DEVICE_NOTIFICATION_TITLE.format(
- lab,
- datetime_util.GetTimeWithTimezone(
- datetime.datetime.now()).strftime("%Y-%m-%d"))
- message = ""
- message += SEND_DEVICE_NOTIFICATION_HEADER.format(lab)
- message += "\n\n"
- for host in devices[lab]:
- if host == "_recipients" or not devices[lab][host]:
- continue
- message += "hostname\n"
- message += host
- message += "\n\ndevices\n"
- message += "\n".join(devices[lab][host])
- message += "\n\n\n"
- message += "\n\n"
- message += SEND_NOTIFICATION_FOOTER.format(lab)
-
- try:
- email_message.body = message
- email_message.check_initialized()
- email_message.send()
- except mail.MissingRecipientError as e:
- logging.exception(e)
-
-
-def send_job_notification(jobs):
- """Sends notification for job error.
-
- Args:
- jobs: a JobModel entity, or a list of JobModel entities.
- """
- if not jobs:
- return
- if type(jobs) is not list:
- jobs = [jobs]
-
- # grouping jobs by lab to send to each lab owner and admins at once.
- labs_to_alert = {}
- for job in jobs:
- lab_query = model.LabModel.query(
- model.LabModel.hostname == job.hostname)
- labs = lab_query.fetch()
- if labs:
- lab = labs[0]
- if lab.name not in labs_to_alert:
- labs_to_alert[lab.name] = {}
- labs_to_alert[lab.name]["jobs"] = []
- labs_to_alert[lab.name]["_recipients"] = []
- if lab.owner not in labs_to_alert[lab.name]["_recipients"]:
- labs_to_alert[lab.name]["_recipients"].append(lab.owner)
- labs_to_alert[lab.name]["_recipients"].extend([
- x for x in lab.admin
- if x not in labs_to_alert[lab.name]["_recipients"]
- ])
- labs_to_alert[lab.name]["jobs"].append(job)
- else:
- logging.warning(
- "Could not find a lab model for hostname {}".format(
- job.hostname))
- continue
-
- for lab in labs_to_alert:
- email_message = mail.EmailMessage()
- email_message.sender = SENDER_ADDRESS.format(
- app_identity.get_application_id())
- try:
- email_message.to = verify_recipient_address(
- labs_to_alert[lab]["_recipients"])
- except ValueError as e:
- logging.error(e)
- continue
- email_message.subject = SEND_JOB_NOTIFICATION_TITLE.format(
- lab,
- datetime_util.GetTimeWithTimezone(
- datetime.datetime.now()).strftime("%Y-%m-%d"))
- message = ""
- message += SEND_JOB_NOTIFICATION_HEADER.format(lab)
- message += "\n\n"
- message += "http://{}.appspot.com/job".format(
- app_identity.get_application_id())
- message += "\n\n"
- for job in labs_to_alert[lab]["jobs"]:
- message += "hostname: {}\n\n".format(job.hostname)
- message += "device: {}\n".format(job.device.split("/")[1])
- message += "device serial: {}\n".format(", ".join(job.serial))
- message += (
- "device: branch - {}, target - {}, build_id - {}\n").format(
- job.manifest_branch, job.build_target, job.build_id)
- message += "gsi: branch - {}, target - {}, build_id - {}\n".format(
- job.gsi_branch, job.gsi_build_target, job.gsi_build_id)
- message += "test: branch - {}, target - {}, build_id - {}\n".format(
- job.test_branch, job.test_build_target, job.test_build_id)
- message += "job created: {}\n".format(
- datetime_util.GetTimeWithTimezone(
- job.timestamp).strftime("%Y-%m-%d %H:%M:%S %Z"))
- message += "job status: {}\n".format([
- key for key, value in Status.JOB_STATUS_DICT.items()
- if value == job.status
- ][0])
- message += "\n\n\n"
- message += "\n\n"
- message += SEND_NOTIFICATION_FOOTER.format(lab)
-
- try:
- email_message.body = message
- email_message.check_initialized()
- email_message.send()
- except mail.MissingRecipientError as e:
- logging.exception(e)
-
-
-def send_schedule_suspension_notification(schedule):
- """Sends notification when a schedule is suspended, or resumed.
-
- Args:
- schedule: a ScheduleModel entity.
- """
- if not schedule:
- return
-
- if not schedule.device:
- return
-
- email_message = mail.EmailMessage()
- email_message.sender = SENDER_ADDRESS.format(
- app_identity.get_application_id())
-
- lab_names = []
- for device in schedule.device:
- if not "/" in device:
- continue
- lab_name = device.split("/")[0]
- lab_names.append(lab_name)
-
- recipients = []
- for lab_name in lab_names:
- lab_query = model.LabModel.query(model.LabModel.name == lab_name)
- labs = lab_query.fetch()
- if labs:
- lab = labs[0]
- if lab.owner not in recipients:
- recipients.append(lab.owner)
- recipients.extend([x for x in lab.admin if x not in recipients])
- else:
- logging.warning(
- "Could not find a lab model for lab {}".format(lab_name))
-
- try:
- email_message.to = verify_recipient_address(recipients)
- except ValueError as e:
- logging.error(e)
- return
-
- status_text = "suspended" if schedule.suspended else "resumed"
- email_message.subject = SEND_SCHEDULE_SUSPENSION_NOTIFICATION_TITLE.format(
- status_text,
- datetime_util.GetTimeWithTimezone(
- datetime.datetime.now()).strftime("%Y-%m-%d"))
- message = ""
- message += SEND_SCHEDULE_SUSPENSION_NOTIFICATION_HEADER.format(status_text)
- message += "\n\n"
- message += "\n\ndevices\n"
- message += "\n".join(schedule.device)
- message += "\n\ndevice branch\n"
- message += schedule.manifest_branch
- message += "\n\ndevice build target\n"
- message += schedule.build_target
- message += "\n\ngsi branch\n"
- message += schedule.gsi_branch
- message += "\n\ngsi build target\n"
- message += schedule.gsi_build_target
- message += "\n\ntest branch\n"
- message += schedule.test_branch
- message += "\n\ntest build target\n"
- message += schedule.test_build_target
- message += "\n\n"
- message += ("Please see the details in the following link: "
- "http://{}.appspot.com/schedule".format(
- app_identity.get_application_id()))
- message += "\n\n\n\n"
- message += SEND_SCHEDULE_SUSPENSION_NOTIFICATION_FOOTER
-
- try:
- email_message.body = message
- email_message.check_initialized()
- email_message.send()
- except mail.MissingRecipientError as e:
- logging.exception(e)
-
-
-def verify_recipient_address(address):
- """Verifies recipients address.
-
- Args:
- address: a list of strings or a string, recipient(s) address.
-
- Returns:
- A list of verified addresses if list type argument is given, or
- a string of a verified address if str type argument is given.
-
- Raises:
- ValueError if type of address is neither list nor str.
- """
- # pattern for 'any@google.com', and 'any name <any@google.com>'
- verify_patterns = [
- re.compile(".*@google\.com$"),
- re.compile(".*<.*@google\.com>$")
- ]
- if not address:
- return None
- if type(address) is list:
- verified_address = [
- x for x in address
- if any(pattern.match(x) for pattern in verify_patterns)
- ]
- return verified_address
- elif type(address) is str:
- return address if any(
- pattern.match(address) for pattern in verify_patterns) else None
- else:
- raise ValueError("Wrong type - {}.".format(type(address)))
diff --git a/gae/webapp/src/utils/logger.py b/gae/webapp/src/utils/logger.py
deleted file mode 100644
index 20c03d2..0000000
--- a/gae/webapp/src/utils/logger.py
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-
-class Logger(object):
- """A class to log messages to a list of strings.
-
- Attributes:
- log_message: a list of strings, containing the log messages.
- log_indent: integer, representing the index level
- """
-
- def __init__(self):
- self.log_message = []
- self.log_indent = 0
-
- def Clear(self):
- """Clears the log buffer."""
- self.log_message = []
- self.log_indent = 0
-
- def Get(self):
- """Retruns a list of all log message strings."""
- return self.log_message
-
- def Println(self, msg):
- """Stores a new string `msg` to the log buffer."""
- indent = " " * self.log_indent
- if msg and type(msg) is not str:
- msg = str(msg)
- self.log_message.append(indent + msg)
-
- def Indent(self):
- """Increase indent of log message."""
- self.log_indent += 1
-
- def Unindent(self):
- """Decrease indent of log message."""
- if self.log_indent > 0:
- self.log_indent -= 1
diff --git a/gae/webapp/src/utils/model_util.py b/gae/webapp/src/utils/model_util.py
deleted file mode 100644
index aa07a63..0000000
--- a/gae/webapp/src/utils/model_util.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-from webapp.src import vtslab_status as Status
-from webapp.src.utils import email_util
-
-
-def UpdateParentSchedule(job, status):
- """Updates a parent schedule of the given job with status.
-
- Args:
- job: a JobModel entity.
- status: an integer, job status value.
- """
- if status not in [
- Status.JOB_STATUS_DICT["complete"],
- Status.JOB_STATUS_DICT["infra-err"],
- Status.JOB_STATUS_DICT["expired"],
- Status.JOB_STATUS_DICT["bootup-err"]
- ]:
- return
-
- if job.parent_schedule:
- schedule = job.parent_schedule.get()
- if schedule:
- previous_suspended = schedule.suspended
- if schedule.error_count is None:
- schedule.error_count = 0
- if status == Status.JOB_STATUS_DICT["complete"]:
- schedule.error_count = 0
- schedule.suspended = False
- elif status in [
- Status.JOB_STATUS_DICT["infra-err"],
- Status.JOB_STATUS_DICT["expired"],
- Status.JOB_STATUS_DICT["bootup-err"]
- ]:
- schedule.error_count += 1
- if schedule.error_count >= Status.NUM_ERRORS_FOR_SUSPENSION:
- schedule.suspended = True
- schedule.put()
- if previous_suspended != schedule.suspended:
- email_util.send_schedule_suspension_notification(schedule)
diff --git a/gae/webapp/src/utils/model_util_test.py b/gae/webapp/src/utils/model_util_test.py
deleted file mode 100644
index 4be54b1..0000000
--- a/gae/webapp/src/utils/model_util_test.py
+++ /dev/null
@@ -1,170 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import datetime
-import unittest
-
-try:
- from unittest import mock
-except ImportError:
- import mock
-
-from webapp.src import vtslab_status as Status
-from webapp.src.proto import model
-from webapp.src.scheduler import schedule_worker
-from webapp.src.testing import unittest_base
-from webapp.src.utils import model_util
-
-
-class ModelTest(unittest_base.UnitTestBase):
- """Tests for PeriodicJobHeartBeat cron class."""
-
- def testJobAndScheduleModel(self):
- """Asserts JobModel and ScheduleModel.
-
- When JobModel's status is changed, ScheduleModel's error_count is
- changed based on the status. This should not be applied before JobModel
- entity is updated to Datastore.
- """
- period = 360
-
- lab = self.GenerateLabModel()
- lab.put()
-
- device = self.GenerateDeviceModel(hostname=lab.hostname)
- device.put()
-
- schedule = self.GenerateScheduleModel(
- device_model=device, lab_model=lab, period=period)
- schedule.put()
-
- build_dict = self.GenerateBuildModel(schedule)
- for key in build_dict:
- build_dict[key].put()
-
- # Mocking ScheduleHandler and essential methods.
- scheduler = schedule_worker.ScheduleHandler(mock.Mock())
- scheduler.response = mock.Mock()
- scheduler.response.write = mock.Mock()
- scheduler.request.get = mock.MagicMock(return_value="")
-
- print("\nCreating a job...")
- scheduler.post()
- jobs = model.JobModel.query().fetch()
- self.assertEqual(1, len(jobs))
-
- print("Occurring infra error...")
- job = jobs[0]
- job.status = Status.JOB_STATUS_DICT["infra-err"]
- parent_schedule = job.parent_schedule.get()
- parent_from_db = model.ScheduleModel.query().fetch()[0]
-
- # in test error_count could be None but in real there will be no None.
- self.assertNotEqual(1, parent_schedule.error_count)
- self.assertNotEqual(1, parent_from_db.error_count)
-
- # error count should be changed after put
- job.put()
- model_util.UpdateParentSchedule(job, job.status)
- self.assertEqual(1, parent_schedule.error_count)
- self.assertEqual(1, parent_from_db.error_count)
-
- print("Suspending a job...")
- for num in xrange(2):
- jobs = model.JobModel.query().fetch()
- for job in jobs:
- job.timestamp = datetime.datetime.now() - datetime.timedelta(
- minutes=(period + 10))
- job.put()
-
- parent_from_db = model.ScheduleModel.query().fetch()[0]
- self.assertEqual(1 + num, parent_schedule.error_count)
- self.assertEqual(1 + num, parent_from_db.error_count)
-
- # reset a device manually to re-schedule
- device = model.DeviceModel.query().fetch()[0]
- device.status = Status.DEVICE_STATUS_DICT["fastboot"]
- device.scheduling_status = (
- Status.DEVICE_SCHEDULING_STATUS_DICT["free"])
- device.timestamp = datetime.datetime.now()
- device.put()
-
- scheduler.post()
- jobs = model.JobModel.query().fetch()
- self.assertEqual(2 + num, len(jobs))
-
- ready_jobs = model.JobModel.query(
- model.JobModel.status == Status.JOB_STATUS_DICT[
- "ready"]).fetch()
- self.assertEqual(1, len(ready_jobs))
-
- ready_job = ready_jobs[0]
- ready_job.status = Status.JOB_STATUS_DICT["infra-err"]
- parent_schedule = ready_job.parent_schedule.get()
- parent_from_db = model.ScheduleModel.query().fetch()[0]
- self.assertEqual(1 + num, parent_schedule.error_count)
- self.assertEqual(1 + num, parent_from_db.error_count)
-
- # # error count should be changed after put
- ready_job.put()
- model_util.UpdateParentSchedule(ready_job, ready_job.status)
- self.assertEqual(2 + num, parent_schedule.error_count)
- self.assertEqual(2 + num, parent_from_db.error_count)
-
- print("Asserting a schedule's suspend status...")
- # after three errors the schedule should be suspended.
- schedule_from_db = model.ScheduleModel.query().fetch()[0]
- schedule_from_db.put()
- self.assertEqual(3, schedule_from_db.error_count)
- self.assertEqual(True, schedule_from_db.suspended)
-
- # reset a device manually to re-schedule
- device = model.DeviceModel.query().fetch()[0]
- device.status = Status.DEVICE_STATUS_DICT["fastboot"]
- device.scheduling_status = (
- Status.DEVICE_SCHEDULING_STATUS_DICT["free"])
- device.timestamp = datetime.datetime.now()
- device.put()
-
- print("Asserting that job creation is blocked...")
- jobs = model.JobModel.query().fetch()
- self.assertEqual(3, len(jobs))
-
- for job in jobs:
- job.timestamp = datetime.datetime.now() - datetime.timedelta(
- minutes=(period + 10))
- job.put()
-
- scheduler.post()
-
- # a job should not be created.
- jobs = model.JobModel.query().fetch()
- self.assertEqual(3, len(jobs))
-
- print("Asserting that job creation is allowed after resuming...")
- schedule_from_db = model.ScheduleModel.query().fetch()[0]
- schedule_from_db.suspended = False
- schedule_from_db.put()
-
- scheduler.post()
-
- jobs = model.JobModel.query().fetch()
- self.assertEqual(4, len(jobs))
-
-
-if __name__ == "__main__":
- unittest.main()