Skip to content

Commit

Permalink
Apple Parser (#303)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
chadell and slyngshede authored Jan 7, 2025
1 parent 5a43070 commit 0bf7922
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ By default, there is a `GenericProvider` that supports a `SimpleProcessor` using

#### Supported providers based on other parsers

- Apple
- AWS
- AquaComms
- BSO
Expand Down
2 changes: 2 additions & 0 deletions circuit_maintenance_parser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
HGC,
NTT,
AquaComms,
Apple,
Arelion,
Cogent,
Colt,
Expand Down Expand Up @@ -40,6 +41,7 @@

SUPPORTED_PROVIDERS = (
GenericProvider,
Apple,
AquaComms,
Arelion,
AWS,
Expand Down
88 changes: 88 additions & 0 deletions circuit_maintenance_parser/parsers/apple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""Apple peering parser."""
import email
import re

from datetime import datetime, timezone
from typing import Dict, List

from circuit_maintenance_parser.output import Impact, Status
from circuit_maintenance_parser.parser import EmailSubjectParser, Text, CircuitImpact


class SubjectParserApple(EmailSubjectParser):
"""Subject parser for Apple notification."""

def parse_subject(self, subject: str) -> List[Dict]:
"""Use the subject of the email as summary.
Args:
subject (str): Message subjects
Returns:
List[Dict]: List of attributes for Maintenance object
"""
return [{"summary": subject}]


class TextParserApple(Text):
"""Parse the plaintext content of an Apple notification.
Args:
Text (str): Plaintext message
"""

def parse_text(self, text: str) -> List[Dict]:
"""Extract attributes from an Apple notification email.
Args:
text (str): plaintext message
Returns:
List[Dict]: List of attributes for Maintenance object
"""
data = {
"circuits": self._circuits(text),
"maintenance_id": self._maintenance_id(text),
"start": self._start_time(text),
"stamp": self._start_time(text),
"end": self._end_time(text),
"status": Status.CONFIRMED, # Have yet to see anything but confirmation.
"organizer": "[email protected]",
"provider": "apple",
"account": "Customer info unavailable",
}
return [data]

def _circuits(self, text):
pattern = r"Peer AS: (\d*)"
match = re.search(pattern, text)
return [CircuitImpact(circuit_id=f"AS{match.group(1)}", impact=Impact.OUTAGE)]

def _maintenance_id(self, text):
# Apple ticket numbers always starts with "CHG".
pattern = r"CHG(\d*)"
match = re.search(pattern, text)
return match.group(0)

def _get_time(self, pattern, text):
# Apple sends timestamps as RFC2822 for the US
# but a custom format for EU datacenters.
match = re.search(pattern, text)
try:
# Try EU timestamp
return int(
datetime.strptime(match.group(1), "%Y-%m-%d(%a) %H:%M %Z").replace(tzinfo=timezone.utc).timestamp()
)
except ValueError:
# Try RFC2822 - US timestamp
rfc2822 = match.group(1)
time_tuple = email.utils.parsedate_tz(rfc2822)
return email.utils.mktime_tz(time_tuple)

def _start_time(self, text):
pattern = "Start Time: ([a-zA-Z0-9 :()-]*)"
return self._get_time(pattern, text)

def _end_time(self, text):
pattern = "End Time: ([a-zA-Z0-9 :()-]*)"
return self._get_time(pattern, text)
11 changes: 11 additions & 0 deletions circuit_maintenance_parser/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from circuit_maintenance_parser.errors import ProcessorError, ProviderError
from circuit_maintenance_parser.output import Maintenance
from circuit_maintenance_parser.parser import EmailDateParser, ICal

from circuit_maintenance_parser.parsers.apple import SubjectParserApple, TextParserApple
from circuit_maintenance_parser.parsers.aquacomms import HtmlParserAquaComms1, SubjectParserAquaComms1
from circuit_maintenance_parser.parsers.aws import SubjectParserAWS1, TextParserAWS1
from circuit_maintenance_parser.parsers.bso import HtmlParserBSO1
Expand Down Expand Up @@ -205,6 +207,15 @@ def get_provider_type(cls) -> str:
####################


class Apple(GenericProvider):
"""Apple provider custom class."""

_processors: List[GenericProcessor] = [
CombinedProcessor(data_parsers=[TextParserApple, SubjectParserApple]),
]
_default_organizer = "[email protected]"


class AquaComms(GenericProvider):
"""AquaComms provider custom class."""

Expand Down
40 changes: 40 additions & 0 deletions tests/unit/data/apple/apple1.eml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
MIME-version: 1.0
Date: Thu, 26 Aug 2021 18:22:09 +0100
Subject: Apple (AS714) Net Maintenance Notice for AS12345 in Chicago
Content-type: multipart/mixed; boundary="===============1255676001481217459=="

--===============1255676001481217459==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Apple (AS714) Network Maintenance Notification
Reference Internal Ticket: CHG000123456
Message sent to: [email protected]
Details:
Start Time: 2 May 2023 14:00 UTC
End Time: 16 May 2023 23:59 UTC
Peering Details:
Apple AS: 714
Peer AS: 12345
Peer IP: ['10.0.0.2', '2000:0000:0000:0000:0000:0000:0000:0002']
There is no change required on your end as we will gracefully drain the traffic prior to maintenance.
When the maintenance is completed, Apple will automatically re-route traffic back.
For reference, our current max prefix setting recommendation is: IPv4 = 10000 and IPv6 = 1000.
Questions about this event can be sent to [email protected].
Thank you for peering with Apple!
Regards,
Apple Peering NOC (AS714)
[email protected]
as714.peeringdb.com
--===============1255676001481217459==--
19 changes: 19 additions & 0 deletions tests/unit/data/apple/apple1_result.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[
{
"circuits": [
{
"circuit_id": "AS12345",
"impact": "OUTAGE"
}
],
"end": 1684281540,
"maintenance_id": "CHG000123456",
"start": 1683036000,
"stamp": 1683036000,
"status": "CONFIRMED",
"summary": "Apple (AS714) Net Maintenance Notice for AS12345 in Chicago",
"organizer": "[email protected]",
"provider": "apple",
"account": "Customer info unavailable"
}
]
18 changes: 18 additions & 0 deletions tests/unit/data/apple/apple1_text_parser_result.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
{
"circuits": [
{
"circuit_id": "AS12345",
"impact": "OUTAGE"
}
],
"end": 1684281540,
"maintenance_id": "CHG000123456",
"start": 1683036000,
"stamp": 1683036000,
"status": "CONFIRMED",
"organizer": "[email protected]",
"provider": "apple",
"account": "Customer info unavailable"
}
]
11 changes: 11 additions & 0 deletions tests/unit/test_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
HGC,
NTT,
AquaComms,
Apple,
Arelion,
Cogent,
Colt,
Expand Down Expand Up @@ -62,6 +63,16 @@
GENERIC_ICAL_RESULT_PATH,
],
),
# Apple
(
Apple,
[
("email", Path(dir_path, "data", "apple", "apple1.eml")),
],
[
Path(dir_path, "data", "apple", "apple1_result.json"),
],
),
# AquaComms
(
AquaComms,
Expand Down
7 changes: 7 additions & 0 deletions tests/unit/test_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from circuit_maintenance_parser.errors import ParserError
from circuit_maintenance_parser.parser import EmailDateParser, ICal
from circuit_maintenance_parser.parsers.apple import TextParserApple
from circuit_maintenance_parser.parsers.aquacomms import HtmlParserAquaComms1, SubjectParserAquaComms1
from circuit_maintenance_parser.parsers.aws import SubjectParserAWS1, TextParserAWS1
from circuit_maintenance_parser.parsers.bso import HtmlParserBSO1
Expand Down Expand Up @@ -94,6 +95,12 @@ def default(self, o):
Path(dir_path, "data", "ical", "ical7"),
Path(dir_path, "data", "ical", "ical7_result.json"),
),
# Apple
(
TextParserApple,
Path(dir_path, "data", "apple", "apple1.eml"),
Path(dir_path, "data", "apple", "apple1_text_parser_result.json"),
),
# AquaComms
(
HtmlParserAquaComms1,
Expand Down

0 comments on commit 0bf7922

Please sign in to comment.