diff --git a/README.md b/README.md
index 0f5c248a..e9cb7249 100644
--- a/README.md
+++ b/README.md
@@ -75,6 +75,7 @@ By default, there is a `GenericProvider` that supports a `SimpleProcessor` using
- Equinix
- EXA (formerly GTT)
- HGC
+- Google
- Lumen
- Megaport
- Momentum
diff --git a/circuit_maintenance_parser/__init__.py b/circuit_maintenance_parser/__init__.py
index f22c54d2..f9940ef4 100644
--- a/circuit_maintenance_parser/__init__.py
+++ b/circuit_maintenance_parser/__init__.py
@@ -16,6 +16,7 @@
Equinix,
EUNetworks,
GTT,
+ Google,
HGC,
Lumen,
Megaport,
@@ -43,6 +44,7 @@
CrownCastle,
Equinix,
EUNetworks,
+ Google,
GTT,
HGC,
Lumen,
diff --git a/circuit_maintenance_parser/parsers/google.py b/circuit_maintenance_parser/parsers/google.py
new file mode 100644
index 00000000..ef7ef2f6
--- /dev/null
+++ b/circuit_maintenance_parser/parsers/google.py
@@ -0,0 +1,44 @@
+"""Google parser."""
+import logging
+import re
+from datetime import datetime
+
+from circuit_maintenance_parser.parser import Html, Impact, CircuitImpact, Status
+
+# pylint: disable=too-many-nested-blocks, too-many-branches
+
+logger = logging.getLogger(__name__)
+
+
+class HtmlParserGoogle1(Html):
+ """Notifications Parser for Google notifications."""
+
+ def parse_html(self, soup):
+ """Execute parsing."""
+ data = {}
+ data["circuits"] = []
+ data["status"] = Status.CONFIRMED
+
+ for span in soup.find_all("span"):
+ if span.string is None:
+ continue
+ if span.string.strip() == "Start Time:":
+ dt_str = span.next_sibling.string.strip()
+ data["start"] = self.dt2ts(datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S %z UTC"))
+ elif span.string.strip() == "End Time:":
+ dt_str = span.next_sibling.string.strip()
+ data["end"] = self.dt2ts(datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S %z UTC"))
+ elif span.string.strip() == "Peer ASN:":
+ data["account"] = span.parent.next_sibling.string.strip()
+ elif span.string.strip() == "Google Neighbor Address(es):":
+ googleaddr = span.parent.next_sibling.string.strip()
+ elif span.string.strip() == "Peer Neighbor Address(es):":
+ cid = googleaddr + "-" + span.parent.next_sibling.string.strip()
+ data["circuits"].append(CircuitImpact(circuit_id=cid, impact=Impact.OUTAGE))
+
+ summary = list(soup.find("div").find("div").strings)[-1].strip()
+ match = re.search(r" - Reference (.*)$", summary)
+ data["summary"] = summary
+ data["maintenance_id"] = match[1]
+
+ return [data]
diff --git a/circuit_maintenance_parser/provider.py b/circuit_maintenance_parser/provider.py
index cf40789a..aded2224 100644
--- a/circuit_maintenance_parser/provider.py
+++ b/circuit_maintenance_parser/provider.py
@@ -26,6 +26,7 @@
from circuit_maintenance_parser.parsers.crowncastle import HtmlParserCrownCastle1
from circuit_maintenance_parser.parsers.equinix import HtmlParserEquinix, SubjectParserEquinix
from circuit_maintenance_parser.parsers.gtt import HtmlParserGTT1
+from circuit_maintenance_parser.parsers.google import HtmlParserGoogle1
from circuit_maintenance_parser.parsers.hgc import HtmlParserHGC1, HtmlParserHGC2, SubjectParserHGC1
from circuit_maintenance_parser.parsers.lumen import HtmlParserLumen1
from circuit_maintenance_parser.parsers.megaport import HtmlParserMegaport1
@@ -252,6 +253,15 @@ class EUNetworks(GenericProvider):
_default_organizer = "noc@eunetworks.com"
+class Google(GenericProvider):
+ """Google provider custom class."""
+
+ _processors: List[GenericProcessor] = [
+ CombinedProcessor(data_parsers=[EmailDateParser, HtmlParserGoogle1]),
+ ]
+ _default_organizer = "noc-noreply@google.com"
+
+
class GTT(GenericProvider):
"""EXA (formerly GTT) provider custom class."""
diff --git a/tests/unit/data/google/google1.html b/tests/unit/data/google/google1.html
new file mode 100644
index 00000000..9990a28e
--- /dev/null
+++ b/tests/unit/data/google/google1.html
@@ -0,0 +1,64 @@
+
+
Google Network Maintenance Notification - Reference PCR/123456
+
+
+
+ This message was sent to: [sample@example.net]
+ This address was selected from the
Google ISP Portal. You can update your preferences by navigating to "Configuration > Contacts" and adjusting Email Subscriptions for each user. You can find additional details about Contacts
here.
+
+
+
+
+
+
Details:
+
Start Time: 2023-12-05 20:55:00 +0000 UTC
+
End Time: 2023-12-06 05:05:00 +0000 UTC
+
Duration: 8h10m0s
+
+
+ Google ASN: | 15169 |
+ Peer ASN: | 65001 |
+
+ =20
+
+ Google Neighbor Address(es): | 192.0.2.1 |
+ Peer Neighbor Address(es): | 192.0.2.0 |
+
+ =20
+
+ Google Neighbor Address(es): | 2001:db8::1 |
+ Peer Neighbor Address(es): | 2001:db8:: |
+
+ =20
+
+
+
+ Google systems will automatically re-route the traffic away from the peering link(s) under maintenance.
+
+
+ Depending on the nature of the work, your BGP session with Google may stay up through the maintenance window. Traffic may shift to alternate sessions with Google and/or your upstream provider(s).
+
+
+ Questions about this event can be sent to noc@google.com.
+
+
+ Thank you for peering with Google!
+
+
+
+
+ PRIVILEGE AND CONFIDENTIALITY NOTICE: The information in this
+ e-mail communication and any attached documents may be privileged,
+ confidential or otherwise protected from disclosure and is intended only
+ for the use of the designated recipient(s). If the reader is neither the
+ intended recipient nor an employee or agent thereof who is responsible
+ for delivering it to the intended recipient, you are hereby notified
+ that any review, dissemination, distribution, copying or other use of
+ this communication is strictly prohibited. If you have received this
+ communication in error, please immediately notify us by return e-mail
+ and promptly delete the original electronic e-mail communication and
+ any attached documentation. Receipt by anyone other than the intended
+ recipient is not a waiver of any attorney-client or work-product
+ privilege.
+
+
diff --git a/tests/unit/data/google/google1_parser_result.json b/tests/unit/data/google/google1_parser_result.json
new file mode 100644
index 00000000..a7d84b51
--- /dev/null
+++ b/tests/unit/data/google/google1_parser_result.json
@@ -0,0 +1,20 @@
+[
+ {
+ "account": "65001",
+ "circuits": [
+ {
+ "circuit_id": "192.0.2.1-192.0.2.0",
+ "impact": "OUTAGE"
+ },
+ {
+ "circuit_id": "2001:db8::1-2001:db8::",
+ "impact": "OUTAGE"
+ }
+ ],
+ "end": 1701839100,
+ "maintenance_id": "PCR/123456",
+ "start": 1701809700,
+ "status": "CONFIRMED",
+ "summary": "Network Maintenance Notification - Reference PCR/123456"
+ }
+]
diff --git a/tests/unit/data/google/google2.eml b/tests/unit/data/google/google2.eml
new file mode 100644
index 00000000..5e8c58cb
--- /dev/null
+++ b/tests/unit/data/google/google2.eml
@@ -0,0 +1,157 @@
+Date: Wed, 06 Dec 2023 00:40:05 +0000
+Subject: [Scheduled] Google Planned Network Maintenance Notification - Reference PCR/123456
+From: Google NOC
+Content-Type: multipart/alternative; boundary="000000000000a556e4060bffffff"
+
+--000000000000a556e4060bffffff
+Content-Type: text/plain; charset="UTF-8"; format=flowed; delsp=yes
+
+Google Network Maintenance Notification - Reference PCR/123456
+
+
+This message was sent to: [sample@example.net]
+This address was selected from the Google ISP Portal. You can update your
+preferences by navigating to "Configuration > Contacts" and adjusting Email
+Subscriptions for each user. You can find additional details about Contacts
+here.
+
+
+Details:
+Start Time: 2023-12-05 20:55:00 +0000 UTC
+End Time: 2023-12-06 05:05:00 +0000 UTC
+Duration: 8h10m0s
+
+
+Google ASN: 15169 Peer ASN: 65001
+
+Google Neighbor Address(es): 192.0.2.1 Peer Neighbor Address(es):
+192.0.2.0
+
+Google Neighbor Address(es): 2001:db8::1 Peer Neighbor
+Address(es): 2001:db8::
+
+
+Google systems will automatically re-route the traffic away from the
+peering link(s) under maintenance.
+
+Depending on the nature of the work, your BGP session with Google may stay
+up through the maintenance window. Traffic may shift to alternate sessions
+with Google and/or your upstream provider(s).
+
+Questions about this event can be sent to noc@google.com.
+
+Thank you for peering with Google!
+
+
+
+PRIVILEGE AND CONFIDENTIALITY NOTICE: The information in this e-mail
+communication and any attached documents may be privileged, confidential or
+otherwise protected from disclosure and is intended only for the use of the
+designated recipient(s). If the reader is neither the intended recipient
+nor an employee or agent thereof who is responsible for delivering it to
+the intended recipient, you are hereby notified that any review,
+dissemination, distribution, copying or other use of this communication is
+strictly prohibited. If you have received this communication in error,
+please immediately notify us by return e-mail and promptly delete the
+original electronic e-mail communication and any attached documentation.
+Receipt by anyone other than the intended recipient is not a waiver of any
+attorney-client or work-product privilege.
+
+
+--000000000000a556e4060bffffff
+Content-Type: text/html; charset="UTF-8"
+Content-Transfer-Encoding: quoted-printable
+
+
+
+
Google Network Maintenance No=
+tification - Reference PCR/123456
+
+ =20
+
+ This message was sent to: [sample@example.net]
+ This address was selected from the
Google ISP Portal. You can update your preferences by navigating to "C=
+onfiguration > Contacts" and adjusting Email Subscriptions for each user. Y=
+ou can find additional details about Contacts
here.
+
+
+
+
+ =20
+
Details:
+
Start Time: 2023-12-05 20:55:00=
+ +0000 UTC
+
End Time: 2023-12-06 05:05:00 &=
+#43;0000 UTC
+
Duration: 8h10m0s
+
+
+ Google ASN: | 151=
+69 |
+ Peer ASN: | 65001<=
+/td>
+ |
+ =20
+
+ Google Neighbor Address(es):=
+span> | 192.0.2.1 |
+ Peer Neighbor Address(es): | 192.0.2.0 |
+
+ =20
+
+ Google Neighbor Address(es):=
+span> | 2001:db8::1 |
+ Peer Neighbor Address(es): | 2001:db8:: |
+
+ =20
+
+
+
+ Google systems will automatically re-route the traffic away from the peer=
+ing link(s) under maintenance.
+
+
+ Depending on the nature of the work, your BGP session with Google may sta=
+y up through the maintenance window. Traffic may shift to alternate session=
+s with Google and/or your upstream provider(s).
+
+
+ Questions about this event can be sent to noc@google.com.
+
+
+ Thank you for peering with Google!
+
+
+
+
+ PRIVILEGE AND CONFIDENTIALITY NOTICE: The information in this
+ e-mail communication and any attached documents may be privileged,
+ confidential or otherwise protected from disclosure and is intended onl=
+y
+ for the use of the designated recipient(s). If the reader is neither th=
+e
+ intended recipient nor an employee or agent thereof who is responsible
+ for delivering it to the intended recipient, you are hereby notified
+ that any review, dissemination, distribution, copying or other use of
+ this communication is strictly prohibited. If you have received this
+ communication in error, please immediately notify us by return e-mail
+ and promptly delete the original electronic e-mail communication and
+ any attached documentation. Receipt by anyone other than the intended
+ recipient is not a waiver of any attorney-client or work-product
+ privilege.
+
+
+
+--000000000000a556e4060bffffff--
diff --git a/tests/unit/data/google/google2_result.json b/tests/unit/data/google/google2_result.json
new file mode 100644
index 00000000..ce194ea5
--- /dev/null
+++ b/tests/unit/data/google/google2_result.json
@@ -0,0 +1,25 @@
+[
+ {
+ "account": "65001",
+ "circuits": [
+ {
+ "circuit_id": "192.0.2.1-192.0.2.0",
+ "impact": "OUTAGE"
+ },
+ {
+ "circuit_id": "2001:db8::1-2001:db8::",
+ "impact": "OUTAGE"
+ }
+ ],
+ "end": 1701839100,
+ "maintenance_id": "PCR/123456",
+ "organizer": "noc-noreply@google.com",
+ "provider": "google",
+ "sequence": 1,
+ "stamp": 1701823205,
+ "start": 1701809700,
+ "status": "CONFIRMED",
+ "summary": "Network Maintenance Notification - Reference PCR/123456",
+ "uid": "0"
+ }
+]
diff --git a/tests/unit/test_e2e.py b/tests/unit/test_e2e.py
index 489ad9a0..a1152ef7 100644
--- a/tests/unit/test_e2e.py
+++ b/tests/unit/test_e2e.py
@@ -21,6 +21,7 @@
Colt,
CrownCastle,
EUNetworks,
+ Google,
GTT,
HGC,
Lumen,
@@ -420,6 +421,16 @@
Path(dir_path, "data", "hgc", "hgc2_result.json"),
],
),
+ # Google
+ (
+ Google,
+ [
+ ("email", Path(dir_path, "data", "google", "google2.eml")),
+ ],
+ [
+ Path(dir_path, "data", "google", "google2_result.json"),
+ ],
+ ),
# Lumen
(
Lumen,
diff --git a/tests/unit/test_parsers.py b/tests/unit/test_parsers.py
index c2baa672..644cecea 100644
--- a/tests/unit/test_parsers.py
+++ b/tests/unit/test_parsers.py
@@ -14,6 +14,7 @@
from circuit_maintenance_parser.parsers.colt import CsvParserColt1, SubjectParserColt1, SubjectParserColt2
from circuit_maintenance_parser.parsers.crowncastle import HtmlParserCrownCastle1
from circuit_maintenance_parser.parsers.equinix import HtmlParserEquinix, SubjectParserEquinix
+from circuit_maintenance_parser.parsers.google import HtmlParserGoogle1
from circuit_maintenance_parser.parsers.gtt import HtmlParserGTT1
from circuit_maintenance_parser.parsers.hgc import HtmlParserHGC1, HtmlParserHGC2
from circuit_maintenance_parser.parsers.lumen import HtmlParserLumen1
@@ -297,6 +298,12 @@
Path(dir_path, "data", "equinix", "equinix9.eml"),
Path(dir_path, "data", "equinix", "equinix9_result.json"),
),
+ # Google
+ (
+ HtmlParserGoogle1,
+ Path(dir_path, "data", "google", "google1.html"),
+ Path(dir_path, "data", "google", "google1_parser_result.json"),
+ ),
# GTT
(
HtmlParserGTT1,