Skip to content

Commit

Permalink
Issue nautobot#210: Ability to parse multiple maintenance windows fro…
Browse files Browse the repository at this point in the history
…m Zayo maintenance notification (nautobot#214)

* Add multiple windows to Zayo maintenances

* Updated unit test cases for Zayo Parser

* Fixed CI/black job: replace single quotes with double quotes

* Fixed CI/black job: double spaces in between classes

* Fixed pydocstyle job: No blank lines allowed after function docstring

---------

Co-authored-by: mkekez <[email protected]>
  • Loading branch information
csessh and mkekez authored Mar 10, 2023
1 parent 5a96986 commit 7fc8181
Show file tree
Hide file tree
Showing 12 changed files with 392 additions and 61 deletions.
65 changes: 30 additions & 35 deletions circuit_maintenance_parser/parsers/zayo.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Zayo parser."""
import logging
import re
from copy import deepcopy
from typing import Dict

import bs4 # type: ignore
Expand Down Expand Up @@ -44,21 +45,30 @@ class HtmlParserZayo1(Html):

def parse_html(self, soup):
"""Execute parsing."""
maintenances = []
data = {}
self.parse_bs(soup.find_all("b"), data)
self.parse_tables(soup.find_all("table"), data)

if data:
if "status" not in data:
text = soup.get_text()
if "will be commencing momentarily" in text:
data["status"] = Status("IN-PROCESS")
elif "has been completed" in text or "has closed" in text:
data["status"] = Status("COMPLETED")
elif "has rescheduled" in text:
data["status"] = Status("RE-SCHEDULED")
if not data:
return [{}]

return [data]
if "status" not in data:
text = soup.get_text()
if "will be commencing momentarily" in text:
data["status"] = Status("IN-PROCESS")
elif "has been completed" in text or "has closed" in text:
data["status"] = Status("COMPLETED")
elif "has rescheduled" in text:
data["status"] = Status("RE-SCHEDULED")

for maintenance_window in data.get("windows", []):
maintenance = deepcopy(data)
maintenance["start"], maintenance["end"] = maintenance_window
del maintenance["windows"]
maintenances.append(maintenance)

return maintenances

def parse_bs(self, btags: ResultSet, data: dict):
"""Parse B tag."""
Expand All @@ -71,41 +81,23 @@ def parse_bs(self, btags: ResultSet, data: dict):
data["status"] = Status("CONFIRMED")
elif "has cancelled" in line.text.lower():
data["status"] = Status("CANCELLED")
# Some Zayo notifications may include multiple activity dates.
# For lack of a better way to handle this, we consolidate these into a single extended activity range.
#
# For example, given:
#
# 1st Activity Date
# 01-Nov-2021 00:01 to 01-Nov-2021 05:00 ( Mountain )
# 01-Nov-2021 06:01 to 01-Nov-2021 11:00 ( GMT )
#
# 2nd Activity Date
# 02-Nov-2021 00:01 to 02-Nov-2021 05:00 ( Mountain )
# 02-Nov-2021 06:01 to 02-Nov-2021 11:00 ( GMT )
#
# 3rd Activity Date
# 03-Nov-2021 00:01 to 03-Nov-2021 05:00 ( Mountain )
# 03-Nov-2021 06:01 to 03-Nov-2021 11:00 ( GMT )
#
# our end result would be (start: "01-Nov-2021 06:01", end: "03-Nov-2021 11:00")
elif "activity date" in line.text.lower():
logger.info("Found 'activity date': %s", line.text)

if "windows" not in data:
data["windows"] = []

for sibling in line.next_siblings:
text = sibling.text if isinstance(sibling, bs4.element.Tag) else sibling
logger.debug("Checking for GMT date/timestamp in sibling: %s", text)

if "( GMT )" in text:
window = self.clean_line(sibling).strip("( GMT )").split(" to ")
start = parser.parse(window.pop(0))
start_ts = self.dt2ts(start)
# Keep the earliest of any listed start times
if "start" not in data or data["start"] > start_ts:
data["start"] = start_ts
end = parser.parse(window.pop(0))
start_ts = self.dt2ts(start)
end_ts = self.dt2ts(end)
# Keep the latest of any listed end times
if "end" not in data or data["end"] < end_ts:
data["end"] = end_ts
data["windows"].append((start_ts, end_ts))
break
elif line.text.lower().strip().startswith("reason for maintenance:"):
data["summary"] = self.clean_line(line.next_sibling)
Expand Down Expand Up @@ -148,13 +140,15 @@ def parse_tables(self, tables: ResultSet, data: Dict):
"Customer Circuit ID",
],
)

if all(table_headers != expected_headers for expected_headers in expected_headers_ref):
logger.warning("Table headers are not as expected: %s", head_row)
continue

data_rows = table.find_all("td")
if len(data_rows) % 5 != 0:
raise AssertionError("Table format is not correct")

number_of_circuits = int(len(data_rows) / 5)
for idx in range(number_of_circuits):
data_circuit = {}
Expand All @@ -165,5 +159,6 @@ def parse_tables(self, tables: ResultSet, data: Dict):
elif "no expected impact" in impact.lower():
data_circuit["impact"] = Impact("NO-IMPACT")
circuits.append(CircuitImpact(**data_circuit))

if circuits:
data["circuits"] = circuits
40 changes: 37 additions & 3 deletions tests/unit/data/zayo/zayo5_html_parser_result.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
[
{
"maintenance_id": "TTN-0003456789",
"summary": "Routine Fiber splice - NO Impact is Expected to your services. This notification is to advise you that we will be entering a splice case that houses live traffic.",
"circuits": [
{
"circuit_id": "/OGYX/123456/ /ZYO /",
Expand All @@ -10,10 +12,42 @@
"impact": "NO-IMPACT"
}
],
"end": 1635937200,
"maintenance_id": "TTN-0003456789",
"status": "IN-PROCESS",
"start": 1635746460,
"end": 1635764400
},
{
"maintenance_id": "TTN-0003456789",
"summary": "Routine Fiber splice - NO Impact is Expected to your services. This notification is to advise you that we will be entering a splice case that houses live traffic.",
"circuits": [
{
"circuit_id": "/OGYX/123456/ /ZYO /",
"impact": "NO-IMPACT"
},
{
"circuit_id": "/OGYX/234567/ /ZYO /",
"impact": "NO-IMPACT"
}
],
"status": "IN-PROCESS",
"start": 1635832860,
"end": 1635850800
},
{
"maintenance_id": "TTN-0003456789",
"summary": "Routine Fiber splice - NO Impact is Expected to your services. This notification is to advise you that we will be entering a splice case that houses live traffic.",
"circuits": [
{
"circuit_id": "/OGYX/123456/ /ZYO /",
"impact": "NO-IMPACT"
},
{
"circuit_id": "/OGYX/234567/ /ZYO /",
"impact": "NO-IMPACT"
}
],
"status": "IN-PROCESS",
"summary": "Routine Fiber splice - NO Impact is Expected to your services. This notification is to advise you that we will be entering a splice case that houses live traffic."
"start": 1635919260,
"end": 1635937200
}
]
40 changes: 39 additions & 1 deletion tests/unit/data/zayo/zayo5_result.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,49 @@
"impact": "NO-IMPACT"
}
],
"end": 1635937200,
"end": 1635764400,
"maintenance_id": "TTN-0003456789",
"stamp": 1635918838,
"status": "IN-PROCESS",
"start": 1635746460,
"summary": "Routine Fiber splice - NO Impact is Expected to your services. This notification is to advise you that we will be entering a splice case that houses live traffic."
},
{
"account": "Some Customer Inc",
"circuits": [
{
"circuit_id": "/OGYX/123456/ /ZYO /",
"impact": "NO-IMPACT"
},
{
"circuit_id": "/OGYX/234567/ /ZYO /",
"impact": "NO-IMPACT"
}
],
"end": 1635850800,
"maintenance_id": "TTN-0003456789",
"stamp": 1635918838,
"status": "IN-PROCESS",
"start": 1635832860,
"summary": "Routine Fiber splice - NO Impact is Expected to your services. This notification is to advise you that we will be entering a splice case that houses live traffic."
},
{
"account": "Some Customer Inc",
"circuits": [
{
"circuit_id": "/OGYX/123456/ /ZYO /",
"impact": "NO-IMPACT"
},
{
"circuit_id": "/OGYX/234567/ /ZYO /",
"impact": "NO-IMPACT"
}
],
"end": 1635937200,
"maintenance_id": "TTN-0003456789",
"stamp": 1635918838,
"status": "IN-PROCESS",
"start": 1635919260,
"summary": "Routine Fiber splice - NO Impact is Expected to your services. This notification is to advise you that we will be entering a splice case that houses live traffic."
}
]
44 changes: 39 additions & 5 deletions tests/unit/data/zayo/zayo6_html_parser_result.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,53 @@
[
{
"maintenance_id": "TTN-0004567890",
"summary": "Routine Fiber splice - NO Impact is Expected to your services. This notification is to advise you that we will be entering a splice case that houses live traffic.",
"circuits": [
{
"circuit_id": "/OGYX/123418/ /ZYO /",
"impact": "NO-IMPACT"
},
{
"circuit_id": "/OGYX/123408/ /ZYO /",
"circuit_id":"/OGYX/123408/ /ZYO /",
"impact": "NO-IMPACT"
}
],
"end": 1635937200,
"maintenance_id": "TTN-0004567890",
"status": "COMPLETED",
"start": 1635746460,
"end": 1635764400
},
{
"maintenance_id": "TTN-0004567890",
"summary": "Routine Fiber splice - NO Impact is Expected to your services. This notification is to advise you that we will be entering a splice case that houses live traffic.",
"circuits": [
{
"circuit_id": "/OGYX/123418/ /ZYO /",
"impact": "NO-IMPACT"
},
{
"circuit_id":"/OGYX/123408/ /ZYO /",
"impact": "NO-IMPACT"
}
],
"status": "COMPLETED",
"start": 1635832860,
"end": 1635850800
},
{
"maintenance_id": "TTN-0004567890",
"summary": "Routine Fiber splice - NO Impact is Expected to your services. This notification is to advise you that we will be entering a splice case that houses live traffic.",
"circuits": [
{
"circuit_id": "/OGYX/123418/ /ZYO /",
"impact": "NO-IMPACT"
},
{
"circuit_id":"/OGYX/123408/ /ZYO /",
"impact": "NO-IMPACT"
}
],
"status": "COMPLETED",
"summary": "Routine Fiber splice - NO Impact is Expected to your services. This notification is to advise you that we will be entering a splice case that houses live traffic."
"start": 1635919260,
"end": 1635937200
}
]
]
44 changes: 41 additions & 3 deletions tests/unit/data/zayo/zayo6_result.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,53 @@
"impact": "NO-IMPACT"
},
{
"circuit_id": "/OGYX/123408/ /ZYO /",
"circuit_id":"/OGYX/123408/ /ZYO /",
"impact": "NO-IMPACT"
}
],
"end": 1635937200,
"end": 1635764400,
"maintenance_id": "TTN-0004567890",
"stamp": 1635936668,
"status": "COMPLETED",
"start": 1635746460,
"summary": "Routine Fiber splice - NO Impact is Expected to your services. This notification is to advise you that we will be entering a splice case that houses live traffic."
},
{
"account": "Some Customer Inc",
"circuits": [
{
"circuit_id": "/OGYX/123418/ /ZYO /",
"impact": "NO-IMPACT"
},
{
"circuit_id":"/OGYX/123408/ /ZYO /",
"impact": "NO-IMPACT"
}
],
"end": 1635850800,
"maintenance_id": "TTN-0004567890",
"stamp": 1635936668,
"status": "COMPLETED",
"start": 1635832860,
"summary": "Routine Fiber splice - NO Impact is Expected to your services. This notification is to advise you that we will be entering a splice case that houses live traffic."
},
{
"account": "Some Customer Inc",
"circuits": [
{
"circuit_id": "/OGYX/123418/ /ZYO /",
"impact": "NO-IMPACT"
},
{
"circuit_id":"/OGYX/123408/ /ZYO /",
"impact": "NO-IMPACT"
}
],
"end": 1635937200,
"maintenance_id": "TTN-0004567890",
"stamp": 1635936668,
"status": "COMPLETED",
"start": 1635919260,
"summary": "Routine Fiber splice - NO Impact is Expected to your services. This notification is to advise you that we will be entering a splice case that houses live traffic."
}
]
]
30 changes: 28 additions & 2 deletions tests/unit/data/zayo/zayo7_html_parser_result.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,36 @@
"impact": "OUTAGE"
}
],
"end": 1637067600,
"end": 1636894800,
"maintenance_id": "TTN-0005432100",
"start": 1636876860,
"status": "COMPLETED",
"summary": "Zayo will implement maintenance to repair damaged fiber splice case, to prevent unplanned outages"
},
{
"circuits": [
{
"circuit_id": "/IPYX/100722/ /ZYO /",
"impact": "OUTAGE"
}
],
"end": 1636981200,
"maintenance_id": "TTN-0005432100",
"start": 1636963260,
"status": "COMPLETED",
"summary": "Zayo will implement maintenance to repair damaged fiber splice case, to prevent unplanned outages"
},
{
"circuits": [
{
"circuit_id": "/IPYX/100722/ /ZYO /",
"impact": "OUTAGE"
}
],
"end": 1637067600,
"maintenance_id": "TTN-0005432100",
"start": 1637049660,
"status": "COMPLETED",
"summary": "Zayo will implement maintenance to repair damaged fiber splice case, to prevent unplanned outages"
}
]
]
Loading

0 comments on commit 7fc8181

Please sign in to comment.