Skip to content

Commit 0ba5099

Browse files
authored
feat: isolate py27 code interfacing with mailman from py39 code interfacing with django. (ietf-tools#4140)
* feat: isolate py27 code interfacing with mailman from py39 code interfacing with django. * fix: improve memory footprint and remove unneeded import. * fix: make new bin command executable.
1 parent ba7d468 commit 0ba5099

3 files changed

Lines changed: 74 additions & 34 deletions

File tree

bin/mm_hourly

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/bin/bash
22

3-
# Hourly datatracker jobs, run as mailman
3+
# Hourly datatracker jobs, ***run as mailman***
44
#
55
# This script is expected to be triggered by cron from
66
# $DTDIR/etc/cron.d/datatracker which should be symlinked from
@@ -13,14 +13,12 @@ export PYTHONIOENCODING=utf-8
1313
program=${0##*/}
1414
trap 'echo "$program($LINENO): Command failed with error code $? ([$$] $0 $*)"; exit 1' ERR
1515

16-
# Note that we're using the last 2.7 release here, not the current release
17-
DTDIR=/a/www/ietf-datatracker/py27
16+
DTDIR=/a/www/ietf-datatracker/web
1817
cd $DTDIR/
1918

2019
# Set up the virtual environment
2120
source $DTDIR/env/bin/activate
2221

2322
logger -p user.info -t cron "Running $DTDIR/bin/mm_hourly"
2423

25-
## XXX commented out pending rewrite -- mailman 2 python interface is not available under Python 3
2624
$DTDIR/ietf/manage.py import_mailman_listinfo

ietf/bin/mailman_listinfo.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/python2.7
2+
# Copyright The IETF Trust 2022, All Rights Reserved
3+
# Note the shebang. This specifically targets deployment on IETFA and intends to use its system python2.7.
4+
5+
# This is an adaptor to pull information out of Mailman2 using its python libraries (which are only available for python2).
6+
# It is NOT django code, and does not have access to django.conf.settings.
7+
8+
import json
9+
import sys
10+
11+
from collections import defaultdict
12+
13+
def main():
14+
15+
sys.path.append('/usr/lib/mailman')
16+
17+
have_mailman = False
18+
try:
19+
from Mailman import Utils
20+
from Mailman import MailList
21+
from Mailman import MemberAdaptor
22+
have_mailman = True
23+
except ImportError:
24+
pass
25+
26+
27+
if not have_mailman:
28+
sys.stderr.write("Could not import mailman modules -- skipping import of mailman list info")
29+
sys.exit()
30+
31+
names = list(Utils.list_names())
32+
33+
# need to emit dict of names, each name has an mlist, and each mlist has description, advertised, and members (calculated as below)
34+
result = defaultdict(dict)
35+
for name in names:
36+
mlist = MailList.MailList(name, lock=False)
37+
result[name] = dict()
38+
result[name]['internal_name'] = mlist.internal_name()
39+
result[name]['real_name'] = mlist.real_name
40+
result[name]['description'] = mlist.description # Not attempting to change encoding
41+
result[name]['advertised'] = mlist.advertised
42+
result[name]['members'] = list()
43+
if mlist.advertised:
44+
members = mlist.getRegularMemberKeys() + mlist.getDigestMemberKeys()
45+
members = set([ m for m in members if mlist.getDeliveryStatus(m) == MemberAdaptor.ENABLED ])
46+
result[name]['members'] = list(members)
47+
json.dump(result, sys.stdout)
48+
49+
if __name__ == "__main__":
50+
main()

ietf/mailinglists/management/commands/import_mailman_listinfo.py

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,22 @@
11
# Copyright The IETF Trust 2016-2019, All Rights Reserved
22

3+
import json
34
import sys
5+
import subprocess
46
import time
57
from textwrap import dedent
68

79
import debug # pyflakes:ignore
810

11+
from pathlib import Path
12+
913
from django.conf import settings
1014
from django.core.management.base import BaseCommand
1115
from django.core.exceptions import MultipleObjectsReturned
1216

13-
sys.path.append(settings.MAILMAN_LIB_DIR)
14-
15-
have_mailman = False
16-
try:
17-
from Mailman import Utils
18-
from Mailman import MailList
19-
from Mailman import MemberAdaptor
20-
have_mailman = True
21-
except ImportError:
22-
pass
2317

2418
from ietf.mailinglists.models import List, Subscribed
2519
from ietf.utils.log import log
26-
from ietf.utils.text import decode
2720

2821
mark = time.time()
2922

@@ -39,51 +32,50 @@ def log_time(msg):
3932
log(msg+' (%.1fs)'% (t-mark))
4033
mark = t
4134

42-
if not have_mailman:
43-
note("Could not import mailman modules -- skipping import of mailman list info")
35+
cmd = str(Path(settings.BASE_DIR) / "bin" / "mailman_listinfo.py")
36+
result = subprocess.run([cmd], capture_output=True)
37+
if result.stderr:
38+
log("Error exporting information from mailmain")
39+
log(result.stderr)
4440
return
41+
mailman_export = json.loads(result.stdout)
4542

4643
log("Starting import of list info from Mailman")
47-
names = list(Utils.list_names())
48-
names.sort()
44+
names = sorted(mailman_export.keys())
4945
log_time("Fetched list of mailman list names")
5046
addr_max_length = Subscribed._meta.get_field('email').max_length
5147

5248
subscribed = { l.name: set(l.subscribed_set.values_list('email', flat=True)) for l in List.objects.all().prefetch_related('subscribed_set') }
5349
log_time("Computed dictionary of list members")
5450

5551
for name in names:
56-
mlist = MailList.MailList(name, lock=False)
57-
note("List: %s" % mlist.internal_name())
52+
note("List: %s" % mailman_export[name]['internal_name'])
5853
log_time("Fetched Mailman list object for %s" % name)
5954

60-
lists = List.objects.filter(name=mlist.real_name)
55+
lists = List.objects.filter(name=mailman_export[name]['real_name'])
6156
if lists.count() > 1:
6257
# Arbitrary choice; we'll update the remaining item next
6358
for item in lists[1:]:
6459
item.delete()
65-
mmlist, created = List.objects.get_or_create(name=mlist.real_name)
60+
mmlist, created = List.objects.get_or_create(name=mailman_export[name]['real_name'])
6661
dirty = False
67-
desc = decode(mlist.description)[:256]
62+
desc = mailman_export[name]['description'][:256]
6863
if mmlist.description != desc:
6964
mmlist.description = desc
7065
dirty = True
71-
if mmlist.advertised != mlist.advertised:
72-
mmlist.advertised = mlist.advertised
66+
if mmlist.advertised != mailman_export[name]['advertised']:
67+
mmlist.advertised = mailman_export[name]['advertised']
7368
dirty = True
7469
if dirty:
7570
mmlist.save()
7671
log_time(" Updated database List object for %s" % name)
7772
# The following calls return lowercased addresses
78-
if mlist.advertised:
79-
members = mlist.getRegularMemberKeys() + mlist.getDigestMemberKeys()
80-
log_time(" Fetched list of list members")
81-
members = set([ m for m in members if mlist.getDeliveryStatus(m) == MemberAdaptor.ENABLED ])
82-
log_time(" Filtered list of list members")
83-
if not mlist.real_name in subscribed:
84-
log("Note: didn't find '%s' in the dictionary of subscriptions" % mlist.real_name)
73+
if mailman_export[name]['advertised']:
74+
members = set(mailman_export[name]['members'])
75+
if not mailman_export[name]['real_name'] in subscribed:
76+
log("Note: didn't find '%s' in the dictionary of subscriptions" % mailman_export[name]['real_name'])
8577
continue
86-
known = subscribed[mlist.real_name]
78+
known = subscribed[mailman_export[name]['real_name']]
8779
log_time(" Fetched known list members from database")
8880
to_remove = known - members
8981
to_add = members - known

0 commit comments

Comments
 (0)