Skip to content

Commit

Permalink
enhance: add base class 'OVSvsctlList' (#3575)
Browse files Browse the repository at this point in the history
* enhance: add a basic class 'OVSvsctlList'  for parser 'OVSvsctlListBridge'

Signed-off-by: shlao <[email protected]>

* Refactor the hierarchy for command 'ovs-vsctl'
* Call the 'deprecated' function

Signed-off-by: shlao <[email protected]>
  • Loading branch information
shlao authored Nov 1, 2022
1 parent d61496e commit d887994
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 47 deletions.
3 changes: 3 additions & 0 deletions docs/shared_parsers_catalog/ovs_vsctl.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.. automodule:: insights.parsers.ovs_vsctl
:members:
:show-inheritance:
96 changes: 96 additions & 0 deletions insights/parsers/ovs_vsctl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""
Open vSwitch ``ovs-vsctl`` - utility for querying ovs-vswitchd
==============================================================
Classes in this module are:
OVSvsctlList - command ``/usr/bin/ovs-vsctl list TBL [REC]``
------------------------------------------------------------
Parsers in this module are:
OVSvsctlListBridge - command ``/usr/bin/ovs-vsctl list bridge``
---------------------------------------------------------------
"""

from insights import CommandParser, get_active_lines, parser
from insights.parsers import SkipException, optlist_to_dict
from insights.specs import Specs


class OVSvsctlList(CommandParser, list):
"""
Class to parse output of command ``ovs-vsctl list TBL [REC]``.
Generally, the data is in ``key:value`` format with values having
data types as string, numbers, list or dictionary.
Raises:
SkipException: When file is empty.
"""
def parse_content(self, content):
"""
Details of the subset of the Open vSwitch database, which holds the configuration
for the Open vSwitch daemon, are extracted and stored in a list as a dictionary.
"""
# No content found or file is empty
if not content:
raise SkipException("Empty file")

record = {}
for line in get_active_lines(content):
key, value = [i.strip() for i in line.split(":", 1)]
parsed_value = value.strip('"')
if value.startswith("{") and value.endswith("}"):
parsed_value = {}
value = value.strip("{}")
if value:
parsed_value = optlist_to_dict(value, opt_sep=", ", strip_quotes=True)
elif value.startswith("[") and value.endswith("]"):
parsed_value = []
value = value.strip("[]")
if value:
parsed_value = [i.strip(' \t\"\'') for i in value.split(",")]

if key in record:
# A new record comes
self.append(record)
record = {}

record[key] = parsed_value

# Add the last record
self.append(record)

@property
def data(self):
"""
Set data as property to keep compatibility
"""
return self


@parser(Specs.ovs_vsctl_list_bridge)
class OVSvsctlListBridge(OVSvsctlList):
"""
Class to parse output of command ``ovs-vsctl list bridge``.
Sample command output::
name : br-int
other_config : {disable-in-band="true", mac-table-size="2048"}
name : br-tun
other_config : {}
Examples:
>>> bridge_lists[0]["name"]
'br-int'
>>> bridge_lists[0]["other_config"]["mac-table-size"]
'2048'
>>> bridge_lists[0]["other_config"]["disable-in-band"]
'true'
>>> bridge_lists[1].get("name")
'br-tun'
>>> len(bridge_lists[1]["other_config"]) == 0
True
"""
pass
70 changes: 23 additions & 47 deletions insights/parsers/ovs_vsctl_list_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,29 @@
This module provides class ``OVSvsctlListBridge`` for parsing the
output of command ``ovs-vsctl list bridge``.
.. note::
Please refer to its super-class :class:`insights.parsers.ovs_vsctl_list.OVSvsctlList`
for more details.
.. warning::
This parser `OVSvsctlListBridge` is deprecated, please use
:py:class:`insights.ovs_vsctl.OVSvsctlListBridge` instead.
"""

from insights import CommandParser, get_active_lines, parser
from insights.parsers import SkipException, optlist_to_dict
from insights import parser
from insights.parsers.ovs_vsctl import OVSvsctlList
from insights.specs import Specs
from insights.util import deprecated


@parser(Specs.ovs_vsctl_list_bridge)
class OVSvsctlListBridge(CommandParser):
class OVSvsctlListBridge(OVSvsctlList):
"""
.. warning::
This parser `OVSvsctlListBridge` is deprecated, please use
:py:class:`insights.ovs_vsctl.OVSvsctlListBridge` instead.
Class to parse output of command ``ovs-vsctl list bridge``.
Generally, the data is in key:value format with values having
data types as string, numbers, list or dictionary.
Expand Down Expand Up @@ -46,47 +59,10 @@ class OVSvsctlListBridge(CommandParser):
Raises:
SkipException: When file is empty.
"""

bridge_keys = ("_uuid", "auto_attach", "controller", "datapath_id",
"datapath_type", "datapath_version", "external_ids", "fail_mode",
"flood_vlans", "flow_tables", "ipfix", "mcast_snooping_enable:",
"mirrors", "name", "netflow", "other_config", "ports", "protocols",
"rstp_enable", "rstp_status", "sflow", "status", "stp_enable")

def parse_content(self, content):
"""
Details of all the bridges are extracted and stored in a list as dictionary
elements. Each dictionary element contains the information of a specific
bridge.
"""
# No content found or file is empty
if not content:
raise SkipException("Empty file")

self.data = []
bridge_details = {}
for line in get_active_lines(content):
key, value = [i.strip() for i in line.split(":", 1)]
parsed_value = value.strip('"')
if value.startswith("{") and value.endswith("}"):
parsed_value = {}
value = value.strip("{}")
if value:
parsed_value = optlist_to_dict(value, opt_sep=", ", strip_quotes=True)
elif value.startswith("[") and value.endswith("]"):
parsed_value = []
value = value.strip("[]")
if value:
parsed_value = [i.strip(' \t\"\'') for i in value.split(",")]

if key not in bridge_details:
bridge_details[key] = parsed_value
else:
# A new bridge comes
self.data.append(bridge_details)
bridge_details = {key: parsed_value}
# Add the last bridge
self.data.append(bridge_details)

def __getitem__(self, line):
return self.data[line]
def __init__(self, *args, **kwargs):
deprecated(
OVSvsctlListBridge,
"Please use the :class:`insights.ovs_vsctl.OVSvsctlListBridge` instead.",
"3.2.25"
)
super(OVSvsctlListBridge, self).__init__(*args, **kwargs)
116 changes: 116 additions & 0 deletions insights/tests/parsers/test_ovs_vsctl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from insights.parsers import SkipException
from insights.parsers import ovs_vsctl
from insights.parsers.ovs_vsctl import OVSvsctlListBridge
from insights.tests import context_wrap
import doctest
import pytest

OVS_VSCTL_LIST_BRIDGES_ALL = """
_uuid : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
auto_attach : []
controller : [xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx]
datapath_id : "0000a61fd19ea54f"
datapath_type : "enp0s9"
datapath_version : "<unknown>"
external_ids : {a="0"}
fail_mode : secure
flood_vlans : [1000]
flow_tables : {1=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
ipfix : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
mcast_snooping_enable: false
mirrors : [xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx]
name : br-int
netflow : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
other_config : {disable-in-band="true", mac-table-size="2048"}
ports : [xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, 0000000-0000-0000-0000-0000000000000, 1111111-1111-1111-1111-1111111111111]
protocols : ["OpenFlow11", "OpenFlow11", "OpenFlow12", "OpenFlow13"]
rstp_enable : true
rstp_status : {rstp_bridge_id="8.000.a61fd19ea54f", rstp_bridge_port_id="0000", rstp_designated_id="8.000.a61fd19ea54f", rstp_designated_port_id="0000", rstp_root_id="8.000.a61fd19ea54f", rstp_root_path_cost="0"}
sflow : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
status : {"0"="1"}
stp_enable : true
_uuid : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
auto_attach : []
controller : []
datapath_id : "0000d29e6a8acc4c"
datapath_type : ""
datapath_version : "<unknown>"
external_ids : {}
fail_mode : []
flood_vlans : []
flow_tables : {}
ipfix : []
mcast_snooping_enable: false
mirrors : []
name : br-tun
netflow : []
other_config : {}
ports : [xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx]
protocols : []
rstp_enable : false
rstp_status : {}
sflow : []
status : {}
stp_enable : false
""".strip()

OVS_VSCTL_LIST_BRIDGES_FILTERED1 = """
name : br-int
other_config : {disable-in-band="true", mac-table-size="2048"}
name : br-tun
other_config : {}
""".strip()

OVS_VSCTL_LIST_BRIDGES_FILTERED2 = """
_uuid : xxxxxxxx-xxxx-xxxx-xxxxx-xxxxxxxxxxxx
name : br-int
netflow : []
other_config : {disable-in-band="true", mac-table-size="2048"}
stp_enable : false
_uuid : aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
name : br-tun
netflow : []
other_config : {mac-table-size="4096"}
stp_enable : true
""".strip()

EXCEPTION1 = """
""".strip()


def test_ovs_vsctl_documentation():
env = {
"bridge_lists": OVSvsctlListBridge(context_wrap(OVS_VSCTL_LIST_BRIDGES_FILTERED1)),
}
failed, total = doctest.testmod(ovs_vsctl, globs=env)
assert failed == 0


def test_ovs_vsctl_all():
bridges = OVSvsctlListBridge(context_wrap(OVS_VSCTL_LIST_BRIDGES_ALL))
assert bridges[0]["name"] == "br-int"
assert bridges[0]["external_ids"] == {"a": "0"}
assert bridges[0]["flow_tables"] == {"1": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}
assert bridges[0].get("flood_vlans") == ["1000"]
assert bridges[0]["protocols"][-1] == "OpenFlow13"
assert bridges[0]["other_config"]["mac-table-size"] == "2048"
assert bridges[0]["rstp_status"]["rstp_root_path_cost"] == "0"
assert bridges[1]["name"] == "br-tun"
assert bridges[1]["mirrors"] == []
assert bridges[1]["datapath_type"] == ""
assert bridges[1]["status"] == {}
assert bridges[1].get("stp_enable") == "false"


def test_ovs_vsctl():
bridges = OVSvsctlListBridge(context_wrap(OVS_VSCTL_LIST_BRIDGES_FILTERED2))
assert bridges[0].get("name") == "br-int"
assert bridges[0]["other_config"]["mac-table-size"] == "2048"
assert bridges[1]["name"] == "br-tun"


def test_ovs_vsctl_exception1():
with pytest.raises(SkipException) as e:
OVSvsctlListBridge(context_wrap(EXCEPTION1))
assert "Empty file" in str(e)

0 comments on commit d887994

Please sign in to comment.