Skip to content

Commit 0bf7922

Browse files
chadellslyngshede
andauthored
Apple Parser (#303)
* Parser for Apple peering notifications * Parser for Apple peering notifications * Ensure that Blake parses * Apple: Flake8 and DocStrings * Apple: Fix blake error. * Apple: PyDocStyle fixes. * remove pylint deprecated checks * remove subject test as it gets the subjects straight away and needs EML parsing before --------- Co-authored-by: Simon Lyngshede <slyngshede@wikimedia.org>
1 parent 5a43070 commit 0bf7922

File tree

9 files changed

+197
-0
lines changed

9 files changed

+197
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ By default, there is a `GenericProvider` that supports a `SimpleProcessor` using
6868

6969
#### Supported providers based on other parsers
7070

71+
- Apple
7172
- AWS
7273
- AquaComms
7374
- BSO

circuit_maintenance_parser/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
HGC,
1313
NTT,
1414
AquaComms,
15+
Apple,
1516
Arelion,
1617
Cogent,
1718
Colt,
@@ -40,6 +41,7 @@
4041

4142
SUPPORTED_PROVIDERS = (
4243
GenericProvider,
44+
Apple,
4345
AquaComms,
4446
Arelion,
4547
AWS,
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""Apple peering parser."""
2+
import email
3+
import re
4+
5+
from datetime import datetime, timezone
6+
from typing import Dict, List
7+
8+
from circuit_maintenance_parser.output import Impact, Status
9+
from circuit_maintenance_parser.parser import EmailSubjectParser, Text, CircuitImpact
10+
11+
12+
class SubjectParserApple(EmailSubjectParser):
13+
"""Subject parser for Apple notification."""
14+
15+
def parse_subject(self, subject: str) -> List[Dict]:
16+
"""Use the subject of the email as summary.
17+
18+
Args:
19+
subject (str): Message subjects
20+
21+
Returns:
22+
List[Dict]: List of attributes for Maintenance object
23+
"""
24+
return [{"summary": subject}]
25+
26+
27+
class TextParserApple(Text):
28+
"""Parse the plaintext content of an Apple notification.
29+
30+
Args:
31+
Text (str): Plaintext message
32+
"""
33+
34+
def parse_text(self, text: str) -> List[Dict]:
35+
"""Extract attributes from an Apple notification email.
36+
37+
Args:
38+
text (str): plaintext message
39+
40+
Returns:
41+
List[Dict]: List of attributes for Maintenance object
42+
"""
43+
data = {
44+
"circuits": self._circuits(text),
45+
"maintenance_id": self._maintenance_id(text),
46+
"start": self._start_time(text),
47+
"stamp": self._start_time(text),
48+
"end": self._end_time(text),
49+
"status": Status.CONFIRMED, # Have yet to see anything but confirmation.
50+
"organizer": "peering-noc@group.apple.com",
51+
"provider": "apple",
52+
"account": "Customer info unavailable",
53+
}
54+
return [data]
55+
56+
def _circuits(self, text):
57+
pattern = r"Peer AS: (\d*)"
58+
match = re.search(pattern, text)
59+
return [CircuitImpact(circuit_id=f"AS{match.group(1)}", impact=Impact.OUTAGE)]
60+
61+
def _maintenance_id(self, text):
62+
# Apple ticket numbers always starts with "CHG".
63+
pattern = r"CHG(\d*)"
64+
match = re.search(pattern, text)
65+
return match.group(0)
66+
67+
def _get_time(self, pattern, text):
68+
# Apple sends timestamps as RFC2822 for the US
69+
# but a custom format for EU datacenters.
70+
match = re.search(pattern, text)
71+
try:
72+
# Try EU timestamp
73+
return int(
74+
datetime.strptime(match.group(1), "%Y-%m-%d(%a) %H:%M %Z").replace(tzinfo=timezone.utc).timestamp()
75+
)
76+
except ValueError:
77+
# Try RFC2822 - US timestamp
78+
rfc2822 = match.group(1)
79+
time_tuple = email.utils.parsedate_tz(rfc2822)
80+
return email.utils.mktime_tz(time_tuple)
81+
82+
def _start_time(self, text):
83+
pattern = "Start Time: ([a-zA-Z0-9 :()-]*)"
84+
return self._get_time(pattern, text)
85+
86+
def _end_time(self, text):
87+
pattern = "End Time: ([a-zA-Z0-9 :()-]*)"
88+
return self._get_time(pattern, text)

circuit_maintenance_parser/provider.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
from circuit_maintenance_parser.errors import ProcessorError, ProviderError
1515
from circuit_maintenance_parser.output import Maintenance
1616
from circuit_maintenance_parser.parser import EmailDateParser, ICal
17+
18+
from circuit_maintenance_parser.parsers.apple import SubjectParserApple, TextParserApple
1719
from circuit_maintenance_parser.parsers.aquacomms import HtmlParserAquaComms1, SubjectParserAquaComms1
1820
from circuit_maintenance_parser.parsers.aws import SubjectParserAWS1, TextParserAWS1
1921
from circuit_maintenance_parser.parsers.bso import HtmlParserBSO1
@@ -205,6 +207,15 @@ def get_provider_type(cls) -> str:
205207
####################
206208

207209

210+
class Apple(GenericProvider):
211+
"""Apple provider custom class."""
212+
213+
_processors: List[GenericProcessor] = [
214+
CombinedProcessor(data_parsers=[TextParserApple, SubjectParserApple]),
215+
]
216+
_default_organizer = "peering-noc@group.apple.com"
217+
218+
208219
class AquaComms(GenericProvider):
209220
"""AquaComms provider custom class."""
210221

tests/unit/data/apple/apple1.eml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
MIME-version: 1.0
2+
Date: Thu, 26 Aug 2021 18:22:09 +0100
3+
Subject: Apple (AS714) Net Maintenance Notice for AS12345 in Chicago
4+
Content-type: multipart/mixed; boundary="===============1255676001481217459=="
5+
6+
--===============1255676001481217459==
7+
Content-Type: text/plain; charset="us-ascii"
8+
MIME-Version: 1.0
9+
Content-Transfer-Encoding: 7bit
10+
11+
Apple (AS714) Network Maintenance Notification
12+
Reference Internal Ticket: CHG000123456
13+
Message sent to: noc@example.org
14+
15+
Details:
16+
Start Time: 2 May 2023 14:00 UTC
17+
End Time: 16 May 2023 23:59 UTC
18+
19+
Peering Details:
20+
Apple AS: 714
21+
Peer AS: 12345
22+
23+
24+
Peer IP: ['10.0.0.2', '2000:0000:0000:0000:0000:0000:0000:0002']
25+
26+
There is no change required on your end as we will gracefully drain the traffic prior to maintenance.
27+
28+
When the maintenance is completed, Apple will automatically re-route traffic back.
29+
For reference, our current max prefix setting recommendation is: IPv4 = 10000 and IPv6 = 1000.
30+
Questions about this event can be sent to peering-noc@group.apple.com.
31+
32+
Thank you for peering with Apple!
33+
34+
Regards,
35+
36+
Apple Peering NOC (AS714)
37+
peering-noc@group.apple.com
38+
as714.peeringdb.com
39+
40+
--===============1255676001481217459==--
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[
2+
{
3+
"circuits": [
4+
{
5+
"circuit_id": "AS12345",
6+
"impact": "OUTAGE"
7+
}
8+
],
9+
"end": 1684281540,
10+
"maintenance_id": "CHG000123456",
11+
"start": 1683036000,
12+
"stamp": 1683036000,
13+
"status": "CONFIRMED",
14+
"summary": "Apple (AS714) Net Maintenance Notice for AS12345 in Chicago",
15+
"organizer": "peering-noc@group.apple.com",
16+
"provider": "apple",
17+
"account": "Customer info unavailable"
18+
}
19+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[
2+
{
3+
"circuits": [
4+
{
5+
"circuit_id": "AS12345",
6+
"impact": "OUTAGE"
7+
}
8+
],
9+
"end": 1684281540,
10+
"maintenance_id": "CHG000123456",
11+
"start": 1683036000,
12+
"stamp": 1683036000,
13+
"status": "CONFIRMED",
14+
"organizer": "peering-noc@group.apple.com",
15+
"provider": "apple",
16+
"account": "Customer info unavailable"
17+
}
18+
]

tests/unit/test_e2e.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
HGC,
1919
NTT,
2020
AquaComms,
21+
Apple,
2122
Arelion,
2223
Cogent,
2324
Colt,
@@ -62,6 +63,16 @@
6263
GENERIC_ICAL_RESULT_PATH,
6364
],
6465
),
66+
# Apple
67+
(
68+
Apple,
69+
[
70+
("email", Path(dir_path, "data", "apple", "apple1.eml")),
71+
],
72+
[
73+
Path(dir_path, "data", "apple", "apple1_result.json"),
74+
],
75+
),
6576
# AquaComms
6677
(
6778
AquaComms,

tests/unit/test_parsers.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from circuit_maintenance_parser.errors import ParserError
1010
from circuit_maintenance_parser.parser import EmailDateParser, ICal
11+
from circuit_maintenance_parser.parsers.apple import TextParserApple
1112
from circuit_maintenance_parser.parsers.aquacomms import HtmlParserAquaComms1, SubjectParserAquaComms1
1213
from circuit_maintenance_parser.parsers.aws import SubjectParserAWS1, TextParserAWS1
1314
from circuit_maintenance_parser.parsers.bso import HtmlParserBSO1
@@ -94,6 +95,12 @@ def default(self, o):
9495
Path(dir_path, "data", "ical", "ical7"),
9596
Path(dir_path, "data", "ical", "ical7_result.json"),
9697
),
98+
# Apple
99+
(
100+
TextParserApple,
101+
Path(dir_path, "data", "apple", "apple1.eml"),
102+
Path(dir_path, "data", "apple", "apple1_text_parser_result.json"),
103+
),
97104
# AquaComms
98105
(
99106
HtmlParserAquaComms1,

0 commit comments

Comments
 (0)