diff --git a/.coverage b/.coverage new file mode 100644 index 0000000..4885fb4 Binary files /dev/null and b/.coverage differ diff --git a/.env b/.env new file mode 100644 index 0000000..64b4596 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +PYTHONPATH=${workspaceDir}/lib \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d1f3966 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 DingYi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f53f526 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# Changelist +* 1.0.0, first release + +# Feedback + +give your feedback by following ways
+ +* visit https://github.com/dvdface/fiberhome-oltcli (preferred) + +* send email to dvdface@gmail.com + + +# How to install + +`pip install fiberhome-oltcli` + +# Known issues + +None
+ + +# Overview + +With *fiberhome-oltcli* library, you can easily access olt's commandline interface.
+ +# How to use + +* **Chassis class
** + + ``` + # connect testcenter with ip 10.182.32.138 , without reserving any port + # creating chassis class will auto connect chassis + chassis = Chassis('10.182.32.138') + + # disconnect chassis + chassis.disconnect() + + # apply changes ( it will apply automatically) + chassis.apply() + + # connect testcenter and reserve port + chassis = Chassis('10.182.32.138', [{ 'location' : '//10.182.32.138/1/1', 'vid': None}, { 'location' : '//10.182.32.138/1/2', 'vid': None}]) + + # connect testcenter, reserve port and specify a default vlan with specified vid + # when you create a device under the port, it will insert a vlan layer with vid 100 for you + # when you create a streamblock, it will insert a vlan layer with vid 100 for you too + chassis = Chassis('10.182.32.138', [{ 'location' : '//10.182.32.138/1/1', 'vid': 100}]) + + # save xml file + chassis.save('test_configuration.xml') + + # get chassis serial number + chassis.serial + + # get chassis ip + chassis.ip + ``` diff --git a/oltcli/__init__.py b/oltcli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oltcli/aw/__init__.py b/oltcli/aw/__init__.py new file mode 100644 index 0000000..a1df66b --- /dev/null +++ b/oltcli/aw/__init__.py @@ -0,0 +1,2 @@ +from .olt import * +from .onu import * diff --git a/oltcli/aw/olt.py b/oltcli/aw/olt.py new file mode 100644 index 0000000..aa8027f --- /dev/null +++ b/oltcli/aw/olt.py @@ -0,0 +1,148 @@ +''' +Action words used to configure olt-related settigns +''' +from typing import NoReturn, Union + +from ..utils import dotdict, break_frame_slot_port, validate_type +from ..cli import OLTCLI +from ..cli.common import AuthMode + +def add_port_vlan(olt_dev:dotdict, port:str, vid:Union[str, int], strip:bool=False) -> NoReturn: + """add port vlan in uplink port + + Args: + olt_dev (dotdict): olt want to set + port (str): 端口号,形式为frameid/slotid/portid + vid (str or int): VID, 支持1000 - 2000的配置,也支持单个的vid, 如 1000 + strip (bool, optional): 是否剥离tag,默认False,不剥离。True,剥离。 + """ + validate_type('strip', strip, bool) + + cli = OLTCLI.get(olt_dev) + frame, slot, port = break_frame_slot_port(port) + vid = str(vid).replace('-', ' to ') + tag = 'tag' if strip == False else 'untag' + + cli.del_port_vlan(vid, slot, port) + cli.set_port_vlan(vid, tag, slot, port) + +def del_port_vlan(olt_dev:dotdict, port:str, vid:Union[str, int]) -> NoReturn: + """删除 VLAN添加端口 + + Args: + olt_dev (dotdict): 要操作的OLT + port (str): 端口号,形式为frameid/slotid/portid + vid (str or int): VID, 支持1000 - 2000的配置,也支持单个的vid, 如 1000 + """ + cli = OLTCLI.get(olt_dev) + frame, slot, port = break_frame_slot_port(port) + vid = str(vid).replace('-', ' to ') + + cli.del_port_vlan(vid, slot, port) + +def set_igmp_vlan(olt_dev:dotdict, vid:int) -> NoReturn: + """配置组播VLAN + + Args: + olt_dev (dotdict): 要操作的OLT + vid (int): 要配置的组播VLAN + """ + cli = OLTCLI.get(olt_dev) + cli.set_igmp_vlan(vid) + +def set_igmp_mode(olt_dev:dotdict, mode:str) -> NoReturn: + """配置组播模式 + + Args: + olt_dev (dotdict): 要操作的OLT + mode (str): 要配置的组播模式 + """ + cli = OLTCLI.get(olt_dev) + cli.set_igmp_mode(mode) + +def add_service_vlan(olt_dev:dotdict, service_name:str, begin_vid:int, end_vid:int, service_type:str) -> NoReturn: + """配置局端外层VLAN数据 + + Args: + olt_dev (dotdict): 要操作的OLT + name (str)): 业务VLAN名称 + begin_vid (int): 业务起始vid + end_vid (int): 业务结束vid + service_type (str): 业务的类型, 仅限'cnc', 'data', 'iptv', 'ngn', 'system', 'uplinksub', 'vod', 'voip'类型 + """ + cli = OLTCLI.get(olt_dev) + cli.set_service_vlan(service_name, '%s - %s' % (begin_vid, end_vid), service_type) + +def del_service_vlan(olt_dev:dotdict, service_name:str) -> NoReturn: + """要删除的局端外层VLAN + + Args: + olt_dev (dotdict): 要操作的OLT + service_name (str): 局端外层VLAN名称 + """ + cli = OLTCLI.get(olt_dev) + cli.del_service_vlan(service_name) + +def add_static_route(olt_dev:dotdict, ip:str, mask:str, hop:str, metric:int=0) -> NoReturn: + """配置网络层静态路由 + + Args: + olt_dev (dotdict): 要操作的OLT + ip (str): 目的IP + mask (str): 目的掩码 + hop (str): 下一跳 + metric (int): 权值 + """ + cli = OLTCLI.get(olt_dev) + cli.set_static_route(hop, ip, mask, metric) + +def del_static_route(olt_dev:dotdict, ip:str, mask:str, hop:str) -> NoReturn: + """删除网络层静态路由 + + Args: + olt_dev (dotdict): 要操作的OLT + ip (str): 目的IP + mask (str): 目的掩码 + hop (str): 下一跳 + """ + cli = OLTCLI.get(olt_dev) + cli.del_static_route(hop, ip, mask, metric=None) + +def set_pon_auth_mode(olt_dev:dotdict, slot:int, port:int, mode:str) -> NoReturn: + """配置PON口认证模式 + + Args: + olt_dev (dotdict): 要操作的OLT + slot (int): 槽位号 + port (int): 端口号 + mode (str or AuthMode): 认证模式 + """ + cli = OLTCLI.get(olt_dev) + cli.set_auth_mode(slot, port, AuthMode(mode) if type(mode) == str else mode) + + +# TODO: 上下行带宽模板 + +# TODO: QinQ 模板 + +__all__ = [ + # VLAN业务 + 'add_port_vlan', + 'del_port_vlan', + + # VLAN业务-局端VLAN + 'add_service_vlan', + 'del_service_vlan', + + # 组播 + 'set_igmp_vlan', + 'set_igmp_mode', + + # 以太网基本配置 + 'add_static_route', + 'del_static_route', + + # 公共配置 + # 公共配置-Pon口 + 'set_pon_auth_mode' +] \ No newline at end of file diff --git a/oltcli/aw/onu.py b/oltcli/aw/onu.py new file mode 100644 index 0000000..b5f1b1e --- /dev/null +++ b/oltcli/aw/onu.py @@ -0,0 +1,127 @@ +''' +配置ONU的Action Word +''' +from typing import NoReturn, Union +from ..utils import validate_type, dotdict +from ..cli import OLTCLI + + +def clear_port_service(olt_dev:dotdict, onu_dev:dotdict, onu_eth:int) -> NoReturn: + """删除ONU 端口业务配置 + + Args: + olt_dev (dotdict): 要操作的OLT + onu_dev (dotdict): 要操作的ONU + onu_eth (int): 要操作的ONU网口号 + """ + cli = OLTCLI.get(olt_dev) + cli.set_onu_port_vlan_service_count(onu_dev.sn, onu_eth, 0) + +def add_port_service(olt_dev:dotdict, onu_dev:dotdict, onu_eth:int, **kargs) -> NoReturn: + """ONU 端口业务配置 + + Args: + olt_dev (dotdict): 要操作的OLT + onu_dev (dotdict): 要操作的ONU + onu_eth (int): 要操作的ONU网口号 + tls (bool, optional): 配置TLS,可选。True,使能TLS; False,禁用TLS(默认) + classification (list, optional): 业务区分,默认不区分。 + service_type (str, optional): 业务类型。unicast,单播(默认); multicast,多播 + cvlan_mode (str, optional): CVLAN模式。transparent,透传(默认);tag,打标签 + cvlan_vid (int, optional): CVLAN VID。默认,无 + cvlan_cos (int, optional): 优先级,COS。默认,无 + cvlan_tpid (int, optional): 标签协议标识。默认,33024 + isp_vlan_vid (int, optional): SVLAN VID。默认, 无 + isp_vlan_cos (int, optional): SVLAN COS。默认,无 + upstream_bandwidth_profile (str, optional): 上行带宽模板。默认, 无 + downstream_bandwidth_profile (str, optional): 下行带宽模板。默认, 无 + dataservice_bandwidth_type (): 数据业务带宽类型。默认, 系统默认 + priority_queue (int, optional): 优先级队列。默认,0 + gem_port (int, optional): GEM Port。默认,0 + enable_translate (bool, optional): 使能翻译状态。默认,False + translate_vid (int, optional): 翻译VID + translate_cos (int, optional): 翻译优先级 + translate_tpid (int, optional): 翻译TPID + enable_qinq (bool, optional): 使能QinQ状态 + qinq_profile (str, optional): QinQ模板 + service_vlan_name (str, optional): 业务VLAN名称 + svlan_vid (int, optional): 业务VLAN ID + svlan_cos (int, optional): 业务VLAN 优先级 + svlan_tpid (int, optional): 业务VLAN TPID。默认,33024 + + Returns: + int : 所配置业务的索引号 + """ + cli = OLTCLI.get(olt_dev) + + service_count = cli.get_onu_port_vlan_service_count(onu_dev.sn, onu_eth) + cli.set_onu_port_vlan_service_count(onu_dev.sn, onu_eth, service_count + 1) + service_index = service_count + 1 + + + # 配置TLS + if 'tls' in kargs.keys(): + validate_type('tls', kargs['tls'], bool) + cli.set_onu_port_vlan_tls(onu_dev.sn, onu_eth, service_index, kargs['tls']) + + # 配置 业务区分 + if 'classification' in kargs.keys(): + validate_type('classification', kargs['classification'], list) + cli.set_onu_port_vlan_service_classification(onu_dev.sn, onu_eth, service_index, kargs['classification']) + + # 配置 业务类型(有就配置,没有就不配置,不配置默认一般是unicast) + if 'service_type' in kargs.keys(): + validate_type('service_type', kargs['service_type'], str) + cli.set_onu_port_vlan_service_type(onu_dev.sn, onu_eth, service_index, kargs['service_type']) + + # 配置 CVLAN模式 部分 + cvlan_mode = kargs.get('cvlan_mode', 'transparent') + cvlan_vid = kargs.get('cvlan_vid', 'null') + cvlan_cos = kargs.get('cvlan_cos', 'null') + cvlan_tpid = kargs.get('cvlan_tpid', 33024) + cli.set_onu_port_vlan_service_vlan(onu_dev.sn, onu_eth, service_index, (cvlan_mode, cvlan_cos, cvlan_tpid, cvlan_vid)) + + # TODO: ISP VLAN 和 COS + + # TODO: 上下行带宽模板 + + # TODO: 数据业务带宽类型、优先级队列、GEM PORT + + # 翻译设置 + enable_translate = 'enable' if kargs.get('enable_translate', False) else 'disable' + translate_vid = kargs.get('translate_vid', 'null') + translate_cos = kargs.get('translate_cos', 'null') + translate_tpid = kargs.get('translate_tpid', 33024) + cli.set_onu_port_vlan_service_vlan(onu_dev.sn, onu_eth, service_index, ('translate', enable_translate, translate_cos, translate_tpid, translate_vid)) + + # QinQ状态 + if 'enable_qinq' in kargs.keys(): + + assert 'qinq_profile' in kargs.keys(), '使能QinQ时, qinq_profile不能为空' + assert 'service_vlan_name' in kargs.keys(), '使能QinQ时, service_vlan_name不能为空' + assert 'svlan_vid' in kargs.keys(), '使能QinQ时, svlan vid不能为空' + assert 'svlan_cos' in kargs.keys(), '使能QinQ时, svlan cos不能为空' + assert 'svlan_tpid' in kargs.keys(), '使能QinQ时,svlan tpid不能为空' + + enable_qinq = 'enable' if kargs['enable_qinq'] else 'disable' + svlan_cos = kargs['svlan_cos'] + svlan_tpid = kargs['svlan_tpid'] + svlan_vid = kargs['svlan_vid'] + qinq_profile = kargs['qinq_profile'] + service_vlan_name = kargs['service_vlan_name'] + + cli.set_onu_port_vlan_service_vlan(onu_dev.sn, onu_eth, service_index, ('qinq', enable_qinq, svlan_cos, svlan_tpid, svlan_vid, qinq_profile, service_vlan_name)) + + return service_index + + +# TODO: WAN 配置 + + + +__all__ = [ + + 'clear_port_service', + 'add_port_service', + +] \ No newline at end of file diff --git a/oltcli/cli/__init__.py b/oltcli/cli/__init__.py new file mode 100644 index 0000000..13d8034 --- /dev/null +++ b/oltcli/cli/__init__.py @@ -0,0 +1,27 @@ +''' +olt commandline interface +''' + + +from typing import Union +from oltcli.utils import dotdict +from .common import * +from .an6k_17 import * + +class OLTCLI: + + @staticmethod + def get(olt_dev:dotdict) -> Union[OLTCLI_AN6K_17]: + """get a olt commandline object according to given olt dev + + Args: + olt_dev (Device): OLT设备资源 + + Returns: + OLTCLI: 返回OLTCLI类 + """ + + if olt_dev.model == 'AN6000-17': + return OLTCLI_AN6K_17(olt_dev) + + raise RuntimeError('不支持的OLT设备型号') \ No newline at end of file diff --git a/oltcli/cli/an6k_17.py b/oltcli/cli/an6k_17.py new file mode 100644 index 0000000..f3159bb --- /dev/null +++ b/oltcli/cli/an6k_17.py @@ -0,0 +1,4423 @@ +''' +这里是AN6000-17型号OLT的OLTCLI实现模块 +''' + +from datetime import time +from dateutil.parser import parse + +import re +import logging +import time + +from ..utils import value, validate_type, run_by_thread_pool, list_to_str, validate_key, wait_for_true, validate_int_range, len_of_mask +from .common import * +from ..conn import Connection + + + +type_str_to_int_value_map = { + # 用于将show onuqinq-classification-profile返回的字符串转换为数值 + + 'Source MAC Address': 0, + 'Destination MAC Address': 1, + 'Source IPV4 Address': 2, + 'Destination IPV4 Address': 3, + 'VLAN ID': 4, + 'Ethernet TYPE': 5, + 'IP Protocol Type': 6, + 'COS': 7, + 'TOS': 8, + 'L4 Source Port': 9, + 'L4 Destination Port': 10, + 'Destination IPV6 Prefix': 11, + 'Source IPV6 Prefix': 12, + 'IP Version': 13, + 'IPV6 Traffic Class': 14, + 'IPV6 Flow Label': 15, + 'IPV6 Next Header': 16 +} + +op_str_to_int_value_map = { + # 用于将show onuqinq-classification-profile返回的字符串转换为数值 + + '=': 0, + '!=': 1, + '<=': 2, + '>=': 3, + 'Exist then match': 4, + 'No exist then match': 5, + 'Always match': 6 +} + +def type_str_to_int(strValue): + """将show onuqinq-domain-profile返回的type字串转换为数值 + + Args: + strValue (str): type字符串 + + Returns: + int: 字符串对应的数值 + """ + return type_str_to_int_value_map[strValue] + +def op_str_to_int(strValue): + """将show onuqinq-domain-profile返回的op字串转换为数值 + + Args: + strValue (str): op字符串 + + Returns: + int: 字符串对应的数值 + """ + return op_str_to_int_value_map[strValue] + +def onu_value_str_to_str(strValue, intFieldType): + """转换show onuqinq-domain-profile返回的value字串 + + Args: + strValue (str): value字符串 + intFieldType (int): 该字符串对应的Field类型 + Returns: + any: 处理后的字符串 + """ + # 0: Source MAC Source MAC Address 00 00 00 00 00 00 + # 1: Destination MAC Destination MAC Address 00 00 00 00 00 00 + # 2: Source IPv4 Source IPV4 Address 192.168.1.1 + # 3: Destination IPv4 Destination IPV4 Address 192.168.1.1 + # 4: Vlan ID VLAN ID 1000 + # 5: Ethernet type Ethernet TYPE 11 + # 6: IP protocol type IP Protocol Type 0b 00 00 00 00 00 + # 7: Ethernet priority COS 01 00 00 00 00 00 + # 8: TOS/DSCP TOS 0b 00 00 00 00 00 + # 9: L4 source port L4 Source Port 54 + # 10: L4 destination port L4 Destination Port 54 + # 11: Destination IPv6 prefix Destination IPV6 Prefix 1:1:1:1:1:1:1:1 + # 12: Source IPv6 prefix Source IPV6 Prefix 1:1:1:1:1:1:1:1 + # 13: IP version IP Version 04 + # 14: IPv6 traffic class IPV6 Traffic Class 04 00 00 00 00 00 + # 15: IPv6 flow label IPV6 Flow Label 00 00 04 57 00 00 + # 16: IPv6 next header IPV6 Next Header 0a 00 00 00 00 00 + + if intFieldType in [0, 1, 2, 3, 4, 5, 9, 10, 11, 12]: + return strValue.replace(' ', '') + + if intFieldType in [6, 7, 8, 13, 14, 15, 16]: + strList = strValue.split(' ') + # remove '00' in the head until meet no '00' + while len(strList) != 0 and '00' == strList[0]: + del strList[0] + # remove '00' in the tail until meet no '00' + while len(strList) != 0 and '00' == strList[-1]: + del strList[-1] + + strValue = ''.join(strList) + # if value is zero, strValue will be empty string, so we need put 0 here to avoid exception + if strValue == '': + strValue = '0' + + # return + return int(strValue, 16) + +def olt_value_str_to_str(strValue, intFieldId): + """ convert value str in show oltqinq-domain to str value + """ + # 1(Dst Mac) 00 00 00 00 00 00 00 00 + # 2(Src Mac) 00 00 00 00 00 00 00 00 + # 3(Ethernet Type) [ff ff] 00 00 00 00 00 00 + # 4(Vlan4) [ff ff] 00 00 00 00 00 00 + # 5(Vlan3) [ff ff] 00 00 00 00 00 00 + # 6(Vlan2) [ff ff] 00 00 00 00 00 00 + # 7(Vlan1) [ff ff] 00 00 00 00 00 00 + # 8(TOS) [ff] 00 00 00 00 00 00 00 + # 10(TTL) [ff] 00 00 00 00 00 00 00 + # 11(Protocol Type) [ff] 00 00 00 00 00 00 00 + # 12(Src IPv4) 192.168.1.1 + # 13(Src IPv6) 1:2:3:4:5:6:7:8 + # 14(Dst IPv4) 192.168.1.1 + # 15(Dst IPv6) 1:2:3:4:5:6:7:8 + # 16(L4 Src Port) [ff ff] 00 00 00 00 00 00 + # 17(L4 Dst Port) [ff ff] 00 00 00 00 00 00 + # 18(Cos4) [ff] 00 00 00 00 00 00 00 + # 19(Cos3) [ff] 00 00 00 00 00 00 00 + # 20(Cos2) [ff] 00 00 00 00 00 00 00 + # 21(Cos1) [ff] 00 00 00 00 00 00 00 + # 22(Dst IPv6 Prefix) 1:2:3:4:5:6:7:8 + # 23(Src IPv6 Prefix) 1:2:3:4:5:6:7:8 + # 24(IP Version) [ff ff] 00 00 00 00 00 00 + # 25(IPv6 Traffic Class) [ff] 00 00 00 00 00 00 00 + # 26(IPv6 Flow Label) [ff ff] 00 00 00 00 00 00 + # 27(IPv6 Next Header) [ff] 00 00 00 00 00 00 00 + + if intFieldId in [1, 2]: + return strValue.replace(' ', '')[:-4] + + if intFieldId in [12, 13, 14, 15, 22, 23]: + return strValue + + if intFieldId in [3, 4, 5, 6, 7, 8, 10, 11, 16, 17, 18, 19, 20, 21, 24, 25, 26, 27]: + strList = strValue.split(' ') + # remove '00' in the head until meet no '00' + while len(strList) != 0 and '00' == strList[0]: + del strList[0] + # remove '00' in the tail until meet no '00' + while len(strList) != 0 and '00' == strList[-1]: + del strList[-1] + + strValue = ''.join(strList) + # if value is zero, strValue will be empty string, so we need put 0 here to avoid exception + if strValue == '': + strValue = '0' + + # return + return int(strValue, 16) + + assert False, "unknown field id: %s" % intFieldId + +def str_to_bool(strStatus): + """将'enable','disable','enabled'和'disabled'字符串转换为bool类型 + + Args: + strStatus: 待转换的字符串 + + Returns: + bool: 'enable'和'enabled'转换为True,'disable'和'disabled'字符串转换为False + """ + validate_type('strStatus', strStatus, str) + + strStatus = strStatus.strip().lower() + + assert strStatus in ['enable','disable', 'enabled', 'disabled'], "仅支持'enable','disable', 'enabled', 'disabled'四种字符串转换,不支持%s" % strStatus + + if strStatus.find("enable") != -1: + return True + else: + return False + +def bool_to_str(boolStatus): + """将bool值转换为'enable'或'disable'字符串 + + Args: + boolStatus (bool): 待转换的bool值 + Returns: + str: True返回'enable', False返回'disable' + """ + validate_type('boolStatus', boolStatus, bool) + + if boolStatus: + return "enable" + else: + return "disable" + +def is_vlan_in_list(beginVlan, endVlan, tag, vlanList): + """检查vlan列表中是否已经包含指定的vlan。如 1000, 1000, 'T',已经包含在[(1000, 2000, 'T')]列表中。 + + Args: + beginVlan (int): 开始vlan + endVlan (int): 结束vlan + tag (str): vlan的模式,'T'或'U' + vlanList (list): vlan列表。形式如, (1000, 2000, 'T')。 + + Returns: + bool: True,vlanList中包含它; False,vlanList不包含它。 + """ + isContained = False + for v in vlanList: + beginVlan, endVlan, vTag = v + if vTag == tag: + if beginVlan <= beginVlan and endVlan <= endVlan: + isContained = True + + return isContained + +def convert_port_vlan_str(strValue): + """将port vlan字串转换成指定格式 + + Args: + strValue (str): port vlan的配置字符串。如, 1000 tag 1/9 2, 3、1000 to 2000 tag 1/9 2格式。 + + Returns: + tuple: (beginVlan, endVlan, tag, slotPortTupleList)格式的元组。如1000, 1000, tag, [(9, 2), (9, 3)]。 + """ + validate_type('strValue', strValue, str) + + vlanExp = re.compile('(\d+)\s+(to\s+(\d+)\s+)?(\w+)\s+\d+/(\d+)\s+([\d\s,]+)') + + beginVlan, _, endVlan, tag, slot, ports = vlanExp.match(strValue) + if endVlan == None: + endVlan = beginVlan + + portList = [ ] + for port in ports.split(","): + portList.append((value(slot), value(port))) + + return value(beginVlan), value(endVlan), value(tag), portList + +def extract_port_authentication_mode(strValue): + """处理show port authentication-mode命令返回的字符串。 + + Args: + strValue (str): show port authentication-mode命令返回的字符串。 + + Returns: + 字典: 字典的键为(slot, pon),值为授权模式。如, ret[slot, pon] = 'physical id' + """ + # ====================================== + # slot 4 pon 8 ,auth mode is physical id. + # ====================================== + # slot 4 pon 1 ,auth mode is physical id. + # slot 4 pon 2 ,auth mode is physical id. + # slot 4 pon 3 ,auth mode is physical id. + # slot 4 pon 4 ,auth mode is physical id. + # slot 4 pon 5 ,auth mode is physical id. + # slot 4 pon 6 ,auth mode is physical id. + # slot 4 pon 7 ,auth mode is physical id. + # slot 4 pon 8 ,auth mode is physical id. + # slot 4 pon 9 ,auth mode is physical id. + # slot 4 pon 10 ,auth mode is physical id. + # slot 4 pon 11 ,auth mode is physical id. + # slot 4 pon 12 ,auth mode is physical id. + # slot 4 pon 13 ,auth mode is physical id. + # slot 4 pon 14 ,auth mode is physical id. + # slot 4 pon 15 ,auth mode is physical id. + # slot 4 pon 16 ,auth mode is physical id. + # ----- PON AUTH, ITEM=16 ----- + # ====================================== + validate_type('strValue', strValue, str) + + lines = strValue.splitlines() + + portAuthExp = re.compile('slot (\d+) pon (\d+) ,auth mode is ([\w\s-+]+).') + + AuthModeDict = { + + # 用于将show port authentication-mode命令返回的授权模式字符串,映射为AuthMode中的模式 + "logical id": AuthMode.logid, + "logical id + password": AuthMode.logid_psw, + "no auth": AuthMode.no_auth, + "physical password": AuthMode.password, + "physical id + password": AuthMode.phyid_psw, + "physical id or logical id + password or physical password": AuthMode.phyid_o_logid_psw_o_psw, + "physical id or logical id or physical password": AuthMode.phyid_o_logid_o_psw, + "physical id or physical password": AuthMode.phyid_o_psw, + "physical id": AuthMode.phyid + } + + + ret = { } + for line in lines: + match = portAuthExp.match(line) + if match: + slot, pon, authMode = match.groups() + ret[value(slot), value(pon)] = AuthModeDict[authMode] + + return ret + +def extract_authorization(strValue): + """处理show authorization命令得到的信息 + + Args: + strValue (str): show authorization命令得到的信息 + + Returns: + list: 包含信息的字典列表 + """ + # ==================================================================================================== + # ----- ONU Auth Table, Total ITEM = 5 ----- + + # A: Authorized P: Preauthorized R: System Reserved + + # ----- ONU Auth Table, SLOT = 4, PON = 8, ITEM = 5 ----- + # Slot Pon Onu OnuType ST Lic OST PhyId PhyPwd LogicId LogicPwd + # ---- --- --- -------------- -- --- --- ------------ ---------- ------------------------ ------------ + # 4 8 1 5506-04-F1 A 0 up FHTT033178b0 + # 4 8 64 HG6243C A 0 up FHTT92f445c8 + # 4 8 100 5506-10-A1 A 0 up FHTT00010104 + # 4 8 127 5506-02-F A 0 up FHTT0274ab18 + # 4 8 128 5506-10-A1 A 0 up FHTT000aae64 + # ==================================================================================================== + + # A: Authorized P: Preauthorized R: System Reserved + + # ----- ONU Auth Table, SLOT = 4, PON = 8, ITEM = 5 ----- + # Slot Pon Onu OnuType ST Lic OST PhyId PhyPwd LogicId LogicPwd + # ---- --- --- -------------- -- --- --- ------------ ---------- ------------------------ ------------ + # 4 8 1 5506-04-F1 A 0 up FHTT033178b0 + # 4 8 64 HG6243C A 0 up FHTT92f445c8 + # 4 8 100 5506-10-A1 A 0 up FHTT00010104 + # 4 8 127 5506-02-F A 0 up FHTT0274ab18 + # 4 8 128 5506-10-A1 A 0 up FHTT000aae64 + # ==================================================================================================== + validate_type('strValue', strValue, str) + + # 匹配标题 + titlesExp = re.compile('(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)') + # 匹配值 + valuesExp = re.compile('([\d\s]{4,4})\s([\d\s]{3,3})\s([\d\s]{3,3})\s([\w\s-]{14,14})\s([\w\s]{2,2})\s([\d\s]{3,3})\s([\w\s]{3,3})\s([\w\s]{12,12})\s(.{10,10}\s)?(.{24,24}\s)?(.{12,12})?') + + ret = [] + lines = strValue.splitlines() + titles = None + for line in lines: + match = titlesExp.match(line) + if match: + titles = match.groups() + continue + + match = valuesExp.match(line) + if match: + values = match.groups() + # 以字典形式存入 + ret.append({}) + for k, v in zip(titles, values): + ret[-1][value(k)] = value(v, maxValue = 65535) + continue + + return ret + +def extract_discovery(strValue): + """处理show discovery/show onu discovered得到的信息 + + Args: + strValue (str): show discovery/show onu discovered命令返回的字符串 + + Returns: + list: 包含字典的列表 + """ + # ==================================================================================== + # ----- ONU Unauth Table, SLOT = 4, PON = 8, ITEM = 1 ----- + # No OnuType PhyId PhyPwd LogicId LogicPwd Why + # --- -------------- ------------ ---------- ------------------------ ------------ --- + # 1 HG6243C FHTT91fbc5e8 fiberhome fiberhome fiberhome 1 + + # Command executes success. + # ==================================================================================== + # ----- ONU Unauth Table, SLOT = 4, PON = 8, ITEM = 1 ----- + # No OnuType PhyId PhyPwd LogicId LogicPwd Why + # --- -------------- ------------ ---------- ------------------------ ------------ --- + # 1 HG6243C FHTT91fbc5e8 fiberhome fiberhome fiberhome 1 + # + # ==================================================================================== + # ----- ONU Unauth Table, SLOT = 4, PON = 8, ITEM = 6 ----- + # No OnuType PhyId PhyPwd LogicId LogicPwd Why + # --- -------------- ------------ ---------- ------------------------ ------------ --- + # 1 5506-04-F1 FHTT033178b0 fiberhome fiberhome fiberhome 1 + # 2 HG6243C FHTT92f445c8 fiberhome fiberhome fiberhome 1 + # 3 5506-10-A1 FHTT00010104 fiberhome fiberhome fiberhome 1 + # 4 5506-10-A1 FHTT000aae64 fiberhome fiberhome 1 + # 5 HG6243C FHTT91fbc5e8 fiberhome fiberhome fiberhome 1 + # 6 5506-02-F FHTT0274ab18 wangran3 12345678 1 + # ==================================================================================== + + validate_type('strValue', strValue, str) + + slotPortExp = re.compile('SLOT = (\d+), PON = (\d+)') + titleExp = re.compile('(No)\s+(OnuType)\s+(PhyId)\s+(PhyPwd)\s+(LogicId)\s+(LogicPwd)\s+(Why)\s*') + valueExp = re.compile('([\d\s]{3,3})\s([\w\s-]{14,14})\s([\w\s]{12,12})\s([\w\s]{10,10})\s([\w\s]{24,24})\s([\w\s]{12,12})\s([\d\s]{1,3})') + + lines = strValue.splitlines() + + ret = [ ] + titles = None + slot, port = None, None + for line in lines: + match = slotPortExp.search(line) + if match: + slot, port = match.groups() + + if titles == None: + match = titleExp.match(line) + if match: + titles = match.groups() + continue + else: + match = valueExp.match(line) + if match: + values = match.groups() + ret.append({ }) + for k, v in zip(titles, values): + ret[-1][value(k)] = value(v) + ret[-1]['SLOT'] = value(slot) + ret[-1]['PON'] = value(port) + continue + + return ret + +def extract_auto_discover(strValue): + """ 处理show onu auto-discover得到的信息 + + Args: + strValue(str): show onu auto-discover得到的字符串 + + Returns: + list: (slot, portNo, status, agingTime)元组组成的列表 + """ + validate_type('strValue', strValue, str) + + exp = re.compile('slot\s*(\d+)\s*pon\s*(\d+)\s*:\s*(\w+)\s*,\s*agingtime:\s*(\d+)\s*s') + + ret = [ ] + for line in strValue.splitlines(): + match = exp.match(line) + if match != None: + slot, pon, status, agingTime = match.groups() + ret.append((value(slot), value(pon), value(status), value(agingTime))) + + return ret + +def extract_pon_auto_discover(strValue): + """处理show onu auto-discover得到的信息 + + Args: + strValue(str): show onu auto-discover得到的信息 + + Returns: + tuple: status, agingTime组成的元组,其中status为bool类型 + """ + validate_type('strValue', strValue, str) + + exp = re.compile('auto-discover-onu:\s+(\w+),\s+agingtime:\s+(\d+)') + + for line in strValue.splitlines(): + match = exp.match(line) + if match: + strStatus, strAgingTime = match.groups() + return value(strStatus), value(strAgingTime) + + raise RuntimeWarning("未发现Auto Discover数据") + +def extract_manage_vlan(strValue): + """处理show manage-vlan得到的信息 + + Args: + strValue(str): show manage-vlan得到的信息 + + Returns: + list: 包含管理Vlan字典的列表 + """ + # ------------------------------------ + # Manage name : xx + # ------------------------------------ + # Svlan : 1000 + # Scos : 7 + # Port : 9:2[T] + # Device : sub + # Unit : 1000 + # Ethernet address: 48:f9:7c:e9:8a:e3 + # Total protocols : 0 + # RX packets : 0 + # TX packets : 8 + # RX bytes : 0 + # TX bytes : 704 + # MTU : 0 + # ------------------------------------ + # Manage name : yy + # ------------------------------------ + # Svlan : 2000 + # Scos : 7 + # Port : 9:2[T] + # Device : sub + # Unit : 2000 + # Ethernet address: 48:f9:7c:e9:8a:e3 + # Total protocols : 0 + # RX packets : 0 + # TX packets : 8 + # RX bytes : 0 + # TX bytes : 704 + # MTU : 0 + + validate_type('strValue', strValue, str) + + keyValueExp = re.compile('([\w\s]+):\s(.+)') + + ret = [ ] + + for line in strValue.splitlines(): + + match = keyValueExp.match(line) + if match: + k, v = match.groups() + k = value(k) + v = value(v) + + if k == 'Manage name': + ret.append({ }) + + ret[-1][k] = v + + return ret + +def extract_whitelist(strValue): + """处理show whitelist命令得到的信息。 + + Args: + strValue (str): show whitelist命令得到的信息 + + Returns: + list: 包含处理后的信息列表 + """ + # 支持匹配6种输出 + + validate_type('strValue', strValue, str) + + lines = strValue.splitlines() + + # 在config下运行show whitelist命令时 + # ----- Physical Address Whitelist ----- + # Slot Pon Onu Onu-Type Phy-ID Phy-Pwd Used + # ----- ----- ----- -------------- ------------ ---------- ---- + # 13 1 1 null FHTT17f6c2d2 Y + phy1TitleExp = re.compile('(Slot)\s+(Pon)\s+(Onu)\s+(Onu-Type)\s+(Phy-ID)\s+(Phy-Pwd)\s+(Used)') + phy1ValuesExp = re.compile('([\d\s]{5,5})\s([\d\s]{5,5})\s([\d\s]{5,5})\s([\w\s-]{14,14})\s([\w\s]{12,12})\s([\w\d\s]{10,10})\s([\w\s]{1,4})') + + # ----- Physical SN Whitelist----- + # PHYID PHYPWD SLOT PON ONU TYPE EN USED + # ------------ ---------- ----- ----- ----- -------------- --- ---- + # FHTT000aae64 4 8 1 5506-10-A1 EN YES + # -------------------------------- + # SLOT: 4 PON: 8 ITEM: 1 + phy2TitleExp = re.compile('(PHYID)\s+(PHYPWD)\s+(SLOT)\s+(PON)\s+(ONU)\s+(TYPE)\s+(EN)\s+USED') + phy2ValuesExp = re.compile('([\w\s]{12,12})\s([\w\s]{10,10})\s([\d\s]{5,5})\s([\d\s]{5,5})\s([\d\s]{5,5})\s([\w\s-]{14,14})\s([\w\s]{3,3})\s([\w\s]{1,4})') + + # ----- Logic SN Whitelist----- + # Slot Pon Onu Onu-Type Logic-Id Logic-Pwd En Used + # ----- ----- ----- -------------- ------------------------ ------------ -- ---- + # 13 1 2 null FHTT17f6c2d2 Y Y + log1TitleExp = re.compile('(Slot)\s+(Pon)\s+(Onu)\s+(Onu-Type)\s+(Logic-Id)\s+(Logic-Pwd)\s+(En)\s+(Used)') + log1ValueExp = re.compile('([\d\s]{5,5})\s([\d\s]{5,5})\s([\d\s]{5,5})\s([\w\s-]{14,14})\s([\w\s]{24,24})\s([\w\s]{12,12})\s([\w\s]{2,2})\s([\w\s]{1,4})') + + # ----- Logical SN Whitelist----- + # LOGICId LOGICPWD SLOT PON ONU TYPE EN USED + # ------------------------ ------------ ----- ----- ----- -------------- --- ---- + # FHTT000aae64 4 8 2 5506-10-A1 EN YES + # -------------------------------- + # SLOT: 4 PON: 8 ITEM: 1 + log2TitleExp = re.compile('(LOGICId)\s+(LOGICPWD)\s+(SLOT)\s+(PON)\s+(ONU)\s+(TYPE)\s+(EN)\s+(USED)') + log2ValueExp = re.compile('([\w\s]{24,24})\s([\w\s]{12,12})\s([\d\s]{5,5})\s([\d\s]{5,5})\s([\d\s]{5,5})\s([\w\s-]{14,14})\s([\w\s]{3,3})\s([\w\s]{1,4})') + + # ----- Physical Password Whitelist ----- + # Slot Pon Onu Onu-Type Phy-Pwd En Used + # ----- ----- ----- -------------- ---------- -- ---- + # 65535 65535 65535 null 123456 Y N + pwd1TitleExp = re.compile('(Slot)\s+(Pon)\s+(Onu)\s+(Onu-Type)\s+(Phy-Pwd)\s+(En)\s+(Used)') + pwd1ValueExp = re.compile('([\d\s]{5,5})\s([\d\s]{5,5})\s([\d\s]{5,5})\s([\w\s-]{14,14})\s([\w\s]{10,10})\s([\w\s]{2,2})\s([\w\s]{1,4})') + + # ----- Physical Password Whitelist----- + # PHYPWD SLOT PON ONU TYPE EN USED + # ---------- ----- ----- ----- -------------- --- ---- + # 1234567890 4 8 3 5506-10-A1 EN YES + # -------------------------------- + # SLOT: 4 PON: 8 ITEM: 1 + pwd2TitleExp = re.compile('(PHYPWD)\s+(SLOT)\s+(PON)\s+(ONU)\s+(TYPE)\s+(EN)\s+(USED)') + # '123456789a 4 8 65535 5506-10-A1 EN NO ' + pwd2ValueExp = re.compile('([\w\s]{10,10})\s([\d\s]{5,5})\s([\d\s]{5,5})\s([\d\s]{5,5})\s([\w\s-]{14,14})\s([\w\s]{3,3})\s([\w\s]{1,4})') + + exps = [(phy1TitleExp, phy1ValuesExp), + (phy2TitleExp, phy2ValuesExp), + (log1TitleExp, log1ValueExp), + (log2TitleExp, log2ValueExp), + (pwd1TitleExp, pwd1ValueExp), + (pwd2TitleExp, pwd2ValueExp)] + + ret = [ ] + titles = None + valueExp = None + for line in lines: + if titles == None: + # 先看看匹配到哪种标题,然后再用标题对应的抽取值的正则表达式去提取 + for tExp, vExp in exps: + match = tExp.match(line) + if match != None: + titles = match.groups() + valueExp = vExp + break + + else: + # 确定之后,用该种正则表达式进行匹配 + match = valueExp.match(line) + if match: + values = match.groups() + ret.append({ }) + for k, v in zip(titles, values): + ret[-1][value(k)] = value(v) + + return ret + +def extract_last_reg_status_change(strValue): + """处理show onu last-reg-status-change命令得到的数据。其中,时间若为0000-00-00 00:00:00,将会转换为None。 + + Args: + strValue (str): show onu last-reg-status-change命令得到的数据 + + Returns: + list: 字典列表。 + """ + # SLOT PON ONU LAST_OFF_TIME LAST_ON_TIME + # 4 8 1 Last Off Time = 0000-00-00 00:00:00,Last On Time = 2020-09-22 14:09:29. + # SLOT PON ONU LAST_OFF_TIME LAST_ON_TIME + # 4 8 64 Last Off Time = 0000-00-00 00:00:00,Last On Time = 2020-09-22 14:09:30. + # SLOT PON ONU LAST_OFF_TIME LAST_ON_TIME + # 4 8 65 Last Off Time = 0000-00-00 00:00:00,Last On Time = 2020-09-22 14:09:29. + # SLOT PON ONU LAST_OFF_TIME LAST_ON_TIME + # 4 8 100 Last Off Time = 0000-00-00 00:00:00,Last On Time = 0000-00-00 00:00:00. + # SLOT PON ONU LAST_OFF_TIME LAST_ON_TIME + # 4 8 128 Last Off Time = 0000-00-00 00:00:00,Last On Time = 2020-09-22 14:09:30. + validate_type('strValue', strValue, str) + lines = strValue.splitlines() + + titles = ['SLOT', 'PON', 'ONU', 'LAST_OFF_TIME', 'LAST_ON_TIME'] + valuesExp = re.compile("(\d+)\s+(\d+)\s+(\d+)\s+Last Off Time = (\d{4,4}\-\d{2,2}\-\d{2,2}\s\d{2,2}:\d{2,2}:\d{2,2}),Last On Time = (\d{4,4}\-\d{2,2}\-\d{2,2}\s\d{2,2}:\d{2,2}:\d{2,2})\.") + + ret = [ ] + for line in lines: + match = valuesExp.match(line) + if match: + values = match.groups() + ret.append({ }) + for k, v in zip(titles, values): + ret[-1][value(k)] = value(v) + + return ret + +def extract_dhcp_state(strValue): + """处理show dhcp state命令得到的数据 + + Args: + strValue (str): show dhcp state命令得到的数据 + + Returns: + dict: 包含DHCP状态信息的字典 + """ + # DHCP option82 : disabled + # DHCP option18 : enabled + # DHCP option37 : disabled + # EPON DHCP Patch : disabled + # EPON ARP Patch : disabled + validate_type('strValue', strValue, str) + + ret = { } + for line in strValue.splitlines(): + key, value = line.split(":") + ret[key.strip()] = value.strip() + + return ret + +def extract_onu_port_vlan(strValue): + """处理show onu port vlan得到的信息。 + + Args: + strValue (str): show onu port vlan得到的信息。 + + Returns: + list: 包含Port Vlan信息的列表 + """ + # NO. SL/LI/ONU PORT ID TYPE MODE CVID COS TPID TVID COS TPID SVID COS TPID PVID COS SRVTYPE PRIQUE GEMPORT + # ==================================================================================================================== + # 1 4 /8 /1 1 1 unica tran null null 33024 null null null null null null null null default default default + validate_type('strValue', strValue, str) + + ret = [ ] + titles = ['NO', 'SL', 'LI', 'ONU', 'PORT', 'ID', 'TYPE', 'MODE', 'CVID', 'CCOS', 'CTPID', 'TVID', 'TCOS', 'TTPID', 'SVID', 'SCOS', 'STPID', 'PVID', 'PCOS', 'SRVTYPE', 'PRIQUE', 'GEMPORT'] + regexExp = re.compile("(\w+)\s+(\w+)\s+/(\w+)\s+/(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)") + for line in strValue.splitlines(): + match = regexExp.match(line) + if match != None: + ret.append({}) + for k, v in zip(titles, match.groups()): + ret[-1][k] = value(v) + + return ret + +def extract_card_info(strValue): + """处理show card info得到的信息 + + Args: + strValue (str): show card info得到的信息 + + Return: + list : 包含卡信息的字典列表 + """ + # ---------------------AN6000-17--------------------- + # CARD EXIST CONFIG DETECT DETAIL + # 1 --- --- --- --- + # 2 --- --- --- --- + # 3 --- --- --- --- + # 4 --- --- --- --- + # 5 --- --- --- --- + # 6 --- --- --- --- + # 7 --- --- --- --- + # 8 --- --- --- --- + # 9 YES HSCA HSCA MATCH/M + # 10 --- HSCA --- --- + + # ---------------------AN6000-17--------------------- + # CARD EXIST CONFIG DETECT DETAIL BLOCK + # 1 --- --- --- --- --- + # 2 --- --- --- --- --- + # 3 --- --- --- --- --- + # 4 YES GPOA GPOA MATCH OFF + # 5 --- --- --- --- --- + # 6 --- --- --- --- --- + # 7 --- --- --- --- --- + # 8 --- --- --- --- --- + # 9 YES HSCA HSCA MATCH/M --- + # 10 --- HSCA --- --- --- + validate_type('strValue', strValue, str) + + lines = strValue.splitlines() + + titlesExp = re.compile('(CARD)\s+(EXIST)\s+(CONFIG)\s+(DETECT)\s+(DETAIL)\s*(BLOCK)?') + valuesExp = re.compile('(\d+)\s+([\w-]+)\s+([\w-]+)\s+([\w-]+)\s+([\w-\/]+)\s*([\w-]+)?') + + ret = [ ] + titles = None + for line in lines: + if titles == None: + match = titlesExp.match(line) + if match: + titles = match.groups() + continue + else: + match = valuesExp.match(line) + if match: + values = match.groups() + ret.append({ }) + for k, v in zip(titles, values): + if k != None: + ret[-1][value(k)] = value(v) + continue + + return ret + +def extract_onu_port_status(strValue): + """处理show onu port status命令得到的信息 + + Args: + strValue (str): show onu port status命令得到的信息 + + Returns: + list: 包含端口信息的字典列表 + """ + # ----- ONU FE PORT STATUS ----- + # SLOT:4 PON:8 ONU:1 , ITEM=4 + + # PORT ID = 1 + # PORT CONNECT : Linked + # FLOW CONTROL : disable + # PORT PHY STATE : enable + # AUTO NEGOTIATE : enable + # PORT RATE : 1000M + # PORT CONNECT : full + # LOOPBACK STATUS : normal + + # PORT ID = 2 + # PORT CONNECT : Linked + # FLOW CONTROL : disable + # PORT PHY STATE : enable + # AUTO NEGOTIATE : enable + # PORT RATE : 1000M + # PORT CONNECT : full + # LOOPBACK STATUS : normal + + # PORT ID = 3 + # PORT CONNECT : Not Linked + # FLOW CONTROL : disable + # PORT PHY STATE : enable + # AUTO NEGOTIATE : enable + # PORT RATE : 10M + # PORT CONNECT : half + # LOOPBACK STATUS : normal + + # PORT ID = 4 + # PORT CONNECT : Linked + # FLOW CONTROL : disable + # PORT PHY STATE : enable + # AUTO NEGOTIATE : enable + # PORT RATE : 1000M + # PORT CONNECT : full + # LOOPBACK STATUS : normal + + validate_type('strValue', strValue, str) + + summaryExp = re.compile('SLOT:(\d+)\s*PON:(\d+)\s*ONU:(\d+)\s*,\s*ITEM=(\d+)') + portIdExp = re.compile('PORT\s+ID\s+=\s+(\d+)') + keyValueExp = re.compile('(.+):(.+)') + + lines = strValue.splitlines() + + ret = { } + for line in lines: + match = summaryExp.match(line) + if match: + slot, pon, onu, item = match.groups() + ret['SLOT'] = value(slot) + ret['PON'] = value(pon) + ret['ONU'] = value(onu) + ret['ITEM'] = value(item) + ret['PORT'] = [ ] + continue + + match = portIdExp.match(line) + if match: + portId = match.groups()[0] + ret['PORT'].append({ }) + ret['PORT'][-1]['PORT ID'] = value(portId) + continue + + match = keyValueExp.match(line) + if match: + k, v = match.groups() + if value(k) not in ret['PORT'][-1].keys(): + ret['PORT'][-1][value(k)] = value(v) + else: + # 有两个PORT CONNECT, 需要把后一个换个名字 + ret['PORT'][-1]['PORT MODE'] = value(v) + continue + + return ret + +def extract_igmp_vlan(strValue): + """处理show igmp vlan命令得到的数据 + + Args: + strValue (str): show igmp vlan命令得到的数据 + + Returns: + dict: 包含igmp vlan信息的字典 + """ + # ======================================== + # Version :V0 + # Proxy ip address :0.0.0.0 + # SSM ip address :0.0.0.0 + # SSM ip mask :0.0.0.0 + # General Member Interval :0 + # Robustness :0 + # QueryInterval :0 + # Query response interval :0 + # Last member query interval:0 + # Last member query count :0 + # Fast Leave :disable + + # Host-side Group Reserved :0 + # ALL Group Reserved :0 + # Recieve Jions :0 + # Recieve Leaves :0 + # igi->igi_cflags :0x0 + # igi->igi_sflags :0x0 + # ======================================== + validate_type('strValue', strValue, str) + + regexExp = re.compile("(.+):(.+)") + + lines = strValue.splitlines() + + ret = { } + for line in lines: + match = regexExp.match(line) + if match != None: + k, v = match.groups() + ret[value(k)] = value(v) + + return ret + +def extract_port_vlan(strValue): + """处理show port vlan得到的数据 + + Args: + strValue (str): show port vlan得到的数据 + + Returns: + list: 元组列表。形式如,[(startVlan, endVlan, "U"), [(startVlan, endVlan, "T"),...]。 + """ + # port 9:2, + # vlan(optin): + # 1000(U) . + # 1251 ~ 1254(T). + # 3049 ~ 3049(T). + + validate_type('strValue', strValue, str) + + vlanExp = re.compile('(\d+)\s?~?\s?(\d+)?\(([UT])\)') + + lines = strValue.splitlines() + + ret = [ ] + for line in lines: + + match = vlanExp.match(line) + if match: + beginVlan, endVlan, tag = match.groups() + if endVlan == None: + endVlan = beginVlan + + ret.append((value(beginVlan), value(endVlan), value(tag))) + + return ret + +def extract_wan_cfg(strValue): + """提取show onu wan-cfg命令得到的信息 + + Args: + strValue (str): show onu wan-cfg命令得到的信息 + + Return: + dict: 包含wan cfg信息的字典 + """ + validate_type('strValue', strValue, str) + + ret = None + + regexExp0 = re.compile("slot_out \d+ \d+ \d+ index \d+ no wancfg,ret -?\d+.") + if regexExp0.search(strValue) != None: + return ret + + # show wancfg:slot 4 8 1 2 wan_name INTERNET_R_VID_2000 INTERNET route vlan 2000 cos 4 nat enable qos disable upnp disable DSP pppoe 0 mode auto fiberhome fiberhome xxx transparent translate disable tvlan 65535 tcos 65535 qinq disable 33024 65535 bind item 4 1 2 3 101 + # show wancfg:slot 4 8 1 1 wan_name INTERNET_R_VID_1000 INTERNET route vlan 1000 cos 4 nat enable qos enable upnp disable transparent translate disable tvlan 65535 tcos 65535 qinq disable 33024 65535 65535 bind item 4 1 2 3 101 + + mandatoryPartExp = re.compile('show wancfg:slot\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+wan_name\s+(\w+)\s+(\w+)\s+(\w+)\s+vlan\s+(\d+)\s+cos\s+(\d+)\s+nat\s+(\w+)\s+qos\s+(\w+)\s+upnp\s+(\w+)\s+') + # DSP\s+(\w+)\s+(\d+)\s+mode\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+ + pppoeDSPExp = re.compile('DSP\s+(\w+)\s+(\d+)\s+mode\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+') + dhcpDSPExp = re.compile('DSP\s+(\w+)') + staticDSPExp = re.compile('DSP\s+(\w+)\s+ip\s+(\d+\.\d+\.\d+\.\d+)\s+(\d+\.\d+\.\d+\.\d+)\s+(\d+\.\d+\.\d+\.\d+)\s+(\d+\.\d+\.\d+\.\d+)\s+(\d+\.\d+\.\d+\.\d+)') + vlanmodeExp = re.compile('(\w+)\s+translate\s+(\w+)\s+tvlan\s+(\d+)\s+tcos\s+(\d+)\s+qinq\s+(\w+)\s+(\d+)\s+(\d+)\s+(\d+)\s+') + entriesExp = re.compile('bind\s+item\s+(\d+)\s+([\d\s]+)?') + + if mandatoryPartExp.match(strValue) != None: + + if ret == None: + ret = {} + + slot, port, onuId, index, name, mode, type, wvid, wcos, nat, qos, upnp = mandatoryPartExp.match(strValue).groups() + + ret['slot'] = value(slot) + ret['port'] = value(port) + ret['onuId'] = value(onuId) + ret['index'] = value(index) + ret['name'] = value(name) + ret['mode'] = WanMode(mode.lower()) + ret['type'] = WanType(type.lower()) + ret['wvid'] = value(wvid) + ret['wcos'] = value(wcos) + ret['nat'] = value(nat) + ret['qos'] = value(qos) + ret['upnp'] = value(upnp) + + if vlanmodeExp.search(strValue) != None: + + tag, translate, tvlan, tcos, qinq, stpid, svlan, scos = vlanmodeExp.search(strValue).groups() + + ret['vlanmode'] = tag.lower() + ret['tvlan'] = translate + ret['tvid'] = value(tvlan) + ret['tcos'] = value(tcos) + ret['qinq'] = qinq + ret['stpid'] = value(stpid) + ret['svlan'] = value(svlan) + ret['scos'] = value(scos) + + if ret != None and 'dsp' not in ret.keys() and pppoeDSPExp.search(strValue) != None: + + dsp, proxy, pppoemode, username, password, servername = pppoeDSPExp.search(strValue).groups() + + ret['dsp'] = DSPMode(dsp.lower()) + ret['proxy'] = 'enable' if proxy == '1' else 'disable' + ret['pppoemode'] = PPPoEMode(pppoemode.lower()) + ret['username'] = value(username) + ret['password'] = value(password) + ret['servername'] = value(servername) + + if ret != None and 'dsp' not in ret.keys() and staticDSPExp.search(strValue) != None: + + dsp, ip, mask, gate, master, slave = staticDSPExp.search(strValue).groups() + ret['dsp'] = DSPMode(dsp.lower()) + ret['ip'] = value(ip) + ret['mask'] = value(mask) + ret['gate'] = value(gate) + ret['master'] = value(master) + ret['slave'] = value(slave) + + if ret != None and 'dsp' not in ret.keys() and dhcpDSPExp.search(strValue) != None: + + dsp = dhcpDSPExp.search(strValue).groups() + ret['dsp'] = DSPMode(dsp.lower()) + + if ret != None and 'dsp' not in ret.keys(): + ret['dsp'] = DSPMode.dhcp_remoteid + ret['remoteid'] = 'n/a' + + ret['fe'] = [ ] + ret['ssid'] = [ ] + if entriesExp.search(strValue) != None: + + if ret == None: + ret = { } + + itemsCount, itemsValue = entriesExp.search(strValue).groups() + if int(itemsCount) != 0: + + feRegexExp = re.compile('^(\d)$') + ssidRegexExp = re.compile('^10(\d)$') + for v in re.split('\s+', itemsValue): + if None != feRegexExp.match(v): + ret['fe'].append('fe%s' % v) + elif None != ssidRegexExp.match(v): + ret['ssid'].append('ssid%s' % ssidRegexExp.match(v).groups()[0]) + elif '' == v: + continue + else: + assert False, "未知的数值: '%s'(%s)" % (v, re.split('\s+', itemsValue)) + + if ret == None: + logging.getLogger().warning('未匹配到数据:\n%s' % strValue) + + return ret + +def extract_igmp_mode_info(strValue): + """提取show igmp mode命令信息 + + Args: + strValue(str): show igmp mode命令信息 + + Returns: + dict: 包含IGMP信息的字典 + """ + # system is running in IGMPv1/v2 proxy/snooping protocol + # IGMP/MLD Mode : snooping + # Robustness variable : 2 + # Group membership interval : 260 + # Last member query interval: 1 + # Last member query count : 2 + # Query interval : 125 + # Query response interval : 10 + + validate_type('strValue', strValue, str) + + keyValueExp = re.compile('\s*(.+)\s*:\s*(.+)\s*') + + ret = { } + for line in strValue.splitlines(): + match = keyValueExp.match(line) + if match != None: + key, value = match.groups() + ret[key.strip()] = value.strip() + + return ret + +def extract_onu_statistics(strValue): + """提取show onu statistics命令的信息 + + Args: + strValue (str): show onu statistics命令的信息 + + Returns: + dict: 包含ONU统计信息的字典 + """ + # buf:0x8cdcddc4,len448,prtcl:0, lv:4,obj:4/8/4 type:4, order:0. + # From 2020-11-10 14:46:04 To 0000-00-00 00:00:00 + # UPOctetsTransferred : 0 (BYTEs) + # UP TotalFrame : 0 (PKTs) + # UP UnicastFrames : 0 (PKTs) + # UP BroadcastFrames : 0 (PKTs) + # UP MulticastFrames : 0 (PKTs) + # UP CRC-32Errors : 0 (PKTs) + # UPUndersizeFrames : 0 (PKTs) + # UPOversizeFrames : 0 (PKTs) + # UPCollisions : 0 (PKTs) + # 64OctetFrames : 0 (PKTs) + # 65-127OctetFrames : 0 (PKTs) + # 128-255OctetFrames : 0 (PKTs) + # 256-511OctetFrames : 0 (PKTs) + # 512-1023OctetFrames : 0 (PKTs) + # 1024-1518OctetFrames : 0 (PKTs) + # UPFramesDropped : 0 (PKTs) + # DownOctetsTransferred : 71016502636 (BYTEs) + # DownTotalFrame : 572736211 (PKTs) + # DownUnicastFrames : 572518682 (PKTs) + # DownBroadcastFrames : 217479 (PKTs) + # DownMulticastFrames : 50 (PKTs) + # DownCRC-32Errors : 0 (PKTs) + # DownUndersizeFrames : 0 (PKTs) + # DownOversizeFrames : 0 (PKTs) + # DownCollisions : 0 (PKTs) + # DownFramesDropped : 0 (PKTs) + # UPErrorBIP8 : 0 (PKTs) + # DownErrorBIP8 : 0 (PKTs) + # UPSpeed : 0.00 (Mbps) + # DownSpeed : 0.00 (Mbps) + # Optical module type : 20 (Km) + # Temperature : 46.83 ('C) + # Power(Voltage) : 3.34 (V) + # Bias current : 16.65 (mA) + # Tx_power : 2.99 (dbm) + # Rx_power : -13.14 (dbm) + # OLT_Rx_power : -14.32 (dbm) + + validate_type('strValue', strValue, str) + + ret = { } + + exp = re.compile('(.+):(.+)\((.+)\)') + for line in strValue.splitlines(): + match = exp.match(line) + if match: + key, value, unit = match.groups() + ret[key.strip()] = (value.strip(), unit.strip()) + + return ret + +def extract_bandwidth_profile(strValue): + """提取show bandwidth-profile的信息 + + Args: + strValue(str): show bandwidth-profile信息 + + Returns: + list: 包含onu bandwith profile信息的列表 + """ + # -------- onubandwidth profile, num = 10 -------- + # Id Name upMin upMax downMin downMax upFix + # ------------------------------------------------------------------ + # 2 bwp2 50000 100000 50000 100000 10000 + # 3 bwp 10000 10000 10000 10000 10000 + # 8 b_prf_8 0 1000 0 2000 0 + # 51 b_prf_51 0 2000 0 3000 0 + # 326 b_prf_326 0 1000 0 2000 0 + # 392 b_prf_392 0 1000 0 2000 0 + # 592 b_prf_592 0 2000 0 3000 0 + # 623 b_prf_623 0 2000 0 3000 0 + # 667 b_prf_667 0 2000 0 3000 0 + # 720 b_prf_720 0 1000 0 2000 0 + + # -------- onubandwidth profile, num = 1 -------- + # Id Name upMin upMax downMin downMax upFix + # ------------------------------------------------------------------ + # 3 bwp 10000 10000 10000 10000 10000 + validate_type('strValue', strValue, str) + + summaryRegex = re.compile('-+\s+onubandwidth\sprofile,\snum\s+=\s+(\d+)\s-+') + + titlesRegex = re.compile('(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s*') + + itemsRegex = re.compile('(\d+)\s+([\w\d]+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*') + + ret = [ ] + titles = None + count = None + for line in strValue.splitlines(): + + match = titlesre.match(line) + if itemsre.match(line): + + values = match.groups() + + save = { } + for t, v in zip(titles, values): + save[t] = value(v) + + ret.append(save) + continue + + match = summaryre.match(line) + if match: + assert len(match.groups()) == 1 + count = int(match.groups()[0]) + continue + + match = titlesre.match(line) + if match: + titles = match.groups() + continue + + assert count == len(ret) + + return ret + +def extract_onu_bandwidth(strValue): + """提取show onu bandwidth数据 + + Args: + strValue(str): show onu bandwidth数据 + + Returns: + dict: 包含onu bandwitdh信息的字典 + """ + # onu: slot 4 pon 8 onu 1. + # upMaxband: 1250000. + # downMaxband: 2500000. + # upAssureBand: 640. + # downAssureBand: 640. + # upFixband: 0. + # prfId: -1. + validate_type('strValue', strValue, str) + + ret = { } + keyValueRegexExp = re.compile('\s*(\w+)\s*:\s*(-?\d+)\s*\.') + for line in strValue.splitlines(): + match = keyValueRegexExp.match(line) + if match: + k, v = match.groups() + ret[k]=value(v) + + return ret + +def extract_bandwidth(strValue): + """提取show bandwidth数据 + + Args: + strValue(str): show bandwidth数据 + + Returns: + dict: 包含bandwitdh信息的字典 + """ + validate_type('strValue', strValue, str) + + # BANDWIDTH: UP 20000 DOWN 20000 + regexExp = 'BANDWIDTH:\s*UP\s*(\d+)\s*DOWN\s*(\d+)' + + ret = { } + + for line in strValue.splitlines(): + match = regexExp.match(line) + if match: + usPir, dsPir = match.group() + ret['UP'] = value(usPir) + ret['DOWN'] = value(dsPir) + + return ret + +def extract_onu_layer3_rate_limit_profile(strValue): + """提取show onu layer3-ratelimit-profile数据 + + Args: + strValue (str): show onu layer3-ratelimit-profile数据 + + Returns: + list: 包含字典的列表 + """ + # ------Offline onu layer3 rate-limiting info------ + # Wan index: 1. + # Wan name: 1_INTERNET_B_VID_1000. + # Up bandwidth profile id: 3. + # Down bandwidth profile id: 3. + + # Wan index: 2. + # Wan name: 2_INTERNET_B_VID_1000. + # Up bandwidth profile id: 65535. + # Down bandwidth profile id: 65535. + validate_type('strValue', strValue, str) + + regexExp = re.compile('(.+)\s*:\s*(.+).') + + ret = [ ] + for line in strValue.splitlines(): + match = regexExp.match(line) + if match: + k, v = match.groups() + k = k.strip() + v = v.strip() + if k == 'Wan index': + ret.append({ }) + + ret[-1][k] = value(v) + + return ret + +def extract_service_vlan(strValue): + """提取show service-vlan数据 + + Args: + strValue (str): show service-vlan数据 + + Returns: + list: 包含字典的列表 + """ + # servicevlan 101 : + # name : test, type : data + # vlan range: 100 ~ 400 #####end. + # servicevlan 102 : + # name : sip, type : voip + # vlan range: 3990 #####end. + validate_type('strValue', strValue, str) + + vlanIndexExp = re.compile('servicevlan\s+(\d+)\s+:\s*') + nameTypeExp = re.compile('name\s+:\s+(.+),\s+type\s+:\s+(.+)\s*') + svlanExp = re.compile('vlan\s+range:\s+(\d+|\d+\s+~\s+\d+)\s+#####end.') + + ret = [ ] + for line in strValue.splitlines(): + match = vlanIndexExp.match(line) + if match: + ret.append({ }) + ret[-1]['servicevlan'] = value(match.groups()[0].strip()) + continue + + match = nameTypeExp.match(line) + if match: + ret[-1]['name'] = value(match.groups()[0].strip()) + ret[-1]['type'] = value(match.groups()[1].strip()) + continue + + match = svlanExp.match(line) + if match: + ret[-1]['vlan range'] = value(match.groups()[0].strip()) + continue + + return ret + +def extract_onu_qinq_classification_profile(strValue): + """提取show onuqinq-classification-profile数据 + + Args: + strValue (str): show onuqinq-classification-profile数据 + + Returns: + list: 包含字典的列表 + """ + # ------------------QinQ profile [add2000] information------------------ + # Index: 1 + # Type: Source MAC Address Value: 00 00 00 00 00 00 Operator: Exist then match + # ------------------QinQ profile [xx] information------------------ + # Index: 2 + # Type: Source MAC Address Value: 00 00 00 00 00 00 Operator: No exist then match + # Type: Source MAC Address Value: 00 00 00 00 00 00 Operator: No exist then match + # Type: Source MAC Address Value: 00 00 00 00 00 00 Operator: No exist then match + # Type: Source MAC Address Value: 00 00 00 00 00 00 Operator: No exist then match + # Type: Source MAC Address Value: 00 00 00 00 00 00 Operator: No exist then match + # Type: Source MAC Address Value: 00 00 00 00 00 00 Operator: No exist then match + # Type: Source MAC Address Value: 00 00 00 00 00 00 Operator: No exist then match + # Type: Source MAC Address Value: 00 00 00 00 00 00 Operator: No exist then match + validate_type('strValue', strValue, str) + + profileNameExp = re.compile('-+.+\[(.+)\].+-+') + profileIndexExp = re.compile('Index: (\d+)') + profileFieldValueOpExp = re.compile('Type:\s+(.+)\s+Value:\s+(.+)\s+Operator:\s+(.+)') + + ret = [] + for line in strValue.splitlines(): + match = profileNameExp.match(line) + if match: + ret.append({ }) + ret[-1]['name'] = match.groups()[0].strip() + continue + + match = profileIndexExp.match(line) + if match: + ret[-1]['index'] = value(match.groups()[0].strip()) + continue + + match = profileFieldValueOpExp.match(line) + if match: + f, v, o = match.groups() + + f = type_str_to_int(f.strip()) + v = onu_value_str_to_str(v.strip(), f) + o = op_str_to_int(o.strip()) + + fvoList = ret[-1].get('fieldValueOps', [ ]) + fvoList.append((f, v, o)) + ret[-1]['fieldValueOps'] = fvoList + continue + + return ret + +def extract_olt_qinq_domain(strValue): + """提取show oltqinq-domain / index 数据 + + Args: + strValue (str): show oltqinq-domain / index 数据 + + Returns: + dict: 包含信息的字典 + """ + # ------------------QinQ domain [4_8_0536626403] information------------------ + # Domain index: 7 Service num: 1 + + # Service type: 0 Service ID: 1 + # Service[1] upstream rule: + # Type[02] val[00 00 00 00 00 00 00 00] opt[5] + # Service[1] downstream rule: + # Type[02] val[00 00 00 00 00 00 00 00] opt[5] + # Service[1] vlan information: + # Layer 1: oldvlan[129] oldcos[2] action[2] tpid[0x8100] cos[0] newvlan[1041] + # Layer 2: oldvlan[0] oldcos[0] action[3] tpid[0x8100] cos[255] newvlan[65535] + # Layer 3: oldvlan[65535] oldcos[255] action[3] tpid[0x8100] cos[255] newvlan[65535] + # Layer 4: oldvlan[65535] oldcos[255] action[3] tpid[0x8100] cos[255] newvlan[65535] + validate_type('strValue', strValue, str) + + qinqNameExp = re.compile('-+.+\[(.+)\].+-+') + qinqIndexAndSvcIndexExp = re.compile('Domain index:\s*(\d+)\s*Service num:\s*(\d+)\s*') + serviceTypeAndIDExp = re.compile('Service type:\s*(\d+)\s*Service ID:\s*(\d+)\s*') + serviceIndexAndRuleExp = re.compile('Service\[(\d+)\] (\w+) rule:') + typeValueOpExp = re.compile('Type\[(\d+)\]\s+val\[(.+)\]\s+opt\[(\d+)\]') + serviceVlanInfoExp = re.compile('Service\[(\d+)\] vlan information:') + layerExp = re.compile('Layer\s*(\d+):\s*oldvlan\[(.+)\]\s*oldcos\[(.+)\]\s*action\[(.+)\]\s*tpid\[(.+)\]\s*cos\[(.+)\]\s*newvlan\[(.+)\]') + + # { + # 'name': 'profile-name', + # 'index': 'profile-index', + # 'count': 'service-count', + # 'services': + # [ + # { + # 'no': 'service-no', + # 'type': 'service-type', + # 'rule': { + # 'upstream': [ + # + # ], + # + # 'downstream': [ + # ] + # }, + # 'vlan': [ + # (layerNo, oldvlan, oldcos, action, newtpid, newcos, newvlan), + # (layerNo, oldvlan, oldcos, action, newtpid, newcos, newvlan) + # ] + # } + # ] + # } + + ret = { } + + lastKey = None + for line in strValue.splitlines(): + match = qinqNameExp.match(line) + if match: + ret['name'] = value(match.groups()[0]) + continue + + match = qinqIndexAndSvcIndexExp.match(line) + if match: + ret['index'] = value(match.groups()[0]) + ret['count'] = value(match.groups()[1]) + ret['services'] = [ ] + continue + + match = serviceTypeAndIDExp.match(line) + if match: + ret['services'].append({ }) + ret['services'][-1]['no'] = value(match.groups()[1]) + ret['services'][-1]['type'] = 'single' if value(match.groups()[0]) == 0 else 'share' + ret['services'][-1]['rule'] = { } + ret['services'][-1]['vlan'] = [ ] + continue + + match = serviceIndexAndRuleExp.match(line) + if match: + no = value(match.groups()[0]) + upOrDownStream = value(match.groups()[1]) + assert no == ret['services'][-1]['no'] + ret['services'][-1]['rule'][upOrDownStream] = [ ] + lastKey = upOrDownStream + continue + + match = typeValueOpExp.match(line) + if match: + typeCode, rawValue, opCode = match.groups() + intType = value(typeCode) + strValue = olt_value_str_to_str(rawValue, intType) + intOp = value(opCode) + ret['services'][-1]['rule'][lastKey].append((intType, strValue, intOp)) + continue + + match = serviceVlanInfoExp.match(line) + if match: + no = value(match.groups()[0]) + assert no == ret['services'][-1]['no'] + continue + + match = layerExp.match(line) + if match: + layerNo, oldVlan, oldCos, action, newTpid, newCos, newVlan = match.groups() + if action == '1': + action = 'add' + elif action == '2': + action = 'translation' + elif action == '3': + action = 'transparent' + else: + raise ValueError('unknown action value: %s' % action) + + ret['services'][-1]['vlan'].append((value(layerNo), value(oldVlan, 65535), value(oldCos, 255), value(action), value(newTpid), value(newCos, 255), value(newVlan, 65535))) + continue + + return ret + +def extract_olt_qinq_domain_bound_info(strValue): + """提取show oltqinq-domain bound-info信息 + + Args: + strValue (str): show oltqinq-domain bound-info信息 + + Returns: + tuple: 返回(slot, portNo)元组 + """ + validate_type('strValue', strValue, str) + + lines = strValue.splitlines() + + ponBoundInfoExp = re.compile('Pon bound info: slot id: (\d+); pon id: (\d+).') + + for line in lines: + match = ponBoundInfoExp.match(line) + if match: + slot, portNo = match.groups() + return value(slot), value(portNo) + + assert False, '没有找到绑定信息: %s' % strValue + +def extract_system_time(strValue): + """提取show time命令的信息 + + Args: + strValue (str): show time命令信息 + + Returns: + tuple: date, time的元组。如'2020-11-23', '17:27:45' + """ + # Now time is: + # Current Date is 2020-11-23 + # Current Time is 17:27:45 + # System running time is 6 day 21:34:39 + dateExp = re.compile('Current\s+Date\s+is\s+(\d{4,4}-\d{2,2}-\d{2,2})') + timeExp = re.compile('Current\s+Time\s+is\s+(\d{2,2}:\d{2,2}:\d{2,2})') + + lines = strValue.splitlines() + date, time = '', '' + for line in lines: + match = dateExp.match(line) + if match: + date = match.groups()[0] + continue + + match = timeExp.match(line) + if match: + time = match.groups()[0] + continue + + return date.strip(), time.strip() + +def extract_pppoe_plus(strValue): + """提取show pppoe-plus state命令的信息 + + Args: + strValue (str): show pppoe-plus state命令信息 + + Returns: + dict: 包含PPPoE-Plus状态信息的字典 + """ + validate_type('strValue', strValue, str) + + stateExp = re.compile('(.+):(.+)') + + lines = strValue.splitlines() + + ret = { } + for line in lines: + match = stateExp.match(line) + if match: + k, v = match.groups() + ret[value(k)] = value(v) + + return ret + +def extract_ip_address(strValue): + """抽取show ip address命令的信息 + + Args: + strValue (str): show ip address命令返回的信息 + + Returns: + tuple: 返回ip与mask组成的元组 + """ + validate_type('strValue', strValue, str) + + lines = strValue.splitlines() + + ipExpr = re.compile('debugip\s+(\d+\\.\d+\\.\d+\\.\d+)') + maskExpr = re.compile('mask\s+(\d+\\.\d+\\.\d+\\.\d+)') + + ip, mask = None, None + for line in lines: + match = ipExpr.search(line) + if match: + ip = value(match.groups()[0]) + + match = maskExpr.search(line) + if match: + mask = value(match.groups()[0]) + + if ip == None or mask == None: + raise RuntimeWarning('未找到IP/MASK信息') + + return (ip, mask) + +def extract_acl(strValue): + """抽取show acl命令的信息 + + Args: + strValue (str): show acl命令返回的信息 + + Returns: + list: 返回包含ACL信息的列表 + """ + validate_type('strValue', strValue, str) + + titles = ['No', 'IP', 'Mask', 'Status'] + + valueExpr = re.compile('(\d+)\s+(\d+\\.d+\\.d+\\.d+)\s+(\d+\\.d+\\.d+\\.d+)\s+(\w+)') + lines = strValue.splitlines() + + ret = [ ] + for line in lines: + match = valueExpr.match(line) + if match: + ret.append({ }) + for t, v in zip(titles, match.groups()): + ret[value(t)] = value(v) + + return ret + +def extract_snmp_time(strValue): + """抽取show snmp-time命令的信息 + + Args: + strValue (str): show snmp-time显示的信息 + + Returns: + dict: 包含ip和interval信息的字典 + """ + validate_type('strValue', strValue, str) + + intervalExpr = re.compile('INTERVAL=(\d+)') + ipExpr = re.compile('Server\s+IP\s+:\s+(\d+\\.\d+\\.\d+\\.\d+)') + + lines = strValue.splitlines() + + ip, interval = None, None + for line in lines: + match = intervalExpr.search(line) + if match: + interval = value(match.groups()[0]) + + match = ipExpr.search(line) + if match: + ip = value(match.groups()[0]) + + if ip == None or interval == None: + raise RuntimeWarning('未找到IP和Interval信息') + + return { 'ip': ip, 'interval': interval } + +def extract_current_alarm(strValue): + """抽取show alarm current命令的信息 + + Args: + strValue (str): show alarm current显示的信息 + + Returns: + list: 包含信息的字典 + """ + validate_type('strValue', strValue, str) + # TODO: FIXME: 抽取告警信息没有实现 + titleExpr = re.compile('\s*(Item Description)\s+(Code vOLT)\s+(Object)\s+(Begintime)\s+(Endtime)\s*') + valueExpr = re.compile('???') + + lines = strValue.splitlines() + + ret = [ ] + titles = None + for line in lines: + match = titleExpr.match(line) + if match != None: + titles = match.groups() + + match = valueExpr.match(line) + if match != None: + values = match.groups() + ret.append({ }) + for title, value in zip(titles, values): + ret[-1][title] = value + + return ret + + +class OLTCLI_AN6K_17: + """OLTCLI类。通过它,可以对OLT进行操作,就像输入命令行调用一样。 + + """ + + def __init__(self, olt_dev): + """OLT构造函数。 + + Args: + olt_dev (Device): OLT设备资源 + """ + self.olt_dev = olt_dev + + def del_onu_caps_profile(self, name): + """删除ONU能力集模板 + + Args: + name (str): ONU能力集名称 + """ + validate_type('name', name, str) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('no onu caps-profile name %s' % name) + + def add_onu_caps_profile(self, name, onutype, pontype, onucapa, lan1g, lan10g, lan25g, lan2_5g, pots): + """配置ONU能力集 + + Args: + name (str): [description] + onutype (int]): ONU类型,范围10000-11023。 + pontype (int): PON类型,可取的值如下 + 263:1G EPON + 807:10G EPON 10G/10G + 808:10G EPON 1G/10G + 712:1G GPON + 813:10 GPON 2.5G/10G + 650:10GPON 10G/10G + 824: GPON/XGPON/XGSPON auto + 826: 25G PON 25G/25G + onucapa (int)): ONU类型,0:SFU 1:hgu 2:box mdu 3:card mdu 4:DPU + lan1g (int): 1G端口对应的端口号 + lan10g (int): 10G端口对应的端口号 + lan25g (int): 25G端口对应的端口号 + lan2_5g (int): 2.5G端口对应的端口号 + pots (int): pots端口号 + """ + validate_type('name', name, str) + validate_type('onutype', onutype, int) + validate_type('pontype', pontype, int) + validate_type('onucapa', onucapa, int) + validate_type('lan1g', lan1g, int) + validate_type('lan10g', lan10g, int) + validate_type('lan25g', lan25g, int) + validate_type('lan2_5g', lan2_5g, int) + validate_type('pots', pots, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('onu caps-profile add name %s onutype %s pontype %s onucapa %s lan1g %s lan10g %s lan25g %s lan2.5g %s pots %s end' % (name, onutype, pontype, onucapa, lan1g, lan10g, lan25g, lan2_5g, pots)) + + def get_snmp_time(self): + """获取SNMP时间配置 + + Returns: + dict: 包含时间配置参数的字典。interval,表示时间间隔;ip,表示SNMP对时的IP地址。 + """ + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + result = conn.run_cmd('show snmp-time') + + return extract_snmp_time(result) + + def set_snmp_time(self, interval, type, ip): + """设置SNMP相关参数 + + Args: + interval(int): SNMP校时间隔 + type(str): IP类型。有ipv4、ipv6、ipv4z、ipv6z和dns + ip(str): IP地址 + """ + validate_type('interval', interval, int) + validate_type('type', type, str) + validate_type('ip', ip, str) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('snmp-time interval %s servip %s %s' % (interval, type, ip)) + + def set_time_mode(self, mode, hour, min, ems_hour, ems_min): + """设置系统时间模式 + + Args: + mode (str): ne、snmp、ntp、sntp、ptp + hour (str): 时区,如 GMT+5:30、GMT+8 + min (int): 时区分钟 + ems-hour (str): EMS时区, 如 GMT+5:30、GMT+8 + ems-min (int): 时区分钟 + """ + validate_type('mode', mode, str) + validate_type('hour', hour, str) + validate_type('min', min, int) + validate_type('ems_hour', ems_hour, str) + validate_type('ems_min', ems_min, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('time %s hour %s min %s ems-hour %s ems-min %s' % (mode, hour, min, ems_hour, ems_min)) + + def set_time(self, year, month, day, time): + """设置系统时间 + + Args: + year (int): 年 + month (int): 月 + day (int): 日 + time (str): HH:MM:SS字符串的时间 + """ + validate_type('year', year, int) + validate_type('month', month, int) + validate_type('day', day, int) + validate_type('time', time, str) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('time %s %s %s %s' % (year, month, day, time)) + + def set_traffic_suppress(self, slot, rate, type='all'): + """设置卡的包抑制参数 + + Args: + slot (int): 槽位号 + rate (int): 速率 + type (str, optional): 抑制类型。支持broadcast、multicast、unknown和all。默认'all'。 + """ + validate_type('slot', slot, int) + validate_type('rate', rate, int) + validate_type('type', type, str) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('traffic-suppress 1/%s %s value %s' % (slot, type, rate)) + + def set_card_auth(self, slot, type): + """授权指定卡盘 + + Args: + slot (int): 槽位号 + type (str): 卡类型 + """ + validate_type('slot', slot, int) + validate_type('type', type, str) + + type = self.get_card_type(slot) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('card auth 1/%s %s' % (slot, type)) + + def set_card_auto_auth(self): + """对卡进行自动授权 + """ + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('card auto-auth') + + def get_card_type(self, slot): + """查询卡的类型 + + Args: + slot (int): 槽位号 + + Returns: + str: 卡的类型 + """ + validate_type('slot', slot, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + result = conn.run_cmd('show card info') + + entires = extract_card_info(result) + for entry in entires: + if entry['CARD'] == slot: + if entry['DETECT'] != '---': + return entry['DETECT'] + else: + break + + raise RuntimeWarning('未查询到卡的类型') + + def unset_card_auth(self, slot): + """取消卡的授权 + + Args: + slot (int): 卡的槽位号 + """ + validate_type('slot', slot, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('card unauth 1/%s' % slot) + + def set_acl(self, ip, mask , status): + """设置ACL,即允许或者禁止访问的IP网段 + + Args: + ip (str): IP地址 + mask (str): 点分格式的子网掩码 + status (str): enable或disable + + Returns: + int: 返回设置的ID号 + """ + validate_type('ip', ip, str) + validate_type('mask', mask, str) + validate_type('status', status, str) + + # 找一个空闲的ID + id = None + for entry in self.get_acl(): + if entry['IP'] == '0.0.0.0' and entry['Mask'] == '0.0.0.0' and entry['Status'] == 'disable': + id = entry['No'] + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('acl %s ip %s mask %s %s' % (id, ip, mask, status)) + + return id + + def get_acl(self, id = None): + """获取ACL配置信息 + + Args: + id (int, optional): 指定了ID,就返回指定的,否则返回所有的。 + + Returns: + list或dict: 返回包含{No, IP, Mask, Status}字典的列表,或返回单个字典 + """ + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + result = conn.run_cmd('show acl') + + if id == None: + return extract_acl(result) + else: + for entry in extract_acl(result): + if entry['No'] == id: + return entry + + raise RuntimeWarning('未能找到指定ID的ACL信息') + + def del_static_route(self, hop, ip, mask, metric = None): + """删除静态路由 + + Args: + hop (str): 下一跳地址 + ip (str, optional): 目的IP + mask (str或int, optional): 子网掩码,接受点格式的,或者数字长度格式的 + metric (int): 下一跳的metric,默认None,不提供 + """ + validate_type('hop', hop, str) + validate_type('ip', ip, str) + + if type(mask) != str and type(mask) != int: + raise RuntimeWarning('只接受点分格式或者长度格式的掩码') + + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + if metric == None: + conn.run_cmd('no static-route destination-ip %s mask %s nexthop %s' % (ip, mask, hop)) + else: + conn.run_cmd('no static-route destination-ip %s mask %s nexthop %s metric %s' % (ip, mask, hop, metric)) + + def set_static_route(self, hop, ip = '0.0.0.0', mask = '0.0.0.0', metric = 0): + """配置静态路由 + + Args: + hop (str): 下一跳地址 + ip (str, optional): 目的IP, 默认0.0.0.0 + mask (str或int, optional): 子网掩码,接受点格式的,或者数字长度格式的, 默认0.0.0.0 + metric (int): 下一跳的metric,默认0 + """ + validate_type('hop', hop, str) + validate_type('ip', ip, str) + + if type(mask) != str and type(mask) != int: + raise RuntimeWarning('只接受点分格式或者长度格式的掩码') + + validate_type('metric', metric, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('static-route destination-ip %s mask %s nexthop %s metric %s' % (ip, mask, hop, metric)) + + def set_manage_vlan(self, name, svlan, cvlan): + """设置带内管理VLAN + + Args: + svlan (int): SVLAN,外层VLAN + cvlan (int): CVLAN,内层VLAN + """ + validate_type('name', name, str) + validate_type('svlan', svlan, int) + validate_type('cvlan', cvlan, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('manage-vlan %s svlan %s cvlan %s' % (name, svlan, cvlan)) + + def set_manage_vlan_ip(self, version, name, ip, mask): + """设置带内管理IP + + Args: + version (str): IPv4或IPv6 + name (str): 先前设置的带内管理VLAN名称 + ip (str): IP地址 + mask (int或str): 子网掩码,如,255.255.255.0,或子网掩码长度,24 + """ + validate_type('version', version, str) + validate_type('name', name, str) + validate_type('ip', ip, str) + + if type(mask) == str: + mask = len_of_mask(mask) + if type(mask) == int: + pass + else: + raise RuntimeWarning('mask类型非法,只接受str或int类型') + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('manage-vlan %s %s %s/%s' % (version, name, ip, mask)) + + def set_ip_address(self, ip, mask): + """为OLT设置带外管理IP地址 + + Args: + ip (str): OLT IP地址 + mask (str): OLT 子网掩码 + """ + validate_type('ip', ip, str) + validate_type('mask', mask, str) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface meth 1') + try: + conn.run_cmd('ip address %s mask %s' % (ip, mask)) + except ConnectionResetError as cr: + logging.getLogger().debug('当调整OLT的管理IP时,会导致连接断开,这是正常的。需要约3秒恢复。') + + # 等待连接恢复 + time.sleep(5) + + ipR, maskR = self.get_ip_address() + if ipR != ip or maskR != mask: + raise RuntimeWarning('设置带外管理IP地址失败') + + def get_ip_address(self): + """获取OLT带外管理IP地址 + + Returns: + tuple: (ip, mask)组成的元组 + """ + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface meth 1') + result = conn.run_cmd('show ip address') + + return extract_ip_address(result) + + def get_system_time(self): + """返回OLT上面的系统时间。等同执行show time命令。 + + Returns: + datetime: datetime类型的系统时间 + """ + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + result = conn.run_cmd('show time') + + date, time = extract_system_time(result) + + return parse('%s %s' % (date, time)) + + def get_authorization(self): + """获取所有授权的ONU。等同于执行show authorization命令。 + + Returns: + 列表: 包含ONU授权信息的字典列表 + """ + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + result = conn.run_cmd('show authorization') + + return extract_authorization(result) + + def get_onu_position(self, sn): + """根据ONU SN查询ONU的槽位号和端口号 + + Args: + sn (str): ONU SN号 + + Raises: + RuntimeWarning: 查不到该ONUID对应信息时,抛出异常 + + Returns: + tuple: 返回(slot, port)元组,如果查不到抛出异常 + """ + validate_type('sn', sn, str) + + authInfo = self.get_authorization() + for info in authInfo: + if info['PhyId'] == sn: + return info['Slot'], info['Pon'] + + onuInfo = self.get_discovery() + for info in onuInfo: + if info['PhyId'] == sn: + return info['SLOT'], info['PON'] + + logging.getLogger().warning('查不到该ONU(%s)对应的槽位号和端口号,请检查ONU是否发现' % sn) + logging.getLogger().debug('authorization:\n %s' % authInfo) + logging.getLogger().debug('discovery:\n %s' % onuInfo) + raise RuntimeWarning('查不到该ONU(%s)对应的槽位号和端口号,请检查ONU是否发现' % sn) + + def get_onu_last_online_time(self, sn): + """获取ONU最近一次上线时间。等同执行show onu last-reg-status-change 命令。 + + Args: + sn (str): ONU SN + + Returns: + datetime: 查不到ONUID抛异常。查到了,但是无最后一次上线时间(一般从未上线),返回None。正常情况下,返回datetime格式的上线时间。 + """ + validate_type('sn', sn, str) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + result = conn.run_cmd('show onu last-reg-status-change %s' % self.get_onu_id(sn)) + + dictList = extract_last_reg_status_change(result) + + return dictList[0]['LAST_ON_TIME'] + + def get_onu_last_offline_time(self, sn): + """获取ONU最近一次下线时间。等同执行show onu last-reg-status-change 命令。 + + Args: + sn (str): ONU SN + + Returns: + datetime: 查不到ONUID抛异常。查到了,但是无最后一次下线时间(一般从未下线),返回None。正常情况下,返回datetime格式的最近一次的下线时间。 + """ + validate_type('sn', sn, str) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + result = conn.run_cmd('show onu last-reg-status-change %s' % self.get_onu_id(sn)) + + dictList = extract_last_reg_status_change(result) + + return dictList[0]["LAST_OFF_TIME"] + + def is_onu_online(self, sn): + """检查ONU是否在线。等同执行命令show authorization,然后检查其中的OST字段是否为up。 + + Args: + sn (str): ONUD SN + + Returns: + bool: True,在线;False,不在线。 + """ + validate_type('sn', sn, str) + + values = self.get_authorization() + + for value in values: + if value["PhyId"] == sn: + if value["OST"] == "up": + return True + elif value["OST"] == "dn": + return False + + logging.getLogger().warning('查不到该ONU(%s)状态信息,请检查ONU是否进行过授权' % sn) + raise RuntimeWarning('查不到该ONU(%s)状态信息,请检查ONU是否进行过授权' % sn) + + def reset_onu(self, sn, wait = True): + """重置ONU。等同执行onu reset 命令。 + + Args: + sn (str): ONU SN号 + wait (bool, optional): 重置ONU后,等待重新上线。 + """ + validate_type('sn', sn, str) + validate_type('wait', wait, bool) + + if not self.is_onu_online(sn): + raise RuntimeWarning('无法重置离线状态下的ONU') + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + conn.run_cmd('onu reset %s' % self.get_onu_id(sn)) + + def isOffline(): + return not self.is_onu_online(sn) + + wait_for_true(isOffline, 1, 30) + + if wait: + def isOnline(): + return self.is_onu_online(sn) + + wait_for_true(isOnline, 1, 180) + + def reset_all_onu(self, wait = True): + """重置所有ONU。等同执行onu reset all命令。不同于resetONU,不会验证是否重启和重新上线。 + + Args: + wait (bool, optional): 重置ONU后,等待重新上线。 + """ + validate_type('wait', wait, bool) + + # 获取所有在线的ONU对应的槽位号和端口号,以及下面挂的ONUID + stats = { } + for authInfo in self.get_authorization(): + key = (authInfo['Slot'], authInfo['Pon']) + if key not in stats.keys(): + stats[key] = set() + + if authInfo['OST'] == 'up': # 后面只验证重启时在线的ONU,因为有些ONU重启时可能本来就没上线。 + stats[key].add(authInfo['PhyId']) + + # 重启这些个ONU + for slotPort in stats.keys(): + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % slotPort) + # onu reset all命令存在bug,使用普通命令替代 + for sn in stats[slotPort]: + conn.run_cmd('onu reset %s' % self.get_onu_id(sn)) + + + # 等待下线 + def isOffline(): + ret = True + for key in stats.keys(): + for sn in stats[key]: + ret = ret and not self.is_onu_online(sn) + return ret + + wait_for_true(isOffline, 1, 30) + + if wait: + # 等待上线 + def isOnline(): + ret = True + for key in stats.keys(): + for sn in stats[key]: + ret = ret and self.is_onu_online(sn) + return ret + + wait_for_true(isOnline, 1, 180) + + def clear_whitelist(self): + """清空所有授权 + """ + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + for wlMode in [ WhitelistMode.phyid, WhitelistMode.logid, WhitelistMode.password ]: + onuInfos = self.get_whitelist(wlMode) + for onuInfo in onuInfos: + if wlMode in [ WhitelistMode.phyid, WhitelistMode.phyid_psw ]: + conn.run_cmd('no whitelist phy-id %s %s %s' % (onuInfo['Slot'], onuInfo['Pon'], onuInfo['Phy-ID'])) + + if wlMode in [ WhitelistMode.logid, WhitelistMode.logid_psw ]: + conn.run_cmd('no whitelist logic-id %s %s %s' % (onuInfo['Slot'], onuInfo['Pon'], onuInfo['Logic-Id'])) + + if wlMode in [ wlMode.password ]: + conn.run_cmd('no whitelist password %s %s %s' % (onuInfo['Slot'], onuInfo['Pon'], onuInfo['Phy-Pwd'])) + + # 验证 + for wlMode in [ WhitelistMode.phyid, WhitelistMode.logid, WhitelistMode.password ]: + onuInfos = self.get_whitelist(wlMode) + if len(onuInfos) != 0: + raise RuntimeWarning('%s的白名单没有清理干净' % (get_whitelist_query_str(wlMode))) + + def clear_pon_whitelist(self, slot, port): + """清空指定的槽位号和端口号下的所有类型的ONU授权列表。等同于执行no whitelist all。 + + Args: + slot (int): 槽位号 + port (init): 端口号 + """ + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % (slot, port)) + conn.run_cmd('no whitelist %s' % 'all') + + for wlMode in [ WhitelistMode.phyid, WhitelistMode.logid, WhitelistMode.password ]: + whiteList = self.get_pon_whitelist(slot, port, wlMode) + if len(whiteList) != 0: + raise RuntimeError('清空白名单(%s)失败' % wlMode) + + def get_pon_whitelist(self, slot, port, wlMode): + """获取指定槽位号和端口下的指定类型的白名单列表。等同于执行show whitelist命令。 + + Args: + slot (int): 槽位号 + port (int): 端口号 + wlMode (WhitelistMode): 白名单类型 + + Returns: + list: 包含授权信息的列表 + """ + validate_type('slot', slot, int) + validate_type('port', port, int) + validate_type('wlMode', wlMode, WhitelistMode) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % (slot, port)) + result = conn.run_cmd('show whitelist %s' % get_whitelist_query_str(wlMode)) + + return extract_whitelist(result) + + def get_whitelist(self, wlMode): + """读取白名单列表。等同于执行show whitelist命令。 + + Args: + wlMode (WhitelistMode): 指定要获取哪种白名单类型的列表。 + + Returns: + list: 包含授权字典信息的列表 + """ + validate_type('wlMode', wlMode, WhitelistMode) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + result = conn.run_cmd('show whitelist %s' % get_whitelist_query_str(wlMode)) + + return extract_whitelist(result) + + def is_in_whitelist(self, wlMode, id): + """检查ONU是否在对应白名单列表中。 + + Args: + wlMode (WhitelistMode): 要在哪个白名单列表中检查 + id (str): ONU的phyId、logId或passwd + + Returns: + bool: True,在白名单列表中;False,不在白名单列表中 + """ + validate_type('wlMode', wlMode, WhitelistMode) + validate_type('sn', id, str) + + id = value(id) + + onuInfos = self.get_whitelist(wlMode) + for onuInfo in onuInfos: + if wlMode in [ WhitelistMode.phyid, WhitelistMode.phyid_psw ]: + if onuInfo['Phy-ID'] == id: + return True + + if wlMode in [ WhitelistMode.logid, WhitelistMode.logid_psw ]: + if onuInfo['Logic-Id'] == id: + return True + + if wlMode in [ WhitelistMode.password ]: + if onuInfo['Phy-Pwd'] == id: + return True + + return False + + def is_onu_in_whitelist(self, id): + """验证ONU是否在白名单中 + + Args: + id (str): ONU的phyId、logId或passwd + """ + for wlMode in WhitelistMode: + if self.is_in_whitelist(wlMode, id): + return True + + return False + + def add_whitelist(self, wlMode, sn, onuId=None): + """将指定的sn号的ONU增加到指定白名单中。支持三种认证方式: sn、sn/pwd, pwd。增加白名单所需的信息自动去查ONU上报的信息。 + + Args: + wlMode (WhitelistMode): 要增加到哪个白名单 + sn (str): ONU的SN,加白名单所需的信息会自动去查 + onuid (int or None): 指定onuid,不指定自动分配。默认None,自动分配。 + """ + + # 物理授权 + # phy-id / phy-id + psw + # 逻辑授权 + # log-id / log-id + psw + # 密码授权 + # psw + validate_type('wlMode', wlMode, WhitelistMode) + validate_type('sn', sn, str) + if onuId != None: + validate_type('onuid', onuId, int) + + + # 配置所需的LogicId/Pwd, PhyId/Phy + onuDetailInfo = None + onuInfos = self.get_discovery() + for onuInfo in onuInfos: + if onuInfo['PhyId'] == sn: + onuDetailInfo = onuInfo + break + + if onuDetailInfo == None: + raise RuntimeWarning('未查到该ONU信息,无法进行有效配置') + + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + if wlMode == WhitelistMode.phyid: + if onuId == None: + conn.run_cmd('whitelist add phy-id %s' % (onuDetailInfo['PhyId'])) + else: + conn.run_cmd('whitelist add phy-id %s onuid %s' % (onuDetailInfo['PhyId'], onuId)) + + if not self.is_in_whitelist(wlMode, onuDetailInfo['PhyId']): + raise RuntimeWarning("白名单添加失败") + + if wlMode == WhitelistMode.phyid_psw: + if onuId == None: + conn.run_cmd('whitelist add phy-id %s checkcode %s' % (onuDetailInfo['PhyId'], onuDetailInfo['PhyPwd'])) + else: + conn.run_cmd('whitelist add phy-id %s checkcode %s onuid %s' % (onuDetailInfo['PhyId'], onuDetailInfo['PhyPwd'], onuId)) + + if not self.is_in_whitelist(wlMode, onuDetailInfo['PhyId']): + raise RuntimeWarning("白名单添加失败") + + if wlMode == WhitelistMode.logid: + if onuId == None: + conn.run_cmd('whitelist add logic-id %s' % (onuDetailInfo['LogicId'])) + else: + conn.run_cmd('whitelist add logic-id %s onuid %s' % (onuDetailInfo['LogicId'], onuId)) + + if not self.is_in_whitelist(wlMode, onuDetailInfo['LogicId']): + raise RuntimeWarning("白名单添加失败") + + if wlMode == WhitelistMode.logid_psw: + if onuId == None: + conn.run_cmd('whitelist add logic-id %s checkcode %s' % (onuDetailInfo['LogicId'], onuDetailInfo['LogicPwd'])) + else: + conn.run_cmd('whitelist add logic-id %s checkcode %s onuid %s' % (onuDetailInfo['LogicId'], onuDetailInfo['LogicPwd'], onuId)) + + if not self.is_in_whitelist(wlMode, onuDetailInfo['LogicId']): + raise RuntimeWarning("白名单添加失败") + + if wlMode == WhitelistMode.password: + if onuId == None: + conn.run_cmd('whitelist add password %s' % (onuDetailInfo['PhyPwd'])) + else: + conn.run_cmd('whitelist add password %s onuid %s' % (onuDetailInfo['PhyPwd'], onuId)) + + if not self.is_in_whitelist(wlMode, onuDetailInfo['PhyPwd']): + raise RuntimeWarning("白名单添加失败") + + def del_whitelist(self, wlMode, id): + """从指定白名单里删除指定的ONU + + Args: + wlMode (Whitelist): 要从哪个白名单里面删除 + id (str): ONU的phyId、logId或passwd + """ + validate_type('wlMode', wlMode, WhitelistMode) + validate_type('sn', id, str) + + if not self.is_in_whitelist(wlMode, id): + return + + onuInfos = self.get_whitelist(wlMode) + with Connection.get(self.olt_dev) as conn: + + conn.run_cmd('config') + + for onuInfo in onuInfos: + if wlMode in [ WhitelistMode.phyid, WhitelistMode.phyid_psw ] and id == onuInfo['Phy-ID']: + conn.run_cmd('no whitelist phy-id %s %s %s' % (onuInfo['Slot'], onuInfo['Pon'], id)) + + if wlMode in [ WhitelistMode.logid, WhitelistMode.logid_psw ] and id == onuInfo['Logic-Id']: + conn.run_cmd('no whitelist logic-id %s %s %s' % (onuInfo['Slot'], onuInfo['Pon'], onuInfo['Logic-Id'])) + + if wlMode in [ WhitelistMode.password ] and id == onuInfo['Phy-Pwd']: + conn.run_cmd('no whitelist password %s %s %s' % (onuInfo['Slot'], onuInfo['Pon'], onuInfo['Phy-Pwd'])) + + if self.is_in_whitelist(wlMode, id): + raise RuntimeWarning('删除指定白名单中的ONU失败') + + def del_from_whitelist(self, id): + """将指定的ONU从白名单中移除 + + Args: + id (str): ONU的phyId、logId或passwd + """ + for wlMode in WhitelistMode: + self.del_whitelist(wlMode, id) + + def get_auto_discover(self, slot, port): + """获取ONU自动发现设置。等同于执行show onu auto-discover。 + + Args: + slot (int): 槽位号 + port (int): 端口号 + + Returns: + list : 返回(slot, portNo, status, agingTime)元组组成的列表 + """ + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + result = conn.run_cmd('show onu auto-discover 1/%s/%s' % (slot, port)) + + return extract_auto_discover(result) + + def get_pon_auto_discover(self, slot, port): + """获取指定槽位号和端口下的ONU自动发现设置。等同于执行show onu auto-discover。 + + Args: + slot (int): 槽位号 + portNo (int): 端口号 + + Returns: + tuple: (status, agingTime)元组 + """ + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % (slot, port)) + result = conn.run_cmd('show onu auto-discover') + + return extract_pon_auto_discover(result) + + def set_auto_discover(self, where, status, agingTime): + """设置ONU自动发现时间。等同于执行onu auto-discover命令。 + + Args: + where (str): 或者'all' + status (str): enable或者disable + agingTime (int): 发现时间 + """ + validate_type('where', where, str) + validate_type('status', status, str) + validate_type('agingTime', agingTime, int) + validate_int_range(agingTime, 0, 3600) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('onu auto-discover %s %s %s' % (where, status, agingTime)) + + def set_pon_auto_discover(self, slot, port, status, agingTime): + """设置指定槽位号、端口号下的ONU自动发现设置。等同于执行onu auto-discover命令。 + + Args: + slot (int): 槽位号 + port (int): 端口号 + status (str): enable或者disable + agingTime (int): 发现时间 + """ + validate_type('slot', slot, int) + validate_type('port', port, int) + validate_type('status', status, str) + validate_type('agingTime', agingTime, int) + validate_int_range('agingTime', 0, 3600) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % (slot, port)) + conn.run_cmd('onu auto-discover %s %s' % (status, agingTime)) + + def get_discovery(self): + """查询自动发现的ONU。等同于执行show discovery命令。 + + Returns: + list: 返回自动发现的ONU信息列表 + """ + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + result = conn.run_cmd('show discovery') + + ret = extract_discovery(result) + + return ret + + def get_pon_discovered(self, slot, port): + """查询自动发现的ONU。等同于执行show onu discovered命令。 + + Args: + slot (int): 槽位号 + port (int): 端口号 + + Returns: + list: 返回自动发现的ONU信息列表 + """ + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % (slot, port)) + result = conn.run_cmd('show onu discovered') + + ret = extract_discovery(result) + + return ret + + def get_manage_vlan(self, name = None): + """获取所有管理VLAN。等同于执行show manage-vlan all命令 + + Args: + name (str, optional): 要查询的管理VLAN名称。如果为None,则返回所有管理VLAN。默认为None。 + + Returns: + list或dict: 包含管理VLAN信息字典的列表,或指定VLAN信息的字典。 + """ + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + result = conn.run_cmd('show manage-vlan all') + + vlanList = extract_manage_vlan(result) + if name == None: + return vlanList + else: + for vlan in vlanList: + if vlan['Manage name'] == name: + return vlan + raise RuntimeWarning('未找到%s名称的管理VLAN信息' % name) + + def set_auth_mode(self, slot, port, mode): + """设置指定端口的授权模式。等同于执行port authentication-mode命令。 + + Args: + slot (int): 槽位号 + port (int): 端口号 + mode (AuthMode): 授权模式 + """ + validate_type('slot', slot, int) + validate_type('port', port, int) + validate_type('mode', mode, AuthMode) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('port authentication-mode 1/%s/%s mode %s' % (slot, port, mode.value)) + + assert self.get_auth_mode(slot, port) == mode + + def get_auth_mode(self, slot = None, port = None): + """获取指定端口的授权模式。等同于执行show port authentication-mode命令 + + Args: + slot (int, optional): 槽位号。默认None,获取所有端口的授权信息。 + port (int, optional): 端口号。默认None,获取所有端口的授权信息。 + + Returns: + dict或AuthMode: 获取所有授权信息时,返回(slot, port)为键,AuthMode为值的字典。获取个别端口端口的授权模式时,返回AuthMode。 + """ + if (slot, port) != (None, None): + validate_type('slot', slot, int) + validate_type('port', port, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + if (slot, port) == (None, None): + result = conn.run_cmd('show port authentication-mode all') + return extract_port_authentication_mode(result) + else: + result = conn.run_cmd('show port authentication-mode select 1/%s/%s' % (slot, port)) + dictRet = extract_port_authentication_mode(result) + return dictRet[slot, port] + + def set_dhcp_option(self, option, enable=True): + """设置DHCP选项开关。等同于执行dhcp option18/option37/option82/patch命令。 + + Args: + option (DhcpOption): dhcp option选项 + enable (bool, optional): 是否打开。默认为True,打开。 + """ + validate_type('option', option, DhcpOption) + validate_type('enable', enable, bool) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('dhcp %s %s' % (option.value, bool_to_str(enable))) + + def get_dhcp_option(self): + """获取dhcp选项开关状态。等同于执行show dhcp state命令。 + + Return: + dict, 包含dhcp状态信息的字典。 + """ + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + result = conn.run_cmd('show dhcp state') + + ret = extract_dhcp_state(result) + + return ret + + def set_pppoe_plus(self, enable=True): + """设置PPPoE+选项状态。等同于执行pppoe-plus enable/disable命令。 + + Args: + enable (bool, optional): 默认为True,打开PPPoE+开关。 + """ + validate_type('enable', enable, bool) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('pppoe-plus %s' % bool_to_str(enable)) + + def get_pppoe_plus(self): + """获取PPPoE+选项状态。等同于执行show pppoe-plus state命令。 + + return: + bool: True使能, False未使能。 + """ + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + result = conn.run_cmd('show pppoe-plus state') + + ret = extract_pppoe_plus(result) + + return str_to_bool(ret['PPPoE+']) + + def get_onu_sn(self, slot, port, onuId): + """给定ONUID,查找其SN号。等同于执行show authorization,从里面查找对应关系。 + + Args: + slot(int): 槽位号 + port(int): 端口号 + onuId (int): ONU ID + + Return: + str: 找到,返回ONU SN;没找到,返回None。 + """ + validate_type('onuId', onuId, int) + + authList = self.get_authorization() + for info in authList: + if info['Onu'] == onuId and info['Slot'] == slot and info['Pon'] == port: + return info['PhyId'] + + return None + + def get_onu_id(self, sn): + """给定SN,查找ONUID。等同于执行show authorization,从里面查找对应关系。 + + Args: + sn (str): onu sn + + Return: + int : 找到返回ONU ID, 没找到,返回None。 + """ + validate_type('sn', sn, str) + + authList = self.get_authorization() + for info in authList: + if info['PhyId'] == sn: + return info['Onu'] + + return None + + def set_onu_port_vlan_tls(self, sn, eth, index, tls): + """设置ONU Port Vlan TLS特性 + + Args: + sn (str): ONU SN + eth (int): ONU 端口号 + index (int): ONU Service 索引号 + tls (bool): 是否启用tls,True,启用,False,不启用 + """ + validate_type('sn', sn, str) + validate_type('eth', eth, int) + validate_type('index', index, int) + validate_type('tls', tls, bool) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + conn.run_cmd('onu port vlan %s eth %s service %s tls %s' % (self.get_onu_id(sn), eth, index, bool_to_str(tls))) + + def get_onu_port_vlan(self, sn): + """读取ONU的Port Vlan业务设置。等同于执行命令show onu port vlan。 + + Args: + sn (str): 要显示的ONU的SN + + Return: + list : 元组列表。 + """ + validate_type('sn', sn, str) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + result = conn.run_cmd('show onu port vlan %s' % self.get_onu_id(sn)) + + ret = extract_onu_port_vlan(result) + + return ret + + def get_onu_port_status(self, sn): + """获取ONU端口状态。等同于执行命令show onu port status。 + + Args: + sn (str): ONU SN + + Returns: + list: 端口状态列表 + """ + validate_type('sn', sn, str) + + if not self.is_onu_online(sn): + raise RuntimeWarning('无法查询离线状态下的ONU端口状态') + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('terminal length 0') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + result = conn.run_cmd('show onu port status %s' % self.get_onu_id(sn)) + + ret = extract_onu_port_status(result) + + return ret + + def clear_onu_port_vlan(self, sn, eth='all'): + """清理指定ONUID下的Port Vlan设置。等同于执行no onu port vlan命令。 + + Args: + sn (str): ONU SN + eth (str或int): 要清理的网口 + """ + validate_type('sn', sn, str) + if eth != 'all': + validate_type('eth', eth, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + if eth == 'all': + if not self.is_onu_online(sn): + raise RuntimeWarning('ONU不在线,无法查询其端口数') + ethCount = len(self.get_onu_port_status(sn)['PORT']) + for eth in range(1, ethCount + 1): + conn.run_cmd('no onu port vlan %s eth %s' % (self.get_onu_id(sn), eth)) + else: + conn.run_cmd('no onu port vlan %s eth %s' % (self.get_onu_id(sn), eth)) + + def get_onu_port_vlan_service_count(self, sn, eth): + """获取ONU Port VLAN业务个数。 + + Args: + sn (str): ONU SN + eth (int): 要获取的ONU对应的网口 + + Returns: + int: 返回业务个数 + """ + validate_type('sn', sn, str) + validate_type('eth', eth, int) + + + portVlanList = self.get_onu_port_vlan(sn) + + def filterEth(item): + + if item['PORT'] == eth: + return True + + return False + + i = 0 + for _ in filter(filterEth, portVlanList): + i = i + 1 + return i + + def set_onu_port_vlan_service_count(self, sn, eth, count): + """设置ONU Port VLAN业务个数。等同于执行onu port vlan命令。 + + Args: + sn (str): ONU SN + eth (int): 要设置的ONU对应的网口 + count (int): 要设置的业务个数 + """ + validate_type('sn', sn, str) + validate_type('eth', eth , int) + validate_type('count', count, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + conn.run_cmd('onu port vlan %s eth %s service count %s' % (self.get_onu_id(sn), eth, count)) + + def set_onu_port_vlan_service_type(self, sn, eth, index, type): + """"设置ONU Port VLAN业务类型。等同于执行onu port vlan命令。 + + Args: + sn (str): ONU SN + eth (int): 网口的索引值,从1开始。 + index (int): 业务的索引值,从1开始。 + type (str): unicast,表示单播; multicast,表示多播。 + """ + validate_type('sn', sn, str) + validate_type('eth', eth, int) + validate_type('index', index, int) + validate_type('type', type, str) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + conn.run_cmd('onu port vlan %s eth %s service %s type %s' % (self.get_onu_id(sn), eth, index, type)) + + def set_onu_port_vlan_service_vlan(self, sn, eth, index, rule): + """设置ONU Port Vlan业务 + + 设置方法示例: + setONUPortVlanServiceVlan('FHTT000aae64', 1, 1, ('tag', 0, 33024, 1000)) + setONUPortVlanServiceVlan('FHTT000aae64', 1, 1, ('transparent', 0, 33024, 1000)) + setONUPortVlanServiceVlan('FHTT000aae64', 1, 1, ('translate', 'enable', 0, 33024, 1000)) + setONUPortVlanServiceVlan('FHTT000aae64', 1, 1, ('translate', 'disable', 0, 33024, 1000)) + setONUPortVlanServiceVlan('FHTT000aae64', 1, 1, ('qinq', 'enable', 0, 33024, 1000, 'qinqClsProfile', 'serviceProfile')) + + Args: + sn (str): ONU SN + eth (int): 网口的索引值,从1开始。 + index (int): 业务的索引值,从1开始。 + rule (tuple): LAN业务参数, 以元组的方式提供。 + """ + validate_type('sn', sn, str) + validate_type('eth', eth, int) + validate_type('index', index, int) + validate_type('rule', rule, tuple) + + # pvlan格式化模板 + pvlanFormat = '%s priority %s vid %s' + # tag格式化模板 + tagFormat = '%s priority %s tpid %s vid %s' + # translate格式化模板 + translateEnableFormat = '%s %s priority %s tpid %s vid %s' + translateDisableFormat = '%s %s' + # transparent格式化模板 + transparentFormat = '%s priority %s tpid %s vid %s' + # qinq格式化模板 + qinqEnableFormat = '%s %s priority %s tpid %s vid %s %s %s' + qinqDisableFormat = '%s %s' + + mode = rule[0] + if mode == 'pvlan': + ruleString = pvlanFormat % rule + elif mode == 'tag': + ruleString = tagFormat % rule + elif mode == 'translate': + if rule[1] == 'enable': + ruleString = translateEnableFormat % rule + else: + assert rule[1] == 'disable' + ruleString = translateDisableFormat % rule + elif mode == 'transparent': + ruleString = transparentFormat % rule + elif mode == 'qinq': + if rule[1] == 'enable': + ruleString = qinqEnableFormat % rule + else: + assert rule[1] == 'disable' + ruleString = qinqDisableFormat % rule + else: + raise ValueError('未知VLAN设置参数:%s' % str(rule)) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + conn.run_cmd('onu port vlan %s eth %s service %s %s' % (self.get_onu_id(sn), eth, index, ruleString)) + + def del_onu_port_vlan_service(self, sn, eth, index): + """删除指定ONU下面指定网口的指定业务 + + Args: + sn (str): ONU SN + eth (int): 要删除的网口 + index (int): 要删除的业务ID + """ + validate_type('sn', sn, str) + validate_type('eth', eth, int) + validate_type('index', index, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + conn.run_cmd('no onu port vlan %s eth %s service %s' % (self.get_onu_id(sn), eth, index)) + + def set_onu_port_vlan_service_classification(self, sn, eth, index, ruleList): + """设置ONU端口业务区分规则 + + Args: + sn (str): ONU SN + eth (int): 网口的索引值,从1开始。 + index (int): 业务的索引值,从1开始。 + ruleList(list): 规则清单, (类型,操作,值,方向)元组组成的列表 + """ + validate_type('sn', sn, str) + validate_type('eth', eth, int) + validate_type('index', index, int) + validate_type('ruleList', ruleList, list) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + for rule in ruleList: + type_, op_, value_, direction_ = rule + cmd2run = 'onu port vlan %s eth %s service %s %s %s %s %s' % (self.get_onu_id(sn), eth, index, direction_.value, type_.value, value_, op_.value) + conn.run_cmd(cmd2run) + + def set_igmp_vlan(self, vlan): + """设置组播VLAN。等同于执行igmp vlan命令 + + Args: + vlan (int或str): VLAN ID。str类型的会自动转换为int类型。 + """ + if type(vlan) == str: + vlan = value(vlan) + validate_type('vlan', vlan, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('igmp') + conn.run_cmd('igmp vlan %s' % vlan) + + def get_igmp_vlan(self, vlan): + """获取组播VLAN设置。等同于执行命令show igmp vlan。 + + Args: + vlan (int或str): 要查询的组播VLAN ID。 + + Returns: + dict: 组播VLAN信息字典。 + """ + if type(vlan) == str: + vlan = value(vlan) + + validate_type('vlan', vlan, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('igmp') + result = conn.run_cmd('show igmp vlan %s' % vlan) + + return extract_igmp_vlan(result) + + def set_igmp_mode(self, mode): + """设置组播模式。等同于执行命令igmp mode。 + + Args: + mode (IGMPMode或str): 要设置的组播模式。str类型的参数会自动转换为IGMPMode类型。 + """ + #做一次转换 + if type(mode) == str: + mode = IGMPMode(mode) + + validate_type('mode', mode, IGMPMode) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('igmp') + conn.run_cmd('igmp mode %s' % mode.value) + + ret = self.get_igmp_mode() + try: + if ret['IGMP/MLD Mode'] != mode.value: + raise RuntimeWarning('设置%s组播模式失败' % mode.value) + except KeyError as ke: + logging.getLogger().error(ret) + raise RuntimeWarning('验证组播模式设置是否成功时,出现异常,无法IGMP/MLD Mode项') + + def get_igmp_mode(self): + """获取组播模式信息。等同于执行命令show igmp mode。 + + Return: + dict: 包含组播模式信息的字典。 + """ + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('igmp') + result = conn.run_cmd('show igmp mode') + + ret = extract_igmp_mode_info(result) + return ret + + def set_port_vlan(self, vlan, tag=None, slot=None, port=None): + """设置上联口端口VLAN。如果已经设置了某个VLAN,需要先删除再设置,否则会失败。等同于执行port vlan命令。 + + Args: + vlan (str或int): 要设置的VLAN范围。如,1000,或1000 to 2000. + tag (str): tag,数据出去时不剥离标签; untag,数据出去时剥离标签。 + slot (int): 要设置的槽位号。如,9。不指定,则设置所有端口 + port (str): 要设置的端口号。如,'2',或'2, 3'。不指定,则设置所有端口 + """ + vlan = str(vlan) + + validate_type('vlan', vlan, str) + validate_type('tag', tag, str) + validate_type('slot', slot, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + if slot == None and port == None and tag == None: + conn.run_cmd('port vlan %s allslot' % (vlan)) + else: + conn.run_cmd('port vlan %s %s 1/%s %s' % (vlan, tag, slot, port)) + + def get_port_vlan(self, slot, port): + """查询指定槽位和端口的Port VLAN信息。等同于执行show port vlan命令。 + + Args: + slot (int): 要查的槽位号。 + port (int): 要查的端口号。 + + Returns: + list: 包含VLAN信息的元组列表。 + """ + validate_type('slot', slot, int) + validate_type('port', port, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + result = conn.run_cmd('show port vlan 1/%s/%s' % (slot, port)) + + return extract_port_vlan(result) + + def del_port_vlan(self, vlan, slot, port): + """删除上联口端口VLAN。等同于执行no port vlan命令。 + + Args: + vlan (str): 要删除的VLAN范围。如,1000,或1000 to 2000. + slot (int): 要设置的槽位号。如,9 + port (str): 要设置的端口号。如,'2',或'2, 3'。 + """ + + validate_type('vlan', vlan, str) + validate_type('slot', slot, int) + + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + try: + conn.run_cmd('no port vlan %s 1/%s %s' % (vlan, slot, port)) + except RuntimeWarning as rw: + logging.getLogger().debug('不存在该Port VLAN配置') + + def set_onu_wan_cfg(self, **kargs): + """配置ONU WAN设置。等同于执行onu wan-cfg命令。 + + Args: + onuid (int): 要设置的ONU ID + index (int): 要设置的wan cfg的index + mode (WanMode): 要设置的wan模式 + type (WanType): 要设置的wan类型 + wvid (int): WAN VLAN ID + wcos (int): WAN COS + nat (str): 是否使能NAT,enable使能,disable去使能 + qos (str): 是否使能QoS,enable使能,disable去使能 + vlanmode (str, optional): 支持tag和transparent + tvlan (str, optional): 是否翻译VLAN,enable翻译,disable不翻译 + tvid (int, optional): TVLAN ID + tcos (int, optional): TVLAN COS + qinq (str, optional): 是否使能QinQ, enable使能,disable去使能 + stpid (int, optional): QinQ设置中的SVLAN的TPID + svlan (int, optional): QinQ设置中的SVLAN ID + scos (int, optional): QinQ设置中的SVLAN的COS + dsp (DSPMode): DSP模式 + remoteid (str, optional): DSP模式为dhcp-remoteid时,指定的remoteid + ip (str, optional): DSP模式为static时,指定的ip地址 + mask (str, optional): DSP模式为static时,指定的mask + gate (str, optional): DSP模式为static时,指定的gateway + master (str, optional): DSP模式为static时,指定的主DNS + slave (str, optional): DSP模式为static时,指定的从DNS + proxy (str, optional): PPPoE模式下,是否使能代理模式,enable使能,disable去使能 + username (str, optional): PPPoE模式下,鉴权的账号名 + password (str, optional): PPPoE模式下,鉴权的密码 + servername (str, optional): PPPoE模式下,PPPoE服务名 + pppoemode (PPPoEMode, optional): PPPoE模式下,PPPoE模式,支持auto/payload/manual三种 + active (str, optional): 是否active,enable使能,disable去使能 + servicetype (int, optional): 业务类型 + upnp (str, optional): 是否使能uPnP功能,enable使能,disable去使能 + fe (list, optional): 要绑定的网口。字符串列表形式提供,如,['fe1', 'fe2'] + ssid (str, optional): 要绑定的SSID。字符串列表形式提供,如,['ssid1', 'ssid2'] + """ + validate_key(kargs, 'onuId', int) + validate_key(kargs, 'index', int) + validate_key(kargs, 'mode', WanMode) + validate_key(kargs, 'type', WanType) + validate_key(kargs, 'wvid', int) + validate_key(kargs, 'wcos', int) + validate_key(kargs, 'nat', str) + validate_key(kargs, 'qos', str) + + wanCfgCMDPart1 = 'onu wan-cfg %s index %s mode %s type %s %s %s nat %s qos %s ' % \ + ( + kargs['onuId'], + kargs['index'], + kargs['mode'].value, + kargs['type'].value, + kargs['wvid'], + kargs['wcos'], + kargs['nat'], + kargs['qos'] + ) + + wanCfgCMDPart2 = ' ' + if 'vlanmode' in kargs.keys(): + validate_key(kargs, 'vlanmode', str) + validate_key(kargs, 'tvlan', str) + validate_key(kargs, 'tvid', int) + validate_key(kargs, 'tcos', int) + + wanCfgCMDPart2 = 'vlanmode %s tvlan %s %s %s ' % \ + ( + kargs['vlanmode'], + kargs['tvlan'], + kargs['tvid'], + kargs['tcos'] + ) + + if 'qinq' in kargs.keys(): + validate_key(kargs, 'qinq', str) + validate_key(kargs, 'stpid', int) + validate_key(kargs, 'svlan', int) + validate_key(kargs, 'scos', int) + + wanCfgCMDPart2 = 'qinq %s %s %s %s ' % \ + ( + kargs['qinq'], + kargs['stpid'], + kargs['svlan'], + kargs['scos'] + ) + + wanCfgCMDPart3 = ' ' + validate_key(kargs, 'dsp', DSPMode) + if kargs['dsp'] == DSPMode.pppoe: + validate_key(kargs, 'proxy', str) + validate_key(kargs, 'username', str) + validate_key(kargs, 'password', str) + validate_key(kargs, 'servername', str) + validate_key(kargs, 'pppoemode', PPPoEMode) + + wanCfgCMDPart3 = 'dsp %s proxy %s %s %s %s %s ' % \ + ( + kargs['dsp'].value, + kargs['proxy'], + kargs['username'], + kargs['password'], + kargs['servername'], + kargs['pppoemode'].value + ) + + elif kargs['dsp'] == DSPMode.dhcp: + pass + elif kargs['dsp'] == DSPMode.dhcp_remoteid: + validate_key(kargs, 'remoteid', str) + + wanCfgCMDPart3 = 'dsp %s %s ' % \ + ( + kargs['dsp'].value, + kargs['remoteid'] + ) + + elif kargs['dsp'] == DSPMode.static: + validate_key(kargs, 'ip', str) + validate_key(kargs, 'mask', str) + validate_key(kargs, 'gate', str) + validate_key(kargs, 'master', str) + validate_key(kargs, 'slave', str) + + wanCfgCMDPart3 = 'dsp %s ip %s mask %s gate %s master %s slave %s ' % \ + ( + kargs['dsp'].value, + kargs['ip'], + kargs['mask'], + kargs['gate'], + kargs['master'], + kargs['slave'] + ) + else: + pass + + wanCfgCMDPart4 = ' ' + if 'active' in kargs.keys(): + validate_type('active', kargs['active'], str) + wanCfgCMDPart4 = wanCfgCMDPart4 + 'active %s ' % kargs['active'] + + if 'servicetype' in kargs.keys(): + validate_type('servicetype', kargs['servicetype'], int) + wanCfgCMDPart4 = wanCfgCMDPart4 + 'service-type %s ' % kargs['servicetype'] + + if 'upnp' in kargs.keys(): + validate_type('upnp', kargs['upnp'], str) + wanCfgCMDPart4 = wanCfgCMDPart4 + 'upnp_switch %s ' % kargs['upnp'] + + if 'fe' in kargs.keys() or 'ssid' in kargs.keys(): + + if 'fe' not in kargs.keys(): + kargs['fe'] = [ ] + + if 'ssid' not in kargs.keys(): + kargs['ssid'] = [ ] + + wanCfgCMDPart4 = 'entries %s %s %s' % ( + len(kargs['fe']) + len(kargs['ssid']), + list_to_str(kargs['fe']), + list_to_str(kargs['ssid']), + ) + + cmd2Run = wanCfgCMDPart1 + wanCfgCMDPart2 + wanCfgCMDPart3 + wanCfgCMDPart4 + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(kargs['onuId'])) + conn.run_cmd(cmd2Run) + + def get_onu_wan_cfg(self, sn, index): + """读取指定ONU ID和WAN INDEX的配置。等同于执行命令show onu wan-cfg。 + + Args: + sn (str): ONU SN + index (int): 要获取的WAN index + + Returns: + dict: 包含配置信息的字典。字典的键名参考setONUWanCfg + """ + validate_type('sn', sn, str) + validate_type('index', index, int) + + with Connection.get(self.olt_dev) as conn: + + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + result = conn.run_cmd('show onu wan-cfg %s index %s' % (self.get_onu_id(sn), index)) + + return extract_wan_cfg(result) + + def del_onu_wan_cfg(self, sn, index): + """删除指定的ONU WAN配置。等同于执行命令no onu wan-cfg。 + + Args: + sn (str): 要删除的ONU SN + index (int): 要删除的WAN index + """ + validate_type('sn', sn, str) + validate_type('index', index, int) + + # 检查要删除的wan配置是否存在 + wanCfgRet = self.get_onu_wan_cfg(sn, index) + if None == wanCfgRet: + return + + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + + wanCfg = wanCfgRet.copy() + if len(wanCfg['fe']) != '0': + wanCfg['fe'] = [ ] + + if len(wanCfg['ssid']) != '0': + wanCfg['ssid'] = [ ] + + self.set_onu_wan_cfg(**wanCfg) + + conn.run_cmd('no onu wan-cfg %s index %s' % (self.get_onu_id(sn), index)) + + def get_onu_statistics(self, sn): + """获取ONU统计信息。等同于执行命令show onu statistics。 + + Args: + sn (str): ONU SN + + Returns: + dict: 包含ONU统计信息的字典。 + """ + validate_type('sn', sn, str) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + result = conn.run_cmd('show onu statistics %s' % self.get_onu_id(sn)) + + return extract_onu_statistics(result) + + def add_bandwidth_profile(self, name, usCir, usPir, usFir, dsCir, dsPir): + """增加Bandwidth Profile。等同于执行命令bandwidth-profile add。 + + Args: + name (str): Profile名称 + usCir (int): upstream committed information rate + usPir (int): upstream peak information rate + usFir (int): upstream fix information rate + dsCir (int): downstream committed information rate + dsPir (int): downstream peak information rate + """ + validate_type('name', name, str) + validate_type('usCir', usCir, int) + validate_type('usPir', usPir, int) + validate_type('usFir', usFir, int) + validate_type('dsCir', dsCir, int) + validate_type('dsPir', dsPir, int) + + # bandwidth profile name should not exist + assert self.query_bandwidth_profile_id_by_name(name) == None + + # add bandwidth profile + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + # conn.run_cmd('bandwidth-profile add %s upstream cir %s pir %s fir %s downstream cir %s pir %s' % (name, usCir, usPir, usFir, dsCir, dsPir)) + conn.run_cmd('bandwidth-profile add %s upstream-pir %s downstream-pir %s upstream-cir %s downstream-cir %s upstream-fir %s' % (name, usPir, dsPir, usCir, dsCir, usFir)) + + assert self.exist_bandwidth_profile(name, usCir, usPir, usFir, dsCir, dsPir) + + def not_exist_bandwidth_profile(self, nameOrId): + """检查Bandwidth Profile是否不存在。 + + Args: + nameOrId (str或int): Bandwidth Profile的名称或ID + + Returns: + bool: True,不存在;False,存在。 + """ + if type(nameOrId) != int and type(nameOrId) != str: + raise RuntimeWarning('非法的nameOrId类型') + + profiles = self.get_bandwidth_profile() + + for profile in profiles: + if type(nameOrId) == int and profile['Id'] == nameOrId: + return False + + if type(nameOrId) == str and profile['Name'] == nameOrId: + return False + + return True + + def exist_bandwidth_profile(self, nameOrId, usCir, usPir, usFir, dsCir, dsPir): + """检查Bandwidth Profile是否存在。 + + Args: + nameOrId (str或int): Bandwidth Profile的名称或ID + usCir (int): upstream committed information rate + usPir (int): upstream peak information rate + usFir (int): upstream fix information rate + dsCir (int): downstream committed information rate + dsPir (int): downstream peak information rate + + Returns: + bool: True,存在;False,不存在。 + """ + if type(nameOrId) != int and type(nameOrId) != str: + raise RuntimeWarning('非法的nameOrId类型') + + validate_type('usCir', usCir, int) + validate_type('usPir', usPir, int) + validate_type('usFir', usFir, int) + validate_type('dsCir', dsCir, int) + validate_type('dsPir', dsPir, int) + + profiles = self.get_bandwidth_profile() + + for profile in profiles: + if type(nameOrId) == int and profile['Id'] == nameOrId: + if profile['upMin'] == usCir \ + and profile['upMax'] == usPir \ + and profile['upFix'] == usFir \ + and profile['downMin'] == dsCir \ + and profile['downMax'] == dsPir: + return True + + if type(nameOrId) == str and profile['Name'] == nameOrId: + if profile['upMin'] == usCir \ + and profile['upMax'] == usPir \ + and profile['upFix'] == usFir \ + and profile['downMin'] == dsCir \ + and profile['downMax'] == dsPir: + return True + + return False + + def modify_bandwidth_profile(self, nameOrId, usCir, usPir, usFir, dsCir, dsPir): + """修改Bandwidth Profile。等同于执行bandwidth-profile modify命令。 + + Args: + nameOrId (str或int): Bandwidth Profile的名称或ID + usCir (int): upstream committed information rate + usPir (int): upstream peak information rate + usFir (int): upstream fix information rate + dsCir (int): downstream committed information rate + dsPir (int): downstream peak information rate + + """ + if type(nameOrId) != int and type(nameOrId) != str: + raise RuntimeWarning('非法的nameOrId类型') + + validate_type('usCir', usCir, int) + validate_type('usPir', usPir, int) + validate_type('usFir', usFir, int) + validate_type('dsCir', dsCir, int) + validate_type('dsPir', dsPir, int) + + + if type(nameOrId) == int: + nameOrId = 'id %s' % nameOrId + else: # type(nameOrId) == str + nameOrId = 'name %s' % nameOrId + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('bandwidth-profile modify %s upstream cir %s pir %s fir %s downstream cir %s pir %s' % (nameOrId, usCir, usPir, usFir, dsCir, dsPir)) + + assert self.exist_bandwidth_profile(nameOrId, usCir, usPir, usFir, dsCir, dsPir) + + def del_bandwidth_profile(self, nameOrId): + """删除Bandwidth Profile。等同于执行bandwidth-profile delete命令。 + + Args: + nameOrId (str或int): Bandwidth Profile的名称或ID + """ + if type(nameOrId) != int and type(nameOrId) != str: + raise RuntimeWarning('非法的nameOrId类型') + + prfId = None + if type(nameOrId) == int: + prfId = nameOrId + nameOrId = 'id %s' % nameOrId + else: + prfId = self.query_bandwidth_profile_id_by_name(nameOrId) + nameOrId = 'name %s' % nameOrId + + for onu in self.get_authorization(): + prof = self.get_onu_bandwidth_profile(onu['Onu']) + if prof['prfId'] == prfId: + self.clear_onu_bandwithd_profile(onu['Onu']) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('bandwidth-profile delete %s' % (nameOrId)) + + assert self.not_exist_bandwidth_profile(nameOrId) + + def get_bandwidth_profile(self, id='all'): + """获取指定ID的Bandwidth Profile。等同于show bandwidth-profile命令。 + + Args: + id (str, optional): 要获取的profile Id名称。默认为'all',即获取全部。 + + Returns: + list: 包含Bandwidth profile信息的列表 + """ + if type(id) == str: + assert id == 'all' + else: + assert type(id) == int + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + result = conn.run_cmd('show bandwidth-profile %s' % id) + + ret = extract_bandwidth_profile(result) + + return ret + + def query_bandwidth_profile_id_by_name(self, name): + """根据Profile 名称查询对应的ID。 + + Args: + name (str): 要查询Bandwidth Profile的名称 + + Returns: + int: Bandwidth Profile的ID。查不到,返回None。 + """ + validate_type('name', name, str) + + allProfiles = self.get_bandwidth_profile() + for profile in allProfiles: + if profile['Name'] == name: + return int(profile['Id']) + + return None + + def clear_bandwidth_profile(self): + """清理所有Bandwidth Profile。 + """ + profiles = self.get_bandwidth_profile() + + def delFunc(profile): + id = profile['Id'] + self.del_bandwidth_profile(id) + + run_by_thread_pool(delFunc, profiles, 5) + + def set_onu_bandwidth_profile(self, sn, profileIdOrName): + """为ONU关联带宽模板。等同于执行onu bandwidth-profile命令。 + + Args: + sn (str): ONU SN + profileIdOrName (int或str): 带宽模板的ID或者名称 + """ + validate_type('sn', sn, str) + assert type(profileIdOrName) == int or type(profileIdOrName) == str + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + + if type(profileIdOrName) == int: + strProfile = 'profile-id %s' % profileIdOrName + else: + strProfile = 'profile-name %s' % profileIdOrName + + conn.run_cmd('onu bandwidth-profile %s %s' % (self.get_onu_id(sn), strProfile)) + + + if type(profileIdOrName) == str: + id = self.query_bandwidth_profile_id_by_name(profileIdOrName) + else: + id = profileIdOrName + + ret = self.get_onu_bandwidth_profile(sn) + try: + assert ret['prfId'] == id + except KeyError as ke: + logging.getLogger().error(ret) + raise RuntimeWarning('带宽模板关联验证失败') + + def clear_onu_bandwithd_profile(self, sn): + """取消ONU带宽模板的关联。 + + Args: + sn (str): 要取消模板关联的ONU SN + """ + validate_type('sn', sn, str) + + self.set_onu_bandwidth_profile(sn, 0) + + ret = self.get_onu_bandwidth_profile(sn) + + try: + assert ret['prfId'] == 0 + except KeyError as ke: + logging.getLogger().error(ret) + raise RuntimeWarning('带宽模板关联取消失败') + + def get_onu_bandwidth_profile(self, sn): + """查询ONU关联的带宽模板信息。等同于执行命令。 + + Args: + sn (str): 要查询的ONU SN + + Returns: + dict: 包含ONU所关联带宽模板的信息。 + """ + validate_type('sn', sn, str) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + result = conn.run_cmd('show onu bandwidth %s' % self.get_onu_id(sn)) + + ret = extract_onu_bandwidth(result) + ret['prfId'] = ret['prfId'] + 1 + + return ret + + def set_onu_bandwidth(self, sn, usCir, usPir, usFir, dsPir): + """设置ONU带宽。等同于执行onu bandwidth命令。 + + Args: + sn (str): ONU SN + usCir (int): upstream committed information rate + usPir (int): upstream peak information rate + usFir (int): upstream fix information rate + dsPir (int): downstream peak information rate + """ + validate_type('sn', sn, str) + validate_type('usCir', usCir, int) + validate_type('usPir', usPir, int) + validate_type('usFir', usFir, int) + validate_type('dsPir', dsPir, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + conn.run_cmd('onu bandwidth %s upstream-pir %s downstream-pir %s upstream-cir %s upstream-fir %s' % (self.get_onu_id(sn), usPir, dsPir, usCir, usFir)) + + + ret = self.get_onu_bandwidth_profile(sn) + + actualUsCir = ret['upAssureBand'] + actualUsPir = ret['upMaxband'] + actualUsFir = ret['upFixband'] + actualDsPir = ret['downMaxband'] + + assert actualUsCir == usCir \ + and actualUsPir == usPir \ + and actualUsFir == usFir \ + and actualDsPir == dsPir + + def set_pon_bandwidth(self, slot, port, usPir, dsPir): + """设置PON口带宽。等同于执行bandwidth命令。 + + Args: + slot (int): 槽位号 + port (int): 端口号 + usPir (int): 上行Pir + dsPir (int): 下行Pir + """ + validate_type('slot', slot, int) + validate_type('port', port, int) + validate_type('usPir', usPir, int) + validate_type('dsPir', dsPir, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % (slot, port)) + if usPir != dsPir: + conn.run_cmd('bandwidth %s %s' % ('upstream', usPir)) + conn.run_cmd('bandwidth %s %s' % ('downstream', dsPir)) + else: + conn.run_cmd('bandwidth %s %s' % ('all', dsPir)) + + ret = self.get_pon_bandwidth(slot, port) + assert ret['UP'] == usPir and ret['DOWN'] == dsPir + + def get_pon_bandwidth(self, slot, port): + """查询PON口带宽。等同于执行show bandwidth命令。 + + Args: + slot (int): 槽位号 + port (int): 端口号 + + Returns: + dict: 含带宽信息的字典。 + """ + validate_type('slot', slot, int) + validate_type('port', port, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % (slot, port)) + result = conn.run_cmd('show bandwidth') + + ret = extract_bandwidth(result) + + return ret + + def set_onu_port_service_bandwidth(self, sn, eth, serviceIndex, usProfileId, dsProfileId): + """设置端口业务带宽模板。等同于执行onu port service-bandwidth命令。 + + Args: + sn (str): ONU SN + eth (int): 端口号 + serviceIndex (int): 业务Index + usProfileId (int): 上行带宽模板 + dsProfileId (int): 下行带宽模板 + """ + validate_type('sn', sn, str) + validate_type('eth', eth, int) + validate_type('serviceIndex', serviceIndex, int) + validate_type('usProfileId', usProfileId, int) + validate_type('dsProfileId', dsProfileId, int) + + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + conn.run_cmd('onu port service-bandwith %s eth %s service %s upstream-profile %s downstream-profile %s' % (self.get_onu_id(sn), eth, serviceIndex, usProfileId, dsProfileId)) + + def set_onu_port_policy(self, sn, eth, usEnable, usCir, usCbs, usEbs, dsEnable, dsCir, dsPir): + """设置ONU端口策略。等同于执行onu port policing命令。 + + Args: + sn (str): ONU SN + eth (int): 网口号 + usEnable (str): enable,使能上行策略;disable,去使能上行策略。 + usCir (int): 上行CIR + usCbs (int): 上行CBS + usEbs (int): 上行EBS + dsEnable (str): enable,使能下行策略;disable,去使能下行策略。 + dsCir (int): 下行CIR + dsPir (int): 下行PIR + """ + validate_type('sn', sn, str) + validate_type('eth', eth, int) + validate_type('usEnable', usEnable, str) + validate_type('usCir', usCir, int) + validate_type('usCbs', usCbs, int) + validate_type('usEbs', usEbs, int) + validate_type('dsEnable', dsEnable, str) + validate_type('dsCir', dsCir, int) + validate_type('dsPir', dsPir, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + conn.run_cmd('onu port policing %s eth %s upstream %s cir %s cbs %s ebs %s downstream %s cir %s pir %s' % (self.get_onu_id(sn), eth, usEnable, usCir, usCbs, usEbs, dsEnable, dsCir, dsPir)) + + def set_onu_layer3_rate_limit(self, sn, wanIndex, usProfileId, dsProfileId): + """设置ONU三层限速。等同于执行onu layer3-ratelimit-profile命令。 + + Args: + sn (str): ONU SN + wanIndex (int): WAN Index + usProfileId (int): 上行限速模板ProfileId + dsProfileId (int): 下行限速模板ProfileId + """ + validate_type('sn', sn, str) + validate_type('wanIndex', wanIndex, int) + validate_type('usProfileId', usProfileId, int) + validate_type('dsProfileId', dsProfileId, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + conn.run_cmd('onu layer3-ratelimit-profile %s %s upstream-profile-id %s downstream-profile-id %s' % (self.get_onu_id(sn), wanIndex, usProfileId, dsProfileId)) + + found = False + profiles = self.get_onu_layer3_rate_limit(sn) + for profile in profiles: + if profile['Wan index'] == wanIndex: + assert profile['Up bandwidth profile id'] == usProfileId if usProfileId != -1 else 65535 + assert profile['Down bandwidth profile id'] == dsProfileId if dsProfileId != -1 else 65535 + found = True + break + + assert found, '三层限速设置失败: %s' % profiles + + def get_onu_layer3_rate_limit(self, sn, state=None): + """获取ONU 3层限速配置信息。等同于执行show onu layer3-ratelimit-profile命令。 + + Args: + sn (str): ONU sn + state (str, optional): 查询哪种状态的限速. 默认为None,查询offline和online两种状态的限速信息。 + + Returns: + list: 包含限速信息的列表 + """ + validate_type('sn', sn, str) + if state != None: + validate_type('state', state, str) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % self.get_onu_position(sn)) + + onuId = self.get_onu_id(sn) + if state == None: + + + result1 = conn.run_cmd('show onu layer3-ratelimit-profile %s %s' % (onuId, 'offline')) + ret1 = extract_onu_layer3_rate_limit_profile(result1) + + result2 = conn.run_cmd('show onu layer3-ratelimit-profile %s %s' % (onuId, 'online')) + ret2 = extract_onu_layer3_rate_limit_profile(result2) + + ret = ret1 + ret2 + else: + result = conn.run_cmd('show onu layer3-ratelimit-profile %s %s' % (onuId, state)) + ret = extract_onu_layer3_rate_limit_profile(result) + + return ret + + def del_onu_layer3_rate_limit(self, sn, wanIndex): + """删除指定ONU的三层限速。等同于执行onu layer3-ratelimit-profile命令。 + + Args: + sn (str): ONU SN + wanIndex (int): WAN Index + """ + validate_type('sn', sn, str) + validate_type('wanIndex', wanIndex, int) + self.set_onu_layer3_rate_limit(sn, wanIndex, -1, -1) + + def set_service_vlan(self, name, vlan, vlan_type): + """设置业务VLAN + + Args: + name (str): 业务VLAN名称 + vlan (str): 业务VLAN范围。如 1000 - 2000, 或 1000。 + vlan_type (str): 业务VLAN类型。仅限'cnc', 'data', 'iptv', 'ngn', 'system', 'uplinksub', 'vod', 'voip'类型。 + """ + validate_type('name', name, str) + validate_type('vlan', vlan, str) + validate_type('type', vlan_type, str) + + ValidTypesList = [ 'cnc', 'data', 'iptv', 'ngn', 'system', 'uplinksub', 'vod', 'voip'] + assert vlan_type in ValidTypesList, '无效的业务VLAN类型' + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('service-vlan %s %s type %s' % (name, vlan.replace('-', ' to '), vlan_type)) + + assert self.exist_service_vlan(name, vlan, vlan_type) + + def get_service_vlan(self): + """获取业务VLAN信息。等同于执行show service-vlan命令。 + + Returns: + list: 包含业务VLAN信息字典的列表。 + """ + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + result = conn.run_cmd('show service-vlan') + + return extract_service_vlan(result) + + def del_service_vlan(self, name): + """删除业务VLAN信息。等同于执行no service-vlan命令。 + + Args: + name (str): 业务VLAN的名称。 + """ + validate_type('name', name, str) + + if not self.exist_service_vlan(name): + return + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('no service-vlan %s' % name) + + assert not self.exist_service_vlan(name) + + def clear_service_vlan(self): + """清空所有Service Vlan + """ + def delFunc(info): + self.del_service_vlan(info['name']) + + run_by_thread_pool(delFunc, self.get_service_vlan()) + + def exist_service_vlan(self, name, vlan_range=None, service_type=None): + """检查指定的业务VLAN是否存在。 + + Args: + name (str): 业务VLAN名称 + vlan_range (str, optional): 业务VLAN的范围. 默认为None,不指定要检查的范围。 + service_type (str, optional): 业务VLAN的类型. 默认为None,不指定要检查的业务类型。 + + Returns: + bool: True,所指定的业务VLAN,VLAN范围和类型存在; False,不存在。 + """ + validate_type('name', name, str) + if vlan_range != None: + validate_type('vlan', vlan_range, str) + if service_type != None: + validate_type('type', service_type, str) + + svlanList = self.get_service_vlan() + for svlanDict in svlanList: + if svlanDict['name'] == name: + if vlan_range != None: + expectedVlanList = re.split('\s*[~-]\s*', vlan_range) + actualVlanList = re.split('\s*[~-]\s*', str(svlanDict['vlan range'])) + if expectedVlanList != actualVlanList: + return False + else: + if service_type == None: + return True + else: + if service_type == svlanDict['type']: + return True + else: + return False + else: + return True + + return False + + def add_onu_qinq_classification_profile(self, name, fieldValueOpList): + """新增onuqinq-classfication-profile。 + + Args: + name (str): Profile名称 + fieldValueOpList (list): (field, value, op)元组列表。如,[(0, '000000000000', 4)]。 + """ + validate_type('name', name, str) + validate_type('fieldValueOpList', fieldValueOpList, list) + + if self.exist_onu_qinq_classification_profile(name): + logging.getLogger().warning('名为%s的onuqinq-classification-profile已经存在,将会覆盖它' % name) + + self._add_or_modify_onu_qinq_classification_profile(name, fieldValueOpList, op='add') + + def modify_onu_qinq_classification_profile(self, name, fieldValueOpList): + """修改onuqinq-classfication-profile。 + + Args: + name (str): Profile名称 + fieldValueOpList (list): (field, value, op)元组列表。如,[(0, '000000000000', 4)]。 + """ + validate_type('name', name, str) + validate_type('fieldValueOpList', fieldValueOpList, list) + + assert self.exist_onu_qinq_classification_profile(name) + + self._add_or_modify_onu_qinq_classification_profile(name, fieldValueOpList, op='modify') + + def _add_or_modify_onu_qinq_classification_profile(self, name, fieldValueOpList, op='add'): + """增加或修改onuqinq-classfication-profile。 + + Args: + name (str): Profile名称 + fieldValueOpList (list): (field, value, op)元组列表。如,[(0, '000000000000', 4)]。 + op (str, optional): 默认为'add',增加。还可以为'modify'。 + """ + validate_type('name', name, str) + validate_type('fieldValueOpList', fieldValueOpList, list) + validate_type('op', op, str) + + fieldValueOpStr = '' + for param in fieldValueOpList: + fieldValueOpStr = fieldValueOpStr + ' %s %s %s' % param + fieldValueOpStr = fieldValueOpStr.strip() + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('onuqinq-classification-profile %s %s %s' % (op, name, fieldValueOpStr)) + + assert self.exist_onu_qinq_classification_profile(name) + + def get_onu_qinq_classification_profile(self, name=None): + """查询onuqinq-classfication-profile。等同于执行show onuqinq-classification-profile命令。 + + Args: + name (str): 要查询Profile的名称 + + Returns: + list: 包含Profile信息字典的列表 + """ + if name != None: + validate_type('name', name, str) + + if name: + cmdStr = 'name %s' % name + else: + cmdStr = 'all' + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + result = conn.run_cmd('show onuqinq-classification-profile %s' % cmdStr) + + ret = extract_onu_qinq_classification_profile(result) + + return ret + + def del_onu_qinq_classification_profile(self, name): + """删除onuqinq-classfication-profile。等同于执行命令。 + + Args: + name (str): 要删除的Profile的名称 + """ + validate_type('name', name, str) + + if not self.exist_onu_qinq_classification_profile(name): + return + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('onuqinq-classification-profile delete %s' % name) + + assert not self.exist_onu_qinq_classification_profile(name) + + def exist_onu_qinq_classification_profile(self, name): + """检查onuqinq-classfication-profile的是否存在。 + + Args: + name (str): Profile名称。 + + Returns: + bool: True,存在;False,不存在。 + """ + validate_type('name', name, str) + + profiles = self.get_onu_qinq_classification_profile() + + for profile in profiles: + if profile['name'] == name: + return True + + return False + + def add_olt_qinq_domain(self, name): + """新增oltqinq-domain。等同于执行oltqinq-domain add命令。 + + Args: + name (str): oltqinq-domain的名称。 + """ + validate_type('name', name, str) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('oltqinq-domain add %s' % name) + + # verify set successfully + assert self.exist_olt_qinq_domain(name) + + def get_olt_qinq_domain(self, nameOrIndex): + """获取oltqinq-domain。等同于执行命令。 + + Args: + nameOrIndex (str或int): 要获取oltqinq-domain的名称。 + + Returns: + dict: 包含oltqinq-domain信息的字典。 + """ + assert type(nameOrIndex) == str or type(nameOrIndex) == int, "nameOrIndex只接受str或int类型" + + if type(nameOrIndex) == str and not nameOrIndex.isdigit(): + strCmdArgs = nameOrIndex + else: + strCmdArgs = "index %s" % nameOrIndex + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + result = conn.run_cmd('show oltqinq-domain %s' % strCmdArgs) + + return extract_olt_qinq_domain(result) + + def exist_olt_qinq_domain(self, name): + """检查指定的oltqinq-domain是否存在。 + + Args: + name (str): 要检查的oltqinq-domain域的名称。 + + Return: + bool: True,存在;False,不存在。 + """ + validate_type('name', name, str) + + profile = self.get_olt_qinq_domain(name) + if profile == None: + return False + + return True + + def set_olt_qinq_domain_service_count(self, name, count): + """设置oltqinq-domain服务数量。等同于执行oltqinq-domain modify命令。 + + Args: + name (str): 要设置的oltqinq-domain。 + count (int): 服务的数量。 + """ + validate_type('name', name, str) + validate_type('count', count, int) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('oltqinq-domain modify %s service-count %s' % (name, count)) + + # verify + profile = self.get_olt_qinq_domain(name) + assert len(profile['services']) == count + + def set_olt_qinq_domain_service_type(self, name, serviceIndex, type): + """设置oltqinq-domian业务类型。等同执行oltqinq-domain modify命令。 + + Args: + name (str): oltqinq-domain域名称 + serviceIndex (int): 业务的索引号 + type (str): 业务的类型。 + """ + validate_type('name', name, str) + validate_type('serviceIndex', serviceIndex, int) + validate_type('type', type, str) + + + profile = self.get_olt_qinq_domain(name) + assert profile != None, "oltqinq-domain不存在" + assert serviceIndex <= profile['count'], "业务索引号(%s)超出范围, 仅有%s条业务。" % (serviceIndex, profile['count']) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('oltqinq-domain modify %s service %s type %s' % (name, serviceIndex, type)) + + # NEED VERIFY + profile = self.get_olt_qinq_domain(name) + for service in profile['services']: + if service['no'] == serviceIndex and service['type'] == type: + return + + raise RuntimeWarning('设置oltqinq-domian业务类型失败') + + def del_olt_qinq_domain(self, name): + """删除oltqinq-domian域。 + + Args: + name (str): oltqinq-domain域名称。 + """ + validate_type('name', name, str) + + if not self.exist_olt_qinq_domain(name): + return + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('oltqinq-domain delete %s' % name) + + # verify + assert not self.exist_olt_qinq_domain(name) + + def set_olt_qinq_domain_stream_rules(self, name, serviceIndex, stream, ruleList): + """设置oltqinq-domian上下行流识别规则。等同于执行oltqinq-domain命令。 + + Args: + name (str): oltqinq-domian域名称 + serviceIndex (int): 业务索引号 + stream (str): 上行流还是下行流。上行,upstream;下行,downstream + ruleList (list): (field-id, value, condition)形式的元组列表 + """ + validate_type('name', name, str) + validate_type('serviceIndex', serviceIndex, int) + validate_type('stream', stream, str) + validate_type('ruleList', ruleList, list) + + # 将元组列表转换为字符串 + strRule = list_to_str(ruleList, 'field-id %s value %s condition %s') + + # 执行命令 + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('oltqinq-domain %s service %s classification %s %s' % (name, serviceIndex, stream, strRule)) + + # 验证 + profile = self.get_olt_qinq_domain(name) + serviceEntry = None + for service in profile['services']: + if service['no'] == serviceIndex: + serviceEntry = service + break + assert serviceEntry != None + + streamRules = serviceEntry['rule'][stream] + + for fvo, rule in zip(ruleList, streamRules): + assert fvo == rule, '%s != %s' % (fvo, rule) + + def set_olt_qinq_domain_stream_vlan(self, name, serviceIndex, vlanRuleList): + """设置oltqinq-domian VLAN规则。等同于执行oltqinq-domain命令。 + + Args: + name (str): oltqinq-domain域名称 + serviceIndex (int): 业务类型 + vlanRuleList (list): vlan规则的元组列表。如,[(1, 'null', 'null', 'transparent', '33024', 'null', 'null')]。几个参数分别对应vlan层数,用户vid,用户cos,动作,目标tpid,目标cos和目标vid。 + """ + validate_type('name', name, str) + validate_type('serviceIndex', serviceIndex, int) + validate_type('vlanRuleList', vlanRuleList, list) + + # 将规则列表转换为字符串 + strVlanRule = list_to_str(vlanRuleList, 'vlan %s user-vlanid %s user-cos %s %s tpid %s cos %s vlanid %s') + + # run command + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('oltqinq-domain %s service %s %s' % (name, serviceIndex, strVlanRule)) + + # verify + profile = self.get_olt_qinq_domain(name) + serviceEntry = None + for service in profile['services']: + if service['no'] == serviceIndex: + serviceEntry = service + break + assert serviceEntry != None + + vlanRules = serviceEntry['vlan'] + + for rule, vlanRule in zip(vlanRuleList, vlanRules): + assert rule == vlanRule, '%s != %s' % (rule, vlanRule) + + def bound_olt_qinq_domain(self, slot, port, name): + """绑定oltqinq-domain域。等同于执行oltqinq-domain命令。 + + Args: + slot (int): 要绑定的槽位号 + port (int): 要绑定的端口号 + name (str): 要绑定的QinQ域的名称 + """ + validate_type('slot', slot, int) + validate_type('port', port, int) + validate_type('name', name, str) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % (slot, port)) + conn.run_cmd('oltqinq-domain %s' % name) + + def unbound_olt_qinq_domain(self, slot, port, name): + """取消绑定oltqinq-domain。等同于执行no oltqinq-domain命令。 + + Args: + slot (int): 要取消绑定的槽位号 + port (int): 要取消绑定的端口号 + name (str): 要取消绑定的QinQ域的名称 + """ + validate_type('slot', slot, int) + validate_type('port', port, int) + validate_type('name', name, str) + + if not self.is_olt_qinq_domain_bound(slot, port, name): + return + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % (slot, port)) + conn.run_cmd('no oltqinq-domain %s' % name) + + def is_olt_qinq_domain_bound(self, slot, port, name): + """检查指定oltqinq-domain是否绑定。等同于执行show oltqinq-domain bound-info命令。 + + Args: + slot (int): 槽位号 + port (int): 端口号 + name (str): QinQ域名称 + Returns: + bool: True, 绑定;Flase,未绑定。 + """ + validate_type('slot', slot, int) + validate_type('port', port, int) + validate_type('name', name, str) + + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + conn.run_cmd('interface pon 1/%s/%s' % (slot, port)) + try: + result = conn.run_cmd('show oltqinq-domain bound-info %s' % name) + ret = extract_olt_qinq_domain_bound_info(result) + return ret == (value(slot), value(port)) + except AssertionError as ae: + return False + + def clear_olt_qinq_domain(self): + """清空所有OLTQinQDomain + """ + def delFunc(id): + try: + self.del_olt_qinq_domain(self.get_olt_qinq_domain(id)['name']) + except RuntimeWarning: + logging.getLogger().debug('要删除的OLTQinQDomain(%s)不存在' % id) + return + + logging.getLogger().debug('删除OLTQinQDomain(%s)成功' % id) + + run_by_thread_pool(delFunc, range(1,20001)) + + def get_current_alarm(self): + """获取OLT上面当前产生的告警 + """ + with Connection.get(self.olt_dev) as conn: + conn.run_cmd('config') + result = conn.run_cmd('show alarm current') + + ret = extract_current_alarm(result) + + return ret + + +__all__ = [ + + 'OLTCLI_AN6K_17' +] \ No newline at end of file diff --git a/oltcli/cli/common.py b/oltcli/cli/common.py new file mode 100644 index 0000000..5127199 --- /dev/null +++ b/oltcli/cli/common.py @@ -0,0 +1,151 @@ +''' +这里存放所有CLI模块所需的公共枚举值、函数 +''' + +from enum import Enum + +class IGMPMode(Enum): + """所有支持的IGMP模式 + """ + control = 'control' # 可控模式 + proxy_proxy = 'proxy-proxy' # 代理-代理模式 + snooping = 'snooping' # 侦听模式 + proxy_snooping = 'proxy-snooping' # 代理-侦听模式 + disable = 'disable' # 关闭模式 + +class DhcpOption(Enum): + """用于dhcp option18/37/82或patch enable/disable命令 + """ + option18 = 'option18' + option37 = 'option37' + option82 = 'option82' + patch = 'patch' + +class WhitelistMode(Enum): + """whitelist add命令所支持的所有授权模式。 + """ + phyid = 'phy-id' + phyid_psw = 'phy-id+psw' + logid = 'log-id' + logid_psw = 'log-id+psw' + password = 'password' + + +def get_whitelist_query_str(wlMode): + """根据WhitelistMode的类型,返回对应的查询白名单的字符 + + Args: + wlMode (WhitelistMode): 白名单模式 + """ + if wlMode in [ WhitelistMode.phyid, WhitelistMode.phyid_psw ]: + return 'phy-id' + + if wlMode in [ WhitelistMode.logid, WhitelistMode.logid_psw ]: + return 'logic-id' + + if wlMode in [ WhitelistMode.password ]: + return 'password' + + +class AuthMode(Enum): + """用于port authentication-mode 命令,设置授权模式 + """ + logid = 'log-id' + logid_psw = 'log-id+psw' + no_auth = 'no-auth' + password = 'password' + phyid_psw = 'phy-id+psw' + phyid_o_logid_psw_o_psw = 'phy-id/log-id+psw/psw' + phyid_o_logid_o_psw = 'phy-id/log-id/psw' + phyid_o_psw = 'phy-id/psw' + phyid = 'phyid' + +class WanMode(Enum): + + tr069 = 'tr069' + internet = 'internet' + tr069_internet = 'tr069-internet' + other = 'other' + multi = 'multi' + voip = 'voip' + voip_internet = 'voip-internet' + iptv = 'iptv' + radius = 'radius' + radius_internet = 'radius-internet' + unicast_iptv = 'unicast-iptv' + multicast_iptv = 'multicast-iptv' + +class WanType(Enum): + + bridge = 'bridge' + route = 'route' + +class DSPMode(Enum): + + dhcp = 'dhcp' + dhcp_remoteid ='dhcp-remoteid' + static = 'static' + pppoe = 'pppoe' + +class PPPoEMode(Enum): + + auto = 'auto' # 自动连接 + payload = 'payload' # 有流量的时候连接 + manual = 'manual' # 手动连接 + + +class Type(Enum): + """规则类型 + """ + sa = 'sa' # 基于SA MAC地址 + da = 'da' # 基于DA MAC地址 + sip = 'sip' # 基于源IP地址 + dip = 'dip' # 基于目的IP地址 + vid = 'vid' # 基于VLAN ID + sport = 'sport' # 基于L4的源Port + dport = 'dport' # 基于L4的目的Port + iptype = 'iptype' # 基于IP协议类型 + eth_type = 'eth_type' # 基于以太网 + tos = 'tos' # 基于IP ToS + priority = 'priority' # 基于以太网优先级 + daipv6pre = 'daipv6pre' # 基于目的IPv6地址前缀 + saipv6pre = 'saipv6pre' # 基于源IPv6地址前缀 + ipver = 'ipver' # 基于IP版本 + ipv6tra = 'ipv6tra' # 基于IPv6优先级字段分类 + ipv6fl = 'ipv6fl' # 基于IPv6流量标签 + ipv6nh = 'ipv6nh' # 基于下一包头(IPv6) + +class Operator(Enum): + """操作 + """ + equal = 0 + not_equal = 1 + less_than = 2 + greater_than = 3 + exist = 4 + not_exist = 5 + always = 6 + +class Direction(Enum): + """流的类型 + """ + upstream = 'upstream' # 上行流 + downstream = 'downstream' # 下行流 + +__all__ = [ + + 'IGMPMode', + 'DhcpOption', + 'WhitelistMode', + 'AuthMode', + 'WanMode', + 'WanType', + 'DSPMode', + 'PPPoEMode', + + 'Direction', + 'Type', + 'Operator', + + 'get_whitelist_query_str' +] \ No newline at end of file diff --git a/oltcli/conn.py b/oltcli/conn.py new file mode 100644 index 0000000..65cdd23 --- /dev/null +++ b/oltcli/conn.py @@ -0,0 +1,211 @@ +from abc import ABC, abstractmethod +from typing import NoReturn + +from telnetlib import Telnet +import logging + +class Connection(ABC): + """Connection Class + + Connection represents a connection with OLT + """ + def __enter__(self): + """to support with syntax + """ + self.connect() + return self + + def __exit__(self, exc_type, exc_value, exc_tb): + """to support with syntax + """ + self.disconnect() + + @abstractmethod + def connect(self) -> NoReturn: + """called when connect + """ + pass + + @abstractmethod + def disconnect(self) -> NoReturn: + """called when disconnect + """ + pass + + @abstractmethod + def run(self, cmd:str, **kwargs) -> str: + """called when run command through connection + + Args: + cmd (str): command to run + + Returns: + str: result in str + """ + pass + + +class OLTTelnet(Connection): + """OLT Telnet + + OLTTelnet represents a telnet connection with OLT + """ + + def __init__(self, ip:str, username:str, password:str, read_interval:int=1) -> NoReturn: + """init + + Args: + ip (str): OLT ip address + username (str): username for connection + password (str): password for connection + """ + # save information need for connection + self._ip = ip + self._username = username + self._password = password + + # save read interval + self._read_interval = read_interval + + # telnet client + self._telnet = None + + def connect(self): + """connect OLT with given information + """ + # close already opened telnet first + if self._telnet != None: + self._telnet.close() + self._telnet = None + + # connect telnet server + self._telnet = Telnet(self._ip, 23) + + self._login() + self._admin() + + # disable paging + self.run('terminal length 0') + + def _admin(self): + """enter admin mode + """ + assert self._telnet != None + + # input en + self._telnet.read_until(b"User>", self._read_interval).decode("ascii") + self._telnet.write("enable".encode('ascii') + b"\n") + + # input password + self._telnet.read_until(b"Password:", self._read_interval) + self._telnet.write(self._password.encode('ascii') + b"\n") + + def _login(self): + """login + """ + assert self._telnet != None + + # input username + self._telnet.read_until(b"Login:", self._read_interval) + self._telnet.write(self._username.encode('ascii') + b"\n") + + # input password + self._telnet.read_until(b"Password:", self._read_interval) + self._telnet.write(self._password.encode('ascii') + b"\n") + + def disconnect(self): + """disconnect with OLT + """ + if(self._telnet != None): + self._telnet.close() + self._telnet = None + + def run(self, cmd:str, append_return:bool=True, sepcial_end_mode:bool=False) -> str: + """run command through telnet connection + + Args: + cmd (str): command need to run + append_return (bool, optional): whether add RETURN at end of command, default is True + sepcial_end_mode (bool, optional): whether use a special way to end reading result, default is False + + Returns: + str: result to return + """ + if(self._telnet == None): + raise RuntimeError("need connect OLT first") + + # before run command, should read out last result in buffer + self._telnet.read_until(b"# ", self._read_interval).decode("ascii") + + # run command + if append_return: + cmdBytes = cmd.encode('ascii') + b"\r\n" + else: + cmdBytes = cmd.encode('ascii') + self._telnet.write(cmdBytes) + + # read result + output_data = [ ] + while(True): + + # read in every interval + data = self._telnet.read_until(b"# ", self._read_interval).decode("ascii") + output_data.append(data) + + # 这里是为了解决执行pingonu命令时,telnet卡住的问题 + # 每当读到round-trip(ms) min/avg/max信息时,发送回车,解决卡住不输出的问题 + if("round-trip(ms) min/avg/max" in data): + self._telnet.write("\r\n".encode("ascii")) + continue + + # 如果开启了特殊读取模式,则连续两次没有读到数据,退出循环 + if sepcial_end_mode and len(output_data) >= 2: + if output_data[-1] == "" \ + and output_data[-2] == "": + break + else: + # 否则,要读到提示符出现为止 + if("# " in data \ + or "User> " in data \ + or "Login: " in data \ + or "Password: " in data): + break + + # 对输出数据中的特殊字符处理 + output = "".join(output_data) + output = output.replace("--Press any key to continue Ctrl+c to stop--", "") + output = output.replace("\x08", "") + output = output.replace(" " * 48, "") # remove empty line + + # 去掉输出数据中的ansi escape sequence + # ansi escape sequence: https://en.wikipedia.org/wiki/ANSI_escape_code + output = output.replace("\x1b[19;05H ", "") + output = output.replace("\r\n \x1b[2J", "") + output = output.replace("\x1b[2J" ,"") + + + logging.getLogger().info(output) + + # 去掉输出数据中的第一行(输入命令)和最后一行(提示符) + data_without_inputcmd_and_prompt = output.split("\r\n")[1:-1] + + # 将输出数据使用回车换行连接成长字符串 + ret = "\r\n".join(data_without_inputcmd_and_prompt) + + # 检查输出结果中不应该存在命令执行失败的提示。 + if ret.find("Command executes failed.") != -1: + logging.getLogger().debug(output) + raise RuntimeWarning("命令'%s'执行结果中包含'Command executes failed.'" % cmd) + + if ret.find("% Unknown command.") != -1: + logging.getLogger().debug(output) + raise RuntimeWarning("命令'%s'执行结果中包含'% Unknown command.'" % cmd) + + return ret + + +__all__ = [ + + 'Connection' +] + diff --git a/oltcli/device.py b/oltcli/device.py new file mode 100644 index 0000000..44accfa --- /dev/null +++ b/oltcli/device.py @@ -0,0 +1,21 @@ +''' +OLT设备池 +''' + + +from devicepool import DevicePool + +from .utils import read_dict_list_from_csv + +olt_list = read_dict_list_from_csv('**/olt_list.csv') + +olt_pool = DevicePool(olt_list) + + +__all__ = [ + + 'olt_pool' +] + +if __name__ == "__main__": + print(olt_list) \ No newline at end of file diff --git a/oltcli/utils.py b/oltcli/utils.py new file mode 100644 index 0000000..5cfce6f --- /dev/null +++ b/oltcli/utils.py @@ -0,0 +1,835 @@ +import random +import re +import os +import sys +from ping_cmd import host +from types import FunctionType, MethodType +import time +import logging +import pyshark +import inspect +import shutil +from dateutil.parser import parse +from hashlib import md5 +import threadpool +import tempfile +import csv +import pathlib + +def value(strValue, maxValue=None): + """将字符串类型表示的值尽可能的转换成对应格式的类型。如,'1.0'转换成浮点型,'1'或'0xFF'转换成整型。 + + Args: + strValue (str or None): 要转换的字符串格式的值,为None时不转换,直接返回。 + maxValue (int, optional): 当指定该参数时,如果转换的int类型的值为maxValue将会返回'null'。如,指定65535,当值strValue为'65535'时,自动转换为'null',而非整数。 默认为None,不进行判定和转换。 + + Returns: + any: 转换后的值,'1'将为int类型的1, '1.0'将为float类型的1.0, '1920-10-1 10:11:11'转换为datetime格式,'hello'仍然为'hello' + """ + # 如果为None不做转换 + if strValue == None: + return strValue + else: + validate_type('strValue', strValue, str) + + # 去掉首尾空格 + strValue = strValue.strip() + + intExp = re.compile('^-?(0x|0X)?\d+$') # 匹配 10和16进制的±整数 + floatExp = re.compile('^-?\d+.\d+$') # 匹配 10进制的±浮点数 + datetimeExp = re.compile("\d{4,4}-\d{1,2}-\d{1,2}\s\d{1,2}:\d{1,2}:\d{1,2}") + + if intExp.match(strValue): + # 如果是整数类型 + intValue = int(strValue, 10 if strValue.find('0x') == -1 and strValue.find('0x') == -1 else 16) + if maxValue != None and intValue == maxValue: + # 当设置了达到最大值的选项,且满足时,返回'null' + return 'null' + else: + return intValue + elif floatExp.match(strValue): + # 如果是浮点类型 + return float(strValue) + elif datetimeExp.match(strValue): + # 如果是日期类型 + if strValue != '0000-00-00 00:00:00': + return parse(strValue) + else: + return None + else: + # 不识别字符串的不处理 + return strValue + +def validate_type(varName, varValue, expectedType): + """验证变量的类型是否满足要求。 + + Args: + varName (str): 变量名称 + varValue (any): 变量的值 + expectedType (any或list): 希望的类型 + """ + if type(expectedType) == list: + assert type(varValue) in expectedType, "%s应当为%s类型, 但为%s类型" % (varName, expectedType, type(varValue)) + else: + assert type(varValue) == expectedType, "%s应当为%s类型, 但为%s类型" % (varName, expectedType, type(varValue)) + +def validate_ip_addr(ip): + """验证IP地址是否可达。 + + Args: + ip (str): a.b.c.d格式的IP地址 + """ + + assert type(ip) == str, "IP地址应当为str类型,但为%s类型" % type(ip) + + assert is_valid_ipv4_addr(ip), "IP地址格式无效: %s" % ip + + assert is_ip_connectable(ip), "IP地址不可达: %s" % ip + +def validate_int_range(intValue, min, max): + """验证int值是否落在[min, max]区间中。 + + Args: + intValue (int): 要验证范围的值 + min (int): 最小值 + max (int): 最大值 + """ + assert min <= intValue and intValue <= max, "%s超出范围[%s, %s]" % (intValue, min, max) + +def validate_key(name, expectedKey, expectedType): + """验证字典是否包含指定类型的key + + Args: + name (dict): 要进行检查的字典 + expectedKey (Any): 要进行检查的键名 + expectedType (Any或list): 要确认键值的类型 + """ + + assert expectedKey in name.keys(), "字典中不包含该键:%s" % expectedKey + if type(expectedType) != list: + assert type(name[expectedKey]) == expectedType, "字典中该键值的类型不符合预期,预期%s,实际%s" % (expectedType, type(name[expectedKey])) + else: + assert type(name[expectedKey]) in expectedType, "字典中该键值的类型不符合预期,预期%s,实际%s" % (expectedType, type(name[expectedKey])) + +def move(file, dstDir): + """将文件移动到目标目录中。如果目录不存在则创建。 + + Args: + file (str): 文件路径 + dstDir (str): 目标目录路径 + + Returns: + str: 文件新的路径 + """ + file = os.path.abspath(file) + if not os.path.exists(file): + return + + absDstDir = os.path.abspath(dstDir) + + if not os.path.exists(os.path.dirname(absDstDir)): + os.mkdir(os.path.dirname(absDstDir)) + + if not os.path.exists(absDstDir): + os.mkdir(absDstDir) + shutil.move(file, absDstDir) + + return os.path.join(os.path.abspath(absDstDir), os.path.basename(file)) + +def merge(base, more): + """将more字典合并入base字典中,同名的key值将使用more覆盖base中的值。 + Args: + base (dict): 基准字典 + more (dict): 更新字典 + + Returns: + dict: 合并过后的字典 + """ + assert type(base) == dict and type(more) == dict + + b = dict(base) + u = dict(more) + + b.update(u) + + return b + +def expand_list(tupleList): + """将[(x, [y1, y2, y3]), ...]形态的列表展开为[(x, y1), (x, y2), (x, y3), ...]形态 + + Args: + tupleList (list): [(x, [y1, y2, y3]), ...]形态的列表 + + Returns: + list: [(x, y1), (x, y2), (x, y3), ...]形态的列表 + """ + ret = [ ] + for t in tupleList: + x, yList = t + for y in yList: + ret.append((x, y)) + + return ret + +def break_frame_slot_port(port): + """提取frame/slot/port字串中的slot和port信息。如,'1/9/2'提取为(1, 9 , 2) + Args: + strPort (str): frame/slot/port字串 + + Returns: + tuple: (frame, slot, port)元组 + """ + portExpr = re.compile('(\d+)/(\d+)/(\d+)') + match = portExpr.match(port) + assert None != match + f, s, p = match.groups() + + return value(f), value(s), value(p) + +def count_packets(pcapFile, filter): + """统计给定的Pcap文件中满足过滤器条件的个数 + + Args: + pcapFile (str): pcap/pcapng文件路径 + filter (str): Wireshark过滤表达式 + + Returns: + 满足条件的包的个数 + """ + validate_type('pcapFile', pcapFile, str) + validate_type('filter', filter, str) + + assert os.path.exists(pcapFile), "指定的文件不存在: %s" % pcapFile + + pkts = pyshark.FileCapture(pcapFile, display_filter=filter) + count = 0 + for pkt in pkts: + count = count + 1 + + pkts.close() + + return count + +def get_packets_info(pcapFile, attribute, filter = None, disableProtocol = 'rtcp'): + """从给定的Pcap文件中提取所需的信息 + + Args: + pcapFile (str): Pcap文件 + attributes (str或list): 要获取的属性值 + filter (str, optional): 过滤Pcap文件的过滤器 + disableProtocol (str): 要禁用解析的协议。有时候pyshark调用tshark对某些协议的内容强行进一步解析时,会出现Malformed Packet的错误,这个时候把这个出现错误的解析协议屏蔽掉,避免出现异常。 + + 示例: + 获取PPPoED中PADS消息携带的Session ID,同时,还会返回eth层的源MAC和目的MAC + getPacketsInfo(file, ['eth.src', 'eth.dst', 'pppoed.pppoe_session_id'], 'pppoe.code == 0x65') + + 获取IPCP Configuration Ack消息中的IP Address,同时,还会返回eth层的源MAC和目的MAC + getPacketsInfo(file, ['eth.src', 'eth.dst', 'ipcp.opt_ip_address'], '(ppp.code == 2) && (ppp.protocol == 0x8021) && (ipcp.opt.type == 3)') + + Returns: + list: 返回包含提取字段信息(字典)的列表 + """ + validate_type('pcapFile', pcapFile, str) + assert os.path.exists(pcapFile), "指定的文件不存在: %s" % pcapFile + + logging.getLogger().debug('文件%s的md5sum为: %s' % (pcapFile, md5sum(pcapFile))) + + pkts = pyshark.FileCapture(pcapFile, display_filter=filter, disable_protocol=disableProtocol) + + ret = [ ] + + try: + for pkt in pkts: + if type(attribute) == list: + ret.append({}) + for attr in attribute: + ret[-1][attr] = value(str(eval('pkt.%s' % attr))) + continue + + if type(attribute) == str: + ret.append(value(str(eval('pkt.%s' % attribute)))) + finally: + pkts.close() + + + return ret + +def wait_until_no_change(func, interval=5, overtime=600): + """等待函数返回值不再变化 + + Args: + func (functionType): 反复调用该函数获取返回值 + interval (int, optional): 调用间隔。默认5秒。 + overtime (int, optional): 超时时长.默认600秒。 + """ + # only function can be called + assert type(func) == FunctionType or type(func) == MethodType + s_time = time.time() + last_result = func() + while True: + logging.getLogger().info("等待%s秒后重试" % interval) + time.sleep(interval) + result = func() + waittime = time.time() - s_time + if result == last_result: + logging.getLogger().warning("总共等待%.1f秒" % waittime) + break + else: + last_result = result + if waittime > overtime: + raise RuntimeWarning("%s秒等待超时" % overtime) + + return result + +def wait_for_true(condFunc, interval=1, overtime=30): + """等待条件函数返回值为True + + Args: + condFunc (FunctionType): 条件函数,期间反复调用,直至返回为True或者超时 + interval (int, optional): 调用间隔,单位秒。默认1秒。 + overtime (int, optional): 超时时间。默认30秒。 + """ + assert type(condFunc) == FunctionType or type(condFunc) == MethodType + s_time = time.time() + while overtime > (time.time() - s_time): + + if condFunc(): + logging.getLogger().warning("总共等待%.1f秒" % (time.time() - s_time)) + return + else: + logging.getLogger().info("已经等待%.1f秒, 等待%s秒后重试" % (time.time() - s_time, interval)) + time.sleep(interval) + + raise RuntimeWarning('%s秒等待超时' % overtime) + +def is_ip_connectable(ipAddr): + """检查IP地址是否可达 + + Args: + ipaddr (str): IP地址字符串 + + Returns: + bool: True,可达; False不可达 + """ + assert is_valid_ipv4_addr(ipAddr), "IP地址非法: %s" % ipAddr + return 0 == host.ping(ipAddr) + +def is_valid_ipv4_addr(ipaddr): + """检查IP地址格式的是否为x.x.x.x格式。 + + Args: + ipaddr (str): IP地址字符串 + + Returns: + bool: True合法,False非法 + """ + if(None == re.match("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", ipaddr)): + return False + + for n in ipaddr.split("."): + num = int(n) + if(num < 0 and num > 255): + return False + + return True + +def dict_to_opt(dict, prefix='', optBlackList = []): + """将字典中的key/value转换成'key1=value1 key2=value2'这样的字符串。 + + Args: + dict (dict): 包含选项和选项值的字典。 + prefix (str): 在选项前增加的符号。指定则会增加,如,'-',格式化为'-key1=value1 -key2=value2' + optBlackList (list, optional): 黑名单。如果选项的key值在黑名单中,将不会格式化输出到字符串中。默认为空。 + Returns: + str: 格式化完成后的字符串。 + """ + strCmd = '' + for key in dict.keys(): + # key是否在黑名单中 + if key in optBlackList: + continue + + # 如果选项的值为字符类型,且其中带有空格,将用引号括起来 + if type(dict[key]) == str and ' ' in dict[key]: + strOpt = '%s%s "%s"' % (prefix, key, dict[key]) + else: + strOpt = '%s%s %s' % (prefix, key, dict[key]) + + # append to strCmd + strCmd = strCmd + ' ' + strOpt + + return strCmd.strip() # strip space in the head and tail + +def add_dollar_sign(value): + """为给定的值加上$符号。 + + Args: + value (str or list): 可以是单串,空格分隔的长串,或者列表。 + + Returns: + str: 给定'port',返回'$port'。给定'port1 port2', 返回'$port1 $port2'。给定['port1', 'port2'], 返回'$port1 $port2'。 + """ + ret = '' + for var in re.split('\s+', value.strip()) if type(value) == str else value: + ret = ret + ' $%s' % var + + return ret.strip() + +def list_to_str(listArg, template='%s', sep=' '): + """将给定的列表中元素,依次使用模板格式化后,使用指定的分隔符连接为一个长串后返回。如,list2Str([1,2,3], '$%s')返回'$1 $2 $3' + + Args: + listArg (list): 要处理的列表 + template (str): 用于格式化的模板。默认值'%s'。 + Returns: + str: 返回的字符长串 + """ + ret = '' + for i in listArg: + ret = ret + sep + template % i + return ret.strip() + +class MAC: + """MAC类用于生成MAC地址。如,mac = MAC('00:10:94:00:00:00'), mac.next()将生成00:10:94:00:00:01的IP地址。 + """ + def __init__(self, addr='00:10:94:00:00:00'): + """初始化MAC类 + + Args: + addr (str, optional): 指定MAC地址,生成MAC地址将从它的基础上增加而来。默认'00:10:94:00:00:00'。 + """ + self.macExp = re.compile('([A-Fa-f0-9]{2,2}):([A-Fa-f0-9]{2,2}):([A-Fa-f0-9]{2,2}):([A-Fa-f0-9]{2,2}):([A-Fa-f0-9]{2,2}):([A-Fa-f0-9]{2,2})') + assert self.macExp.match(addr) != None, 'invalid mac addr: %s' % addr + self.addr = addr + + def _incrHex(self, hex): + """对给定的hex值加1,并且判断是否大于0xFF需要进位。 + + Args: + hex (str or int): 被加1的值 + + Returns: + tuple: 返回(是否进位, 累加后的HEX值)元组, 如,0xFF增加1后,返回(1, '0x00'), 0xFE增加1后,返回(0, '0xFF') + """ + if type(hex) == str: + hex = int(hex, 16) + + hex = hex + 1 + + if hex > 255: + return 1, '00' + else: + return 0, '%02x' % hex + + def next(self): + """返回下个生成的MAC地址。 + + Returns: + str: 生成的MAC地址字符串。 + """ + m1, m2, m3, m4, m5, m6 = self.macExp.match(self.addr).groups() + + incr, m6 = self._incrHex(m6) + if incr == 1: + incr, m5 =self._incrHex(m5) + if incr == 1: + incr, m4 = self._incrHex(m4) + assert incr == 0 + + self.addr = '%s:%s:%s:%s:%s:%s' % (m1, m2, m3, m4, m5, m6) + return self.addr + +class IP: + """IP类用于生成IP地址。如,ip = IP('192.168.0.0'), ip.next()将会生成192.168.0.1的IP地址。 + """ + + def __init__(self, addr='192.168.0.0'): + """初始化IP类 + + Args: + addr (str, optional): 指定IP网段,生成的IP地址将从该地址的基础上增加而来. 默认为'192.168.0.0'. + """ + + self.ipExp = re.compile('(\d{1,3}).(\d{1,3}).(\d{1,3}).(\d{1,3})') + + match = self.ipExp.match(addr) + assert match != None, 'invalid ip address: %s' % addr + a, b, c, d = match.groups() + intA, intB, intC, intD = int(a), int(b), int(c), int(d) + + assert intD == 0, 'invalid ip address: %s, expected %s.%s.%s.0' % (addr, intA, intB, intC) + + self.addr = addr + self.gateway = '%s.%s.%s.254' % (intA, intB, intC) + + def next(self): + """返回下个生成的IP地址。 + + Returns: + str: 生成的IP地址字符串。 + """ + + a, b, c, d = self.ipExp.match(self.addr).groups() + intA, intB, intC, intD = int(a), int(b), int(c), int(d) + intD = intD + 1 + assert intD < 254, 'out of range: %d.%d.%d.%d' % (intA, intB, intC, intD) + + self.addr = '%d.%d.%d.%d' % (intA, intB, intC, intD) + + return self.addr + +class Port: + """用于创建唯一的端口号 + """ + + def __init__(self, base=1024): + """初始化Port类。 + + Args: + base (int, optional): 从哪个端口号开始创建。默认是1024开始。 + """ + self.base = base + + def next(self): + """返回下个生成的端口号 + + Returns: + int: 端口号 + """ + + port = self.base + self.base = self.base + 1 + + return port + +def group_mac(ip): + """根据给定的组播IP地址,返回其对应的组播MAC地址 + + Args: + ip (str): 组播IP地址 + + Returns: + str: 组播MAC地址 + """ + validate_type('ip', ip, str) + + + ipExp = re.compile('(\d{1,3}).(\d{1,3}).(\d{1,3}).(\d{1,3})') + + # 验证IP地址格式 + match = ipExp.match(ip) + assert match != None, '无效的IP地址格式: %s' % ip + + # 将IP转换为32个单位长度的01字符串 + a, b, c, d = match.groups() + a, b, c, d = int(a), int(b), int(c), int(d) + strBinary = bin(((a << 8 | b ) << 8 | c) << 8 | d)[2:] + + # 验证是否为合法的组播地址 + assert strBinary[0:4] == '1110', '组播IP地址应该以1110开头,但给定的IP为%s开头' % strBinary[0:4] + + # 转换为01字符串格式的MAC组播地址 + strMac = '00000001' + '00000000' + '01011110' + '0' + strBinary[9:] + + # 转换为00:00:00:00:00:00格式的MAC地址 + a, b, c, d, e, f = int(strMac[0:8], 2), int(strMac[8:16], 2), int(strMac[16:24], 2), int(strMac[24:32], 2), int(strMac[32:40], 2), int(strMac[40:48], 2) + strHexMax = '%02x:%02x:%02x:%02x:%02x:%02x' % (a, b, c, d, e, f) + + return strHexMax + +def len_of_mask(mask): + """计算子网掩码对应的长度 + + Args: + mask (str): 点分格式的子网掩码 + + Return: + int: 子网掩码对应的长度,如,255.255.255.0,对应的长度是24 + """ + maskExpr = re.compile('(\d{1,3}).(\d{1,3}).(\d{1,3}).(\d{1,3})') + match = maskExpr.match(mask) + a, b, c, d = match.groups() + a, b, c, d = int(a), int(b), int(c), int(d) + strBinary = bin(((a << 8 | b ) << 8 | c) << 8 | d)[2:] + + length = 0 + zero = False + for i in strBinary: + if i == '1' and zero == False: + length = length + 1 + + if i == '1' and zero == True: + raise RuntimeWarning('非法的子网掩码: %s' % strBinary) + + if i == '0': + zero = True + + return length + +def varname(var): + """获取变量名字符串。如,varname(hello),将会返回'hello' + + Args: + var (any): 变量 + + Returns: + str: 变量的名称 + """ + for fi in reversed(inspect.stack()): + names = [var_name for var_name, var_val in fi.frame.f_locals.items() if var_val is var] + if len(names) > 0: + return names[0] + +def find_class(className, moduleName, caseSensitive=False): + """在模块中查找对应的类 + + Args: + className (str): 类名 + moduleName (str): 模块名 + + Returns: + any: 属性对象 + """ + # 导入模块 + module = __import__(moduleName) + + foundClassName = None + attrList = dir(module) + for attrName in attrList: + if not caseSensitive: + if attrName.lower() == className.lower(): + foundClassName = attrList[attrList.index(attrName)] + break + else: + if attrName == className: + foundClassName = attrList[attrList.index(attrName)] + break + + return getattr(module, foundClassName, None) + +def remove_duplicate_data_from_dic_list(dictList): + """将字典列表里面的重复项移除 + + Args: + dictList(list): 字典列表 + + Returns: + list: 去重后的字典列表 + """ + + return [ dict(t) for t in set([tuple(d.items()) for d in dictList]) ] + +def md5sum(file_path): + """计算文件的md5sum。 + + Args: + file_path (str): 文件的路径 + """ + assert os.path.exists(file_path), "指定的文件不存在: %s" % file_path + + md5Digest = md5() + with open(file_path, 'rb') as f: + while True: + bytes = f.read() + if len(bytes) != 0: + md5Digest.update(bytes) + else: + break + return md5Digest.hexdigest() + +def run_by_thread_pool(func, argList, poolSize=5): + """使用线程池的方法运行函数 + + Args: + func (functionType): 要执行的函数,它接收的参数来自argList中的元素。 + argList (list): 参数列表 + poolSize (int, optional): 线程池大小。默认5。 + """ + pool = threadpool.ThreadPool(5) + + requests = threadpool.makeRequests(func, argList) + [pool.putRequest(req) for req in requests] + pool.wait() + +def next_temp_name(): + """创建一个随机字符串 + + Returns: + str: 随机字符串 + """ + return next(tempfile._get_candidate_names()) + +def random_pick_dict(dictData, count): + """从可枚举值里面随机取指定数目的项 + + Args: + dictData (dict): 字典对象 + count (int): 取指定数目的项目 + + Returns: + dict: 返回处理后的字典(不会修改原字典) + """ + validate_type('dictData', dictData, dict) + + ret = dictData.copy() + + if len(ret.keys()) < count: + raise RuntimeError('字典数据不足,无法挑选') + + while True: + removeCount = len(ret.keys()) - count + if removeCount == 0: + break + + toRemoveKey = [ ] + for key in ret.keys(): + if removeCount == 0: + break + if random.randint(0, 1) == 0: + continue + else: + toRemoveKey.append(key) + removeCount = removeCount - 1 + + for key in toRemoveKey: + del ret[key] + + return ret + +def shift_one_char(data, baseIndex=0): + """将str中的某一位变换一下 + + Args: + data (str): 要变换的数据 + baseIndex(int): 从哪个index之后开始变换。默认为0。 + Returns: + str: 变换后的字符串 + """ + validate_type('data', data, str) + + index = random.randint(baseIndex, len(data)-1) + dataList = list(data) + + alphabet_regex = re.compile('[a-zA-Z]') + digital_regex = re.compile('[0-9]') + + if alphabet_regex.match(dataList[index]): + dataList[index] = chr(ord(dataList[index]) + 1) + + if digital_regex.match(dataList[index]): + dataList[index] = "%s" % ((int(dataList[index]) + 1) % 10) + + return ''.join(dataList) + +def strip_dict_key_and_value(dict_data): + """将字典中key的空格和value的空格都干掉 + + Args: + dict_data (dict): 要处理的字典 + + Returns: + dict: 处理过后的字典 + """ + ret = { } + for key in dict_data.keys(): + ret[key.strip()] = dict_data[key].strip() if dict_data[key].strip() != '' else None + + return ret + +def read_dict_list_from_csv(match_pattern, search_base='.'): + """从CSV文件里面读取字典,并自动去重 + + Args: + match_pattern (str): 搜索文件名、匹配模式 + search_base (str): 搜索的起始目录 + + Returns: + list: 字典列表 + """ + validate_type('match_pattern', match_pattern, str) + validate_type('search_base', search_base, str) + + search_dir = pathlib.Path(search_base) + + ret = [ ] + + for csv_file in search_dir.glob(match_pattern): + with open(csv_file, 'r') as f: + rows = csv.DictReader(f) + for row in rows: + ret.append(strip_dict_key_and_value(row)) + + return [dict(t) for t in set([tuple(d.items()) for d in ret])] + +def read_str_list_from_txt(match_pattern, search_base='.'): + """从文本里面读取字符串列表 + + Args: + match_pattern (str): 搜索文件名、匹配模式 + search_base (str): 搜索的起始目录 + + Returns: + list: 去重,去掉空格的字符串列表 + """ + validate_type('match_pattern', match_pattern, str) + validate_type('search_base', search_base, str) + + search_dir = pathlib.Path(search_base) + + ret = [ ] + + for txt_file in search_dir.glob(match_pattern): + with open(txt_file, 'r') as f: + for line in f.readlines(): + line = line.strip() + if line != '': + ret.append(line) + + ret = list(set(ret)) + ret.sort() + + return ret + +class dotdict(dict): + """dot.notation access to dictionary attributes""" + __getattr__ = dict.get + __setattr__ = dict.__setitem__ + __delattr__ = dict.__delitem__ + +__all__ =[ + 'value', + 'validate_type', + 'validate_ip_addr', + 'validate_int_range', + 'validate_key', + 'move', + 'merge', + 'expand_list', + 'break_frame_slot_port', + 'count_packets', + 'get_packets_info', + 'wait_until_no_change', + 'wait_for_true', + 'is_ip_connectable', + 'is_valid_ipv4_addr', + 'dict_to_opt', + 'add_dollar_sign', + 'list_to_str', + 'MAC', + 'IP', + 'Port', + 'group_mac', + 'len_of_mask', + 'varname', + 'find_class', + 'remove_duplicate_data_from_dic_list', + 'md5sum', + 'run_by_thread_pool', + 'next_temp_name', + 'random_pick_dict', + 'shift_one_char', + 'strip_dict_key_and_value', + 'read_dict_list_from_csv', + 'read_str_list_from_txt', + 'dotdict' +] \ No newline at end of file diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..e1dc2f7 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = --html test_report.html --cov=vstc diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..d911064 --- /dev/null +++ b/setup.py @@ -0,0 +1,30 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import setuptools + + +def readme(): + with open('README.md', 'r') as f: + return f.read() + +setuptools.setup( + name='fiberhome-oltcli', + version='1.0.1', + author='Ding Yi', + author_email='dvdface@hotmail.com', + url='https://github.com/dvdface/fiberhome-oltcli', + description='OLT CommandLine API for FiberHome', + long_description=readme(), + long_description_content_type='text/markdown', + packages=['vstc'], + install_requires=[], + tests_require= ['pytest', 'pytest-html'], + license='MIT', + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + python_requires='>=3.6' +) \ No newline at end of file diff --git a/tpui.bat b/tpui.bat new file mode 100644 index 0000000..e58228e --- /dev/null +++ b/tpui.bat @@ -0,0 +1,2 @@ +del dist\* /Q && python setup.py pytest && python setup.py sdist && twine upload dist/*.tar.gz && pip install fiberhome-oltcli -U +pause \ No newline at end of file