跳转到主要内容

Python库,用于解析电路维护通知并返回结构化数据

项目描述

circuit-maintenance-parser

circuit-maintenance-parser 是一个Python库,用于解析网络服务提供商 (NSP) 的电路维护通知,将异构格式转换为定义良好的结构化格式。

上下文

每个网络都依赖于由网络服务提供商 (NSP) 提供的外部电路,这些电路将它们连接到互联网、办公室分支或外部服务提供商(如公共云)。

显然,这些服务有时需要操作窗口进行升级或修复相关问题,通常以 电路维护期 的形式出现。NSP 通常会通知客户即将发生的事件,以便客户可以采取行动,以最大限度地减少对相关电路常规使用的影响。

许多客户面临的一个挑战是,几乎每个 NSP 都定义了自己的维护通知格式,尽管最终相关信息在 NSP 之间大多是相同的。该库旨在解析来自多个提供商的通知格式,并始终返回相同的对象结构,这将使其更容易后续处理。

本输出格式的定义遵循在NANOG会议期间提出的BCOP,旨在推广iCalendar格式的使用。实际上,如果NSP使用所提出的iCalendar格式,解析器将是直接的,无需定义自定义逻辑,但这个库能够支持不使用此建议做法的其他提供者,以获得相同的结果。

您可以在自动化框架中利用这个库来处理电路维护通知,并使用标准化的Maintenance模型以简单的方式处理收到的电路维护通知。每个Maintenance对象包含以下属性

  • provider:识别维护通知主题服务的提供者。
  • account:识别与维护通知主题服务关联的账户。
  • maintenance_id:包含唯一标识维护(至少在特定提供者范围内)的文本。
  • circuits:受维护通知影响的电路及其具体影响列表。请注意,在维护取消或完成的通知中,一些提供者省略了电路列表,因此对于状态为已取消或完成的维护通知,此列表可能为空。
  • start:定义维护开始日期/时间的GMT时间戳。
  • end:定义维护结束日期/时间的GMT时间戳。
  • stamp:定义维护更新日期/时间的GMT时间戳。
  • organizer:定义原始通知中包含的联系方式。
  • status:定义维护的整体状态或确认。¹
  • summary:关于此维护通知的文本描述。可能是一个空字符串。
  • sequence:涉及此维护窗口的通知的序列号。实际上,这通常与stamp字段冗余,对于大多数非iCalendar解析通知,默认为1。²
  • uid:为相关通知线程的唯一标识符。实际上,这通常与maintenance_id字段冗余,对于大多数非iCalendar解析通知,默认为0。³

请参阅BCOP以了解更多关于这些属性标准化含义的细节。

¹ 根据BCOP,statusX-MAINTNOTE_STATUS)是iCalendar通知的可选字段。但是,Maintenance对象将始终包含一个status值;在iCalendar通知省略此字段的情况下,status将被设置为"NO-CHANGE",如何适当地处理这种情况取决于该库的使用者。其他通知格式的解析器负责根据通知内容设置此字段的适当值,并且可能包括或不包括"NO-CHANGE"作为可能报告的值之一。

² 根据BCOP,sequence是iCalendar通知的强制字段。但是,一些NSP已发送省略SEQUENCE字段的通知,尽管在其他方面与BCOP一致;在这种情况下,该库将报告序列号为-1

工作流程

  1. 我们通过直接或通过init_provider方法实例化一个Provider,根据所选类型返回相应的实例。
  2. 获取NotificationData类的实例。此实例将包含一些内容及其特定类型的DataParts组合在一起(这些类型将与特定的Parser匹配)。例如,一个NotificationData可能描述接收到的电子邮件消息,其中DataParts对应于电子邮件的主题行和正文。存在工厂方法来初始化描述单个二进制数据块的NotificationData,以及直接从原始电子邮件消息或email.message.EmailMessage实例初始化的方法。
  3. 每个Provider使用一个或多个Processors,当调用Provider.get_maintenances(data)方法时,这些Processors将用于构建Maintenances
  4. 每个Processor类使用一个或多个Parsers来处理其处理的每种类型的数据。它可以具有自定义逻辑来组合来自多个Parsers的解析数据,以创建最终的Maintenance对象。
  5. 每个Parser类支持一个或一组相关的数据类型,并实现了用于检索包含相关键/值的DictParser.parse()方法。

默认情况下,有一个支持使用标准ICal ParserSimpleProcessorGenericProvider,这对于提供商使用参考iCalendar标准的情况来说是最简单的使用库的方法。

支持的提供商

支持使用BCOP标准的提供商

  • Arelion(以前称为Telia)
  • EuNetworks
  • EXA(以前称为GTT)(*)
  • NTT
  • PacketFabric
  • Telstra (*)

基于其他解析器的支持提供商

  • AWS
  • AquaComms
  • BSO
  • Cogent
  • Colt
  • Crown Castle Fiber
  • Equinix
  • EXA(以前称为GTT)(*)
  • HGC
  • Global Cloud Xchange
  • Google
  • Lumen
  • Megaport
  • Momentum
  • Netflix(仅限AS2906)
  • Seaborn
  • Sparkle
  • Telstra (*)
  • Turkcell
  • Verizon
  • Windstream
  • Zayo

(*) 同时列在两个列表中,使用BCOP标准和非标准解析器。

注意:由于这些提供商没有原生支持BCOP标准,可能在实现的解析器中存在一些差距,这些差距将通过新的测试用例得到完善。我们鼓励您报告相关的问题

LLM驱动的解析器

该库支持利用大型语言模型(LLM)的附加解析器选项,以便在特定解析器未成功时提供最佳努力解析。

警告:其中一些集成(如OpenAI)需要额外的安装参数。请检查附加部分

当设置了适当的环境变量(见下文)时,这些LLM解析器将自动附加到每个定义的提供商的所有现有处理器之后。

这些集成可能涉及一些API使用的费用。请谨慎使用!作为一个量级,使用OpenAI GPT gpt-3.5-turbo模型解析一封电子邮件的成本为0.004美元。

这些是目前支持的LLM集成

  • PARSER_LLM_QUESTION_STR(可选),用于覆盖默认问题的提问。请谨慎更改。它具有优先级高于PARSER_LLM_QUESTION_FILEPATH

  • PARSER_LLM_QUESTION_FILEPATH(可选),包含用于覆盖默认问题的提问的文件的路径。

  • OpenAI,以下为支持的ENVs

    • PARSER_OPENAI_API_KEY(必需):OpenAI API密钥。
    • PARSER_OPENAI_MODEL(可选):要使用的LLM模型,默认为"gpt-3.5-turbo"。

元数据

每个Maintenance都包含一个metadata属性,提供有关使用的提供商以及成功解析维护过程中使用的进程和解析器的信息。

此信息与验证《维护》的实际内容相关,因为它可能是由一个基于LLM的解析器生成的,这意味着其置信度低于使用预定义解析器。您可以通过检查Metadata.generate_by_llm布尔值来验证。

安装

此库作为Python包在pypi上可用,可以使用pip进行安装:pip install circuit-maintenance-parser

附加功能

OpenAI

pip install circuit-maintenance-parser[openai]

如何使用它?

此库需要两样东西

  • notificationdata:这是库将检查以提取维护通知的数据。它可以很简单(只有一个数据类型和内容,例如iCalendar通知)或更复杂(具有多个不同类型的数据部分,例如来自电子邮件)。
  • provider标识符:用于选择合适的Provider,其中包含用于获取适当的Parsers并使用它们提取的数据的processor逻辑。默认情况下,GenericProvider(在未定义其他提供者类型时使用)将支持使用推荐格式解析iCalendar通知。

Python库

第一步是定义我们将用于解析通知的Provider。如注释所示,存在一个实现黄金标准格式并可重用于任何符合预期的通知的GenericProvider

from circuit_maintenance_parser import init_provider

generic_provider = init_provider()

type(generic_provider)
<class 'circuit_maintenance_parser.provider.GenericProvider'>

然而,通常一些Providers并未完全实现标准,可能缺少一些信息,例如组织者电子邮件或可能需要一些自定义逻辑来组合信息,因此我们允许自定义Providers

ntt_provider = init_provider("ntt")

type(ntt_provider)
<class 'circuit_maintenance_parser.provider.NTT'>

一旦我们有了准备好的Provider,我们需要初始化要处理的数据,我们称之为NotificationData,它可以是从简单的内容和类型初始化,也可以是从更复杂结构,例如电子邮件初始化。

from circuit_maintenance_parser import NotificationData

raw_data = b"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Maint Note//https://github.com/maint-notification//
BEGIN:VEVENT
SUMMARY:Maint Note Example
DTSTART;VALUE=DATE-TIME:20151010T080000Z
DTEND;VALUE=DATE-TIME:20151010T100000Z
DTSTAMP;VALUE=DATE-TIME:20151010T001000Z
UID:42
SEQUENCE:1
X-MAINTNOTE-PROVIDER:example.com
X-MAINTNOTE-ACCOUNT:137.035999173
X-MAINTNOTE-MAINTENANCE-ID:WorkOrder-31415
X-MAINTNOTE-IMPACT:OUTAGE
X-MAINTNOTE-OBJECT-ID;X-MAINTNOTE-OBJECT-IMPACT=NO-IMPACT:acme-widgets-as-a-service
X-MAINTNOTE-OBJECT-ID;X-MAINTNOTE-OBJECT-IMPACT=OUTAGE:acme-widgets-as-a-service-2
X-MAINTNOTE-STATUS:TENTATIVE
ORGANIZER;CN="Example NOC":mailto:noone@example.com
END:VEVENT
END:VCALENDAR
"""

data_to_process = NotificationData.init_from_raw("ical", raw_data)

type(data_to_process)
<class 'circuit_maintenance_parser.data.NotificationData'>

最后,我们从数据中检索维护(它是一个List,因为通知可以包含多个维护)通过调用Provider实例的get_maintenances方法。

maintenances = generic_provider.get_maintenances(data_to_process)

print(maintenances[0].to_json())
{
"account": "137.035999173",
"circuits": [
{
"circuit_id": "acme-widgets-as-a-service",
"impact": "NO-IMPACT"
},
{
"circuit_id": "acme-widgets-as-a-service-2",
"impact": "OUTAGE"
}
],
"end": 1444471200,
"maintenance_id": "WorkOrder-31415",
"organizer": "mailto:noone@example.com",
"provider": "example.com",
"sequence": 1,
"stamp": 1444435800,
"start": 1444464000,
"status": "TENTATIVE",
"summary": "Maint Note Example",
"uid": "42"
}

注意,无论是使用GenericProvider还是NTT提供者,从相同的数据我们都得到相同的结果,因为它们使用的是完全相同的ProcessorParser。唯一的区别是NTT通知中没有organizerprovider,这些信息用一些默认值填充到Provider中,但在这种情况下,原始通知包含所有必要的信息,因此不使用默认值。

ntt_maintenances = ntt_provider.get_maintenances(data_to_process)
assert maintenances_ntt == maintenances

每个维护都包含metadata属性,以了解如何解析。

print(maintenances[0].metadata)
provider='genericprovider' processor="SimpleProcessor" parsers=["ICal"], generated_by_llm=False

CLI

还有一个名为circuit-maintenance-parsercli入口点,它通过几个参数提供对库的简单访问。

  • data-file:存储通知的文件。
  • data-typeicalhtmlemail,取决于数据类型。
  • provider-type:选择正确的Provider。如果为空,则使用GenericProvider
circuit-maintenance-parser --data-file "/tmp/___ZAYO TTN-00000000 Planned MAINTENANCE NOTIFICATION___.eml" --data-type email --provider-type zayo
Circuit Maintenance Notification #0
{
  "account": "some account",
  "circuits": [
    {
      "circuit_id": "/OGYX/000000/ /ZYO /",
      "impact": "OUTAGE"
    }
  ],
  "end": 1601035200,
  "maintenance_id": "TTN-00000000",
  "organizer": "mr@zayo.com",
  "provider": "zayo",
  "sequence": 1,
  "stamp": 1599436800,
  "start": 1601017200,
  "status": "CONFIRMED",
  "summary": "Zayo will implement planned maintenance to troubleshoot and restore degraded span",
  "uid": "0"
}

如何扩展库?

尽管此库旨在包含尽可能多的提供者支持,但可能并非所有成千上万的NSP都受支持,您可能需要添加对某些新提供者的支持。添加新的Provider非常简单,以下示例中我们为使用HTML通知的虚构提供者ABCDE添加了支持。

第一步是创建一个新的文件:circuit_maintenance_parser/parsers/abcde.py。这个文件将包含所有为服务提供商所需的自定义解析器,并且它将导入来自circuit_maintenance_parser.parser的每个解析器类型的基类。在示例中,我们只需要导入Html,并在子类中实现类所需的方法,在这个例子中是parse_html(),它将返回一个包含这个Parser可以提取的所有数据的dict。在这种情况下,我们有两个辅助方法,_parse_bs_parse_tables,它们实现了遍历通知数据的逻辑。

from typing import Dict
import bs4  # type: ignore
from bs4.element import ResultSet  # type: ignore
from circuit_maintenance_parser.parser import Html

class HtmlParserABCDE1(Html):
    def parse_html(self, soup: ResultSet) -> Dict:
        data = {}
        self._parse_bs(soup.find_all("b"), data)
        self._parse_tables(soup.find_all("table"), data)
        return [data]

    def _parse_bs(self, btags: ResultSet, data: Dict):
      ...

    def _parse_tables(self, tables: ResultSet, data: Dict):
      ...

下一步是在circuit_maintenance_parser/provider.py中创建新的Provider,通过定义一个新的类。这个从GenericProvider继承的类只需要定义两个属性

  • _processors:是一个Processor实例的列表,使用多个数据Parsers。在这个例子中,我们不需要创建一个新的自定义Processor,因为组合逻辑很好(最可能的情况),我们只需要使用新定义的HtmlParserABCDE1和提取电子邮件日期的通用EmailDateParser。请注意,您可以在列表中有多个具有不同ParsersProcessors,支持多种格式。
  • _default_organizer:这是一个默认的辅助工具,用于在原始通知不包含信息时填充Maintenance中的organizer属性。
class ABCDE(GenericProvider):
    _processors: List[GenericProcessor] = [
        CombinedProcessor(data_parsers=[EmailDateParser, HtmlParserABCDE1]),
    ]
    _default_organizer = "noc@abcde.com"

然后在circuit_maintenance_parser/__init__.py中公开新的Provider

from .provider import (
    GenericProvider,
    ABCDE,
    ...
)

SUPPORTED_PROVIDERS = (
    GenericProvider,
    ABCDE,
    ...
)

最后,但同样重要的是,您应该更新测试!

  • tests/unit/test_parsers.py中测试新的Parser
  • tests/unit/test_e2e.py中测试新的Provider逻辑

...在tests/unit/data/abcde/中添加必要的数据样本。

您可以使用invoke anonymize-ips --local匿名化您的IPv4和IPv6地址。请注意,仅保留IPv4地址用于文档目的(RFC5737:“192.0.2.0/24”,“198.51.100.0/24”,“203.0.113.0/24”),以防您需要在测试输出中检查这些IP(不太可能)。

贡献

欢迎Pull requests,并自动通过Travis CI构建和测试多个版本的Python。

项目遵循Network to Code软件开发指南,并利用

  • Black,Pylint,Mypy,Bandit和pydocstyle进行Python代码审查和格式化。
  • 单元和集成测试以确保库正常运行。

本地开发

需求

  • 安装poetry
  • 安装本地依赖和库:poetry install
  • 在本地运行CI测试:invoke tests --local

如何添加新的电路维护提供者?

  1. 定义Parsers(从一些通用的Parsers继承或创建一个新的)来从通知中提取数据,通知可能包含多个DataParts。必须匹配ParserDataPartdata_type。自定义Parsers将放在parsers文件夹中。
  2. 更新unit/test_parsers.py,添加新的解析器,提供一些数据来测试和验证提取的数据。
  3. 定义一个新的从GenericProvider继承的Provider,定义要使用的Processors和相应的Parsers。您可能可以重用一些通用的Processors,或者可能需要创建一个自定义的。如果是这种情况,将其放在processors文件夹中。
    • Provider还支持定义一个_include_filter和一个_exclude_filter来限制实际处理的通告,避免对不相关的通告产生假阳性错误。
  4. unit/test_e2e.py中更新新的提供者,提供一些数据来测试和验证创建的最终Maintenances
  5. 公开新的Provider,更新circuit_maintenance_parser/__init__.py中的映射SUPPORTED_PROVIDERS,以正式公开Provider
  6. 您可以在这里运行一些测试以验证您的新单元测试不会对现有测试造成问题,并且通常按预期工作。您可以通过运行pytest --log-cli-level=DEBUG --capture=tee-sys来完成此操作。您可以使用-k标志来缩小您想要执行的测试。如果成功,您的结果应类似于以下内容
-> % pytest --log-cli-level=DEBUG --capture=tee-sys -k test_parsers
...omitted debug logs...
====================================================== 99 passed, 174 deselected, 17 warnings in 10.35s ======================================================
  1. 在本地运行一些最终CI测试,以确保您的更改没有出现linting/格式化问题。您应该努力达到10/10的代码评分。请参考以下示例:invoke tests --local
-> % invoke tests --local
LOCAL - Running command black --check --diff .
All done! ✨ 🍰 ✨
41 files would be left unchanged.
LOCAL - Running command flake8 .
LOCAL - Running command find . -name "*.py" | xargs pylint
************* Module tasks
tasks.py:4:0: W0402: Uses of a deprecated module 'distutils.util' (deprecated-module)

--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)

如何在本地调试circuit-maintenance-parser库

  1. poetry install在本地更新库及其依赖项。
  2. circuit-maintenance-parser现在已使用您最近的本地更改构建。

如果您要向其中一个类添加记录器或调试器

class HtmlParserZayo1(Html):
    def parse_bs(self, btags: ResultSet, data: dict):
        """Parse B tag."""
        raise Exception('Debugging exception')

运行poetry install之后

-> % circuit-maintenance-parser --data-file ~/Downloads/zayo.eml --data-type email --provider-type zayo
Provider processing failed: Failed creating Maintenance notification for Zayo.
Details:
- Processor CombinedProcessor from Zayo failed due to: Debugging exception

注意:由于没有Dockerfile,将出现invoke build错误。这是预期的,因为库运行简单的pytest测试,不使用容器。

-> % invoke build
Building image circuit-maintenance-parser:2.2.2-py3.8
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 2B done
#1 DONE 0.0s
WARNING: failed to get git remote url: fatal: No remote configured to list refs from.
ERROR: failed to solve: rpc error: code = Unknown desc = failed to solve with frontend dockerfile.v0: failed to read dockerfile: open /var/lib/docker/tmp/buildkit-mount1243547759/Dockerfile: no such file or directory

问题

对于任何问题或评论,请首先查看常见问题解答,并随时访问Network to Code的Slack频道(频道#networktocode)。在此注册

许可说明

此库使用由Pareto Software, LLC拥有的Basic World Cities Database:提供者免费提供Basic World Cities Database。此数据库根据在以下网址描述的Creative Commons Attribution 4.0许可证进行许可:https://creativecommons.org/licenses/by/4.0/

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源分发

circuit_maintenance_parser-2.6.1.tar.gz (1.5 MB 查看哈希值

上传时间

构建分发

circuit_maintenance_parser-2.6.1-py3-none-any.whl (1.5 MB 查看哈希值

上传时间 Python 3

支持