Skip to content

Commit

Permalink
Merge pull request nautobot#201 from networktocode/release-v2.2.1
Browse files Browse the repository at this point in the history
Release v2.2.1
  • Loading branch information
chadell authored Jan 17, 2023
2 parents a38252e + 4402720 commit 322d821
Show file tree
Hide file tree
Showing 25 changed files with 1,748 additions and 135 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ jobs:
run: "echo RELEASE_VERSION=${GITHUB_REF:10} >> $GITHUB_ENV"
- name: "Run Poetry Version"
run: "poetry version $RELEASE_VERSION"
- name: "Run Poetry Build"
run: "poetry build"
- name: "Upload binaries to release"
uses: "svenstaro/upload-release-action@v2"
with:
Expand Down Expand Up @@ -176,6 +178,8 @@ jobs:
run: "echo RELEASE_VERSION=${GITHUB_REF:10} >> $GITHUB_ENV"
- name: "Run Poetry Version"
run: "poetry version $RELEASE_VERSION"
- name: "Run Poetry Build"
run: "poetry build"
- name: "Push to PyPI"
uses: "pypa/gh-action-pypi-publish@release/v1"
with:
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## v2.2.1 - 2023-01-17

### Changed

- #197 - Updated Equinix parser: Adding support for additional impact statement and notification types.
- #192 - Updated Cogent parser: Adding subject and text parser.
- #186 - Updated Telia Carrier as Arelion (while keeping Telia for backwards compatibility).

### Fixed

- #198 - Fixed Verizon parser: use European-style day-first date parsing
- #187 - Fixed Zayo parser: adds chardet.detect method before decoding data_part.content.

## v2.2.0 - 2022-10-25

### Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ By default, there is a `GenericProvider` that support a `SimpleProcessor` using

#### Supported providers using the BCOP standard

- Arelion (previously Telia)
- EuNetworks
- NTT
- PacketFabric
- Telia
- Telstra

#### Supported providers based on other parsers
Expand Down
134 changes: 133 additions & 1 deletion circuit_maintenance_parser/parsers/cogent.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,145 @@
from pytz import timezone, UTC
from bs4.element import ResultSet # type: ignore

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

logger = logging.getLogger(__name__)

# pylint: disable=too-many-branches


class SubjectParserCogent1(EmailSubjectParser):
"""Subject parser for Cogent nofifications."""

def parse_subject(self, subject: str):
"""Parse subject.
Example:
11/19/2022 Circuit Provider Maintenance - Edina, MN 1-300123456
Correction 06/11/2021 AB987654321-1 Planned Network Maintenance - San Jose, CA 1-123456789
"""
data: Dict = {"circuits": []}

subject = subject.lower()

if subject.startswith("correction") or "rescheduled" in subject:
data["status"] = Status("RE-SCHEDULED")
elif "cancellation" in subject:
data["status"] = Status("CANCELLED")
elif "planned" in subject or "provider" in subject or "emergency" in subject:
data["status"] = Status("CONFIRMED")
elif "completed" in subject:
data["status"] = Status("COMPLETED")
else:
data["status"] = Status("NO-CHANGE")

match = re.search(r".* ([\d-]+)", subject)
if match:
circuit_id = match.group(1)
data["circuits"].append(CircuitImpact(impact=Impact("OUTAGE"), circuit_id=circuit_id.strip()))

return [data]


class TextParserCogent1(Text):
"""Parse text body of Cogent emails."""

def parse_text(self, text):
"""Execute parsing of text.
Example:
CIRCUIT PROVIDER MAINTENANCE
Dear Cogent Customer,
As a valued customer, Cogent is committed to keeping you informed about any changes in the status of your service with us. This email is to alert you regarding a circuit provider maintenance which will affect your connection to Cogent:
Start time: 10:00pm CT 11/19/2022
End time: 5:00am CT 11/20/2022
Work order number: VN16123
Order ID(s) impacted: 1-300123456
Expected Outage/Downtime: 7 hours
Cogent customers receiving service in Edina, MN will be affected by this outage. This outage has been scheduled by Zayo. The purpose of this maintenance is to repair damaged fiber. Only the Cogent Order ID(s) above will be impacted.
During this maintenance window, you will experience an interruption in service while Zayo completes the maintenance activities; the interruption is expected to be less than 7 hours; however, due to the complexity of the work, your downtime may be longer.
Our network operations engineers closely monitor the work and will do everything possible to minimize any inconvenience to you. If you have any problems with your connection after this time, or if you have any questions regarding the maintenance at any point, please call Customer Support at 1-877-7-COGENT and refer to this Maintenance Ticket: VN16123.
"""
data = {
# "circuits": [],
"summary": "Cogent circuit maintenance",
}

lines = text.splitlines()

for line in lines:
if line.startswith("Dear"):
match = re.search(r"Dear (.*),", line)
if match:
data["account"] = match.group(1)
elif line.startswith("Start time:"):
match = re.search(r"Start time: ([A-Za-z\d: ]*) [()A-Za-z\s]+ (\d+/\d+/\d+)", line)
if match:
start_str = " ".join(match.groups())
elif line.startswith("End time:"):
match = re.search(r"End time: ([A-Za-z\d: ]*) [()A-Za-z\s]+ (\d+/\d+/\d+)", line)
if match:
end_str = " ".join(match.groups())
elif line.startswith("Cogent customers receiving service"):
data["summary"] = line
match = re.search(r"[^Cogent].*?((\b[A-Z][a-z\s-]+)+, ([A-Za-z-]+[\s-]))", line)
if match:
local_timezone = timezone(self._geolocator.city_timezone(match.group(1).strip()))

# set start time using the local city timezone
try:
start = datetime.strptime(start_str, "%I:%M %p %d/%m/%Y")
except ValueError:
start = datetime.strptime(start_str, "%I:%M%p %d/%m/%Y")
local_time = local_timezone.localize(start)
# set start time to UTC
utc_start = local_time.astimezone(UTC)
data["start"] = self.dt2ts(utc_start)
logger.info(
"Mapped start time %s at %s (%s), to %s (UTC)",
start_str,
match.group(1).strip(),
local_timezone,
utc_start,
)
# set end time using the local city timezone
try:
end = datetime.strptime(end_str, "%I:%M %p %d/%m/%Y")
except ValueError:
end = datetime.strptime(end_str, "%I:%M%p %d/%m/%Y")
local_time = local_timezone.localize(end)
# set end time to UTC
utc_end = local_time.astimezone(UTC)
data["end"] = self.dt2ts(utc_end)
logger.info(
"Mapped end time %s at %s (%s), to %s (UTC)",
end_str,
match.group(1).strip(),
local_timezone,
utc_end,
)
elif line.startswith("Work order number:"):
match = re.search("Work order number: (.*)", line)
if match:
data["maintenance_id"] = match.group(1)
elif line.startswith("Order ID(s) impacted:"):
data["circuits"] = []
match = re.search(r"Order ID\(s\) impacted: (.*)", line)
if match:
for circuit_id in match.group(1).split(","):
data["circuits"].append(CircuitImpact(impact=Impact("OUTAGE"), circuit_id=circuit_id.strip()))
elif line.startswith("During this maintenance"):
data["summary"] = line
return [data]


class HtmlParserCogent1(Html):
"""Notifications Parser for Cogent notifications."""

Expand Down
28 changes: 27 additions & 1 deletion circuit_maintenance_parser/parsers/equinix.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ def _parse_b(self, b_elements, data):
impact = Impact.OUTAGE
elif "Loss of redundancy" in impact_line:
impact = Impact.REDUCED_REDUNDANCY
elif "Traffic will be re-routed" in impact_line:
impact = Impact.REDUCED_REDUNDANCY
return impact

def _parse_table(self, theader_elements, data, impact): # pylint: disable=no-self-use
Expand All @@ -97,7 +99,31 @@ def _parse_table(self, theader_elements, data, impact): # pylint: disable=no-se
continue
circuit_info = list(tr_elem.find_all("td"))
if circuit_info:
account, _, circuit = circuit_info # pylint: disable=unused-variable
if len(circuit_info) == 4:
# Equinix Connect notifications contain the IBX name
account, _, _, circuit = circuit_info # pylint: disable=unused-variable
elif len(circuit_info) == 14:
# Equinix Fabric notifications include a lot of additional detail on seller and subscriber ID's
(
account,
_,
_,
circuit,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
) = circuit_info # pylint: disable=unused-variable
elif len(circuit_info) == 3:
account, _, circuit = circuit_info # pylint: disable=unused-variable
else:
return
data["circuits"].append(
{
"circuit_id": circuit.text,
Expand Down
2 changes: 1 addition & 1 deletion circuit_maintenance_parser/parsers/verizon.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def parse_tables(self, tables: ResultSet, data: Dict): # pylint: disable=too-ma
data["start"] = self.dt2ts(start)
data["end"] = self.dt2ts(end)

for row in circuit_table.find("tbody").find_all("tr"):
for row in circuit_table.find_all("tr"):
cells = row.find_all("td")
cells_text = [cell.string.strip() for cell in cells if cell.string]
if not cells_text or cells_text[0].startswith("Company Name"):
Expand Down
21 changes: 15 additions & 6 deletions circuit_maintenance_parser/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import traceback

from typing import Iterable, List, Dict
import chardet

from pydantic import BaseModel

Expand All @@ -19,7 +20,7 @@
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
from circuit_maintenance_parser.parsers.cogent import HtmlParserCogent1
from circuit_maintenance_parser.parsers.cogent import HtmlParserCogent1, TextParserCogent1, SubjectParserCogent1
from circuit_maintenance_parser.parsers.colt import CsvParserColt1, SubjectParserColt1, SubjectParserColt2
from circuit_maintenance_parser.parsers.equinix import HtmlParserEquinix, SubjectParserEquinix
from circuit_maintenance_parser.parsers.gtt import HtmlParserGTT1
Expand Down Expand Up @@ -95,7 +96,8 @@ def filter_check(filter_dict: Dict, data: NotificationData, filter_type: str) ->
if filter_data_type not in filter_dict:
continue

data_part_content = data_part.content.decode().replace("\r", "").replace("\n", "")
data_part_encoding = chardet.detect(data_part.content).get("encoding", "utf-8")
data_part_content = data_part.content.decode(data_part_encoding).replace("\r", "").replace("\n", "")
if any(re.search(filter_re, data_part_content) for filter_re in filter_dict[filter_data_type]):
logger.debug("Matching %s filter expression for %s.", filter_type, data_part_content)
return True
Expand Down Expand Up @@ -172,6 +174,14 @@ class AquaComms(GenericProvider):
_default_organizer = "tickets@aquacomms.com"


class Arelion(GenericProvider):
"""Arelion (formerly Telia Carrier) provider custom class."""

_exclude_filter = {EMAIL_HEADER_SUBJECT: ["Disturbance Information"]}

_default_organizer = "support@arelion.com"


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

Expand All @@ -195,6 +205,7 @@ class Cogent(GenericProvider):

_processors: List[GenericProcessor] = [
CombinedProcessor(data_parsers=[EmailDateParser, HtmlParserCogent1]),
CombinedProcessor(data_parsers=[EmailDateParser, TextParserCogent1, SubjectParserCogent1]),
]
_default_organizer = "support@cogentco.com"

Expand Down Expand Up @@ -308,12 +319,10 @@ class Sparkle(GenericProvider):
_default_organizer = "TISAmericaNOC@tisparkle.com"


class Telia(GenericProvider):
class Telia(Arelion):
"""Telia provider custom class."""

_exclude_filter = {EMAIL_HEADER_SUBJECT: ["Disturbance Information"]}

_default_organizer = "carrier-csc@teliacompany.com"
# Kept for compatibility purposes, but Telia is renamed Arelion


class Telstra(GenericProvider):
Expand Down
Loading

0 comments on commit 322d821

Please sign in to comment.