mirror of
https://git.launchpad.net/~ubuntu-release/britney/+git/britney2-ubuntu
synced 2025-05-08 09:01:36 +00:00
Resend emails periodically.
This commit is contained in:
parent
322108df73
commit
63573fa24a
@ -11,6 +11,9 @@ from britney2.policies.rest import Rest
|
|||||||
from britney2.policies.policy import BasePolicy, PolicyVerdict
|
from britney2.policies.policy import BasePolicy, PolicyVerdict
|
||||||
|
|
||||||
|
|
||||||
|
# Recurring emails should never be more than this many days apart
|
||||||
|
MAX_FREQUENCY = 30
|
||||||
|
|
||||||
API_PREFIX = 'https://api.launchpad.net/1.0/'
|
API_PREFIX = 'https://api.launchpad.net/1.0/'
|
||||||
USER = API_PREFIX + '~'
|
USER = API_PREFIX + '~'
|
||||||
|
|
||||||
@ -25,13 +28,13 @@ BOTS = {
|
|||||||
MESSAGE = """From: Ubuntu Release Team <noreply@canonical.com>
|
MESSAGE = """From: Ubuntu Release Team <noreply@canonical.com>
|
||||||
To: {recipients}
|
To: {recipients}
|
||||||
X-Proposed-Migration: notice
|
X-Proposed-Migration: notice
|
||||||
Subject: [proposed-migration] {source_name} {version} stuck in {series}-proposed
|
Subject: [proposed-migration] {source_name} {version} stuck in {series}-proposed for {rounded_age} days.
|
||||||
|
|
||||||
Hi,
|
Hi,
|
||||||
|
|
||||||
{source_name} {version} needs attention.
|
{source_name} {version} needs attention.
|
||||||
|
|
||||||
It has been stuck in {series}-proposed for {age} day{plural}.
|
It has been stuck in {series}-proposed for {rounded_age} days.
|
||||||
|
|
||||||
You either sponsored or uploaded this package, please investigate why it hasn't been approved for migration.
|
You either sponsored or uploaded this package, please investigate why it hasn't been approved for migration.
|
||||||
|
|
||||||
@ -82,6 +85,8 @@ class EmailPolicy(BasePolicy, Rest):
|
|||||||
def __init__(self, options, suite_info, dry_run=False):
|
def __init__(self, options, suite_info, dry_run=False):
|
||||||
super().__init__('email', options, suite_info, {'unstable'})
|
super().__init__('email', options, suite_info, {'unstable'})
|
||||||
self.filename = os.path.join(options.unstable, 'EmailCache')
|
self.filename = os.path.join(options.unstable, 'EmailCache')
|
||||||
|
# Maps lp username -> email address
|
||||||
|
self.addresses = {}
|
||||||
# Dict of dicts; maps pkg name -> pkg version -> boolean
|
# Dict of dicts; maps pkg name -> pkg version -> boolean
|
||||||
self.emails_by_pkg = defaultdict(dict)
|
self.emails_by_pkg = defaultdict(dict)
|
||||||
# self.cache contains self.emails_by_pkg from previous run
|
# self.cache contains self.emails_by_pkg from previous run
|
||||||
@ -99,6 +104,8 @@ class EmailPolicy(BasePolicy, Rest):
|
|||||||
|
|
||||||
def _scrape_gpg_emails(self, person):
|
def _scrape_gpg_emails(self, person):
|
||||||
"""Find email addresses from one person's GPG keys."""
|
"""Find email addresses from one person's GPG keys."""
|
||||||
|
if person in self.addresses:
|
||||||
|
return self.addresses[person]
|
||||||
addresses = []
|
addresses = []
|
||||||
gpg = self.query_lp_rest_api(person + '/gpg_keys', {})
|
gpg = self.query_lp_rest_api(person + '/gpg_keys', {})
|
||||||
for key in gpg['entries']:
|
for key in gpg['entries']:
|
||||||
@ -121,15 +128,12 @@ class EmailPolicy(BasePolicy, Rest):
|
|||||||
match = re.match(r'^.*<(.+@.+)>$', uid)
|
match = re.match(r'^.*<(.+@.+)>$', uid)
|
||||||
if match:
|
if match:
|
||||||
addresses.append(match.group(1))
|
addresses.append(match.group(1))
|
||||||
return addresses
|
address = self.addresses[person] = address_chooser(addresses)
|
||||||
|
return address
|
||||||
|
|
||||||
def scrape_gpg_emails(self, people):
|
def scrape_gpg_emails(self, people):
|
||||||
"""Find email addresses from GPG keys."""
|
"""Find email addresses from GPG keys."""
|
||||||
addresses = []
|
return [self._scrape_gpg_emails(person) for person in (people or [])]
|
||||||
for person in people or []:
|
|
||||||
address = address_chooser(self._scrape_gpg_emails(person))
|
|
||||||
addresses.append(address)
|
|
||||||
return addresses
|
|
||||||
|
|
||||||
def lp_get_emails(self, pkg, version):
|
def lp_get_emails(self, pkg, version):
|
||||||
"""Ask LP who uploaded this package."""
|
"""Ask LP who uploaded this package."""
|
||||||
@ -153,15 +157,24 @@ class EmailPolicy(BasePolicy, Rest):
|
|||||||
|
|
||||||
def apply_policy_impl(self, email_info, suite, source_name, source_data_tdist, source_data_srcdist, excuse):
|
def apply_policy_impl(self, email_info, suite, source_name, source_data_tdist, source_data_srcdist, excuse):
|
||||||
"""Send email if package is rejected."""
|
"""Send email if package is rejected."""
|
||||||
# Have more patience for things blocked in update_output.txt
|
|
||||||
max_age = 5 if excuse.is_valid else 1
|
|
||||||
series = self.options.series
|
series = self.options.series
|
||||||
version = source_data_srcdist.version
|
version = source_data_srcdist.version
|
||||||
sent = self.cache.get(source_name, {}).get(version, False)
|
|
||||||
age = excuse.daysold or 0
|
age = excuse.daysold or 0
|
||||||
stuck = age >= max_age
|
rounded_age = int(age)
|
||||||
age = int(age)
|
stuck = age >= 3
|
||||||
plural = 's' if age != 1 else ''
|
cached = self.cache.get(source_name, {}).get(version)
|
||||||
|
try:
|
||||||
|
emails, sent_age = cached
|
||||||
|
sent = (age - sent_age) < min(MAX_FREQUENCY, age / 2.0)
|
||||||
|
except TypeError:
|
||||||
|
# This exception happens when source_name, version never seen before
|
||||||
|
emails = []
|
||||||
|
sent_age = 0
|
||||||
|
sent = False
|
||||||
|
# TODO: This transitional code can only trigger on the first
|
||||||
|
# production run, feel free to drop this shortly after merging.
|
||||||
|
if cached is True:
|
||||||
|
sent = True
|
||||||
if self.dry_run:
|
if self.dry_run:
|
||||||
self.log("[email dry run] Considering: %s/%s: %s" %
|
self.log("[email dry run] Considering: %s/%s: %s" %
|
||||||
(source_name, version, "stuck" if stuck else "not stuck"))
|
(source_name, version, "stuck" if stuck else "not stuck"))
|
||||||
@ -171,6 +184,7 @@ class EmailPolicy(BasePolicy, Rest):
|
|||||||
# don't update the cache file in dry run mode; we'll see all output each time
|
# don't update the cache file in dry run mode; we'll see all output each time
|
||||||
return PolicyVerdict.PASS
|
return PolicyVerdict.PASS
|
||||||
if stuck and not sent:
|
if stuck and not sent:
|
||||||
|
if not emails:
|
||||||
emails = self.lp_get_emails(source_name, version)
|
emails = self.lp_get_emails(source_name, version)
|
||||||
if emails:
|
if emails:
|
||||||
recipients = ', '.join(emails)
|
recipients = ', '.join(emails)
|
||||||
@ -181,11 +195,11 @@ class EmailPolicy(BasePolicy, Rest):
|
|||||||
server = smtplib.SMTP('localhost')
|
server = smtplib.SMTP('localhost')
|
||||||
server.sendmail('noreply@canonical.com', emails, msg)
|
server.sendmail('noreply@canonical.com', emails, msg)
|
||||||
server.quit()
|
server.quit()
|
||||||
sent = True
|
sent_age = age
|
||||||
except socket.error as err:
|
except socket.error as err:
|
||||||
self.log("Failed to send mail! Is SMTP server running?")
|
self.log("Failed to send mail! Is SMTP server running?")
|
||||||
self.log(err)
|
self.log(err)
|
||||||
self.emails_by_pkg[source_name][version] = sent
|
self.emails_by_pkg[source_name][version] = (emails, sent_age)
|
||||||
self.save_state()
|
self.save_state()
|
||||||
return PolicyVerdict.PASS
|
return PolicyVerdict.PASS
|
||||||
|
|
||||||
|
@ -193,11 +193,9 @@ class T(unittest.TestCase):
|
|||||||
"""Know when not to send any emails."""
|
"""Know when not to send any emails."""
|
||||||
lp.return_value = ['example@email.com']
|
lp.return_value = ['example@email.com']
|
||||||
e = EmailPolicy(FakeOptions, None)
|
e = EmailPolicy(FakeOptions, None)
|
||||||
FakeExcuse.is_valid = False
|
|
||||||
FakeExcuse.daysold = 0.002
|
FakeExcuse.daysold = 0.002
|
||||||
e.apply_policy_impl(None, None, 'chromium-browser', None, FakeSourceData, FakeExcuse)
|
e.apply_policy_impl(None, None, 'chromium-browser', None, FakeSourceData, FakeExcuse)
|
||||||
FakeExcuse.is_valid = True
|
FakeExcuse.daysold = 2.98
|
||||||
FakeExcuse.daysold = 4.28
|
|
||||||
e.apply_policy_impl(None, None, 'chromium-browser', None, FakeSourceData, FakeExcuse)
|
e.apply_policy_impl(None, None, 'chromium-browser', None, FakeSourceData, FakeExcuse)
|
||||||
# Would email but no address found
|
# Would email but no address found
|
||||||
FakeExcuse.daysold = 10.12
|
FakeExcuse.daysold = 10.12
|
||||||
@ -218,33 +216,25 @@ class T(unittest.TestCase):
|
|||||||
|
|
||||||
@patch('britney2.policies.email.EmailPolicy.lp_get_emails')
|
@patch('britney2.policies.email.EmailPolicy.lp_get_emails')
|
||||||
@patch('britney2.policies.email.smtplib', autospec=True)
|
@patch('britney2.policies.email.smtplib', autospec=True)
|
||||||
def test_smtp_days(self, smtp, lp):
|
def test_smtp_repetition(self, smtp, lp):
|
||||||
"""Pluralize correctly."""
|
"""Resend mails periodically, with decreasing frequency."""
|
||||||
sendmail = smtp.SMTP().sendmail
|
|
||||||
lp.return_value = ['email@address.com']
|
lp.return_value = ['email@address.com']
|
||||||
|
sendmail = smtp.SMTP().sendmail
|
||||||
e = EmailPolicy(FakeOptions, None)
|
e = EmailPolicy(FakeOptions, None)
|
||||||
FakeExcuse.is_valid = False
|
called = []
|
||||||
# day
|
e.cache = {}
|
||||||
FakeExcuse.daysold = 1.01
|
for hours in range(0, 5000):
|
||||||
e.apply_policy_impl(None, None, 'chromium-browser', None, FakeSourceData, FakeExcuse)
|
previous = sendmail.call_count
|
||||||
_, args, _ = sendmail.mock_calls[-1]
|
age = hours / 24
|
||||||
text = args[2]
|
FakeExcuse.daysold = age
|
||||||
self.assertEquals(sendmail.call_count, 1)
|
e.apply_policy_impl(None, None, 'unity8', None, FakeSourceData, FakeExcuse)
|
||||||
self.assertIn(' 1 day.', text)
|
if sendmail.call_count > previous:
|
||||||
# days
|
e.initialise(None) # Refill e.cache from disk
|
||||||
FakeExcuse.daysold = 4.9
|
called.append(age)
|
||||||
e.apply_policy_impl(None, None, 'chromium-browser', None, FakeSourceData, FakeExcuse)
|
# Emails were sent when daysold reached these values:
|
||||||
_, args, _ = sendmail.mock_calls[-1]
|
self.assertSequenceEqual(called, [
|
||||||
text = args[2]
|
3.0, 6.0, 12.0, 24.0, 48.0, 78.0, 108.0, 138.0, 168.0, 198.0
|
||||||
self.assertEquals(sendmail.call_count, 2)
|
])
|
||||||
self.assertIn(' 4 days.', text)
|
|
||||||
# day, exactly 1
|
|
||||||
FakeExcuse.daysold = 1
|
|
||||||
e.apply_policy_impl(None, None, 'chromium-browser', None, FakeSourceData, FakeExcuse)
|
|
||||||
_, args, _ = sendmail.mock_calls[-1]
|
|
||||||
text = args[2]
|
|
||||||
self.assertEquals(sendmail.call_count, 3)
|
|
||||||
self.assertIn(' 1 day.', text)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
Loading…
x
Reference in New Issue
Block a user