Skip to content

[change] NetJSON DeviceMonitoring compliance #2 - #62

Merged
nemesifier merged 35 commits into
masterfrom
netjson-compliance
Aug 1, 2026
Merged

[change] NetJSON DeviceMonitoring compliance #2#62
nemesifier merged 35 commits into
masterfrom
netjson-compliance

Conversation

@purhan

@purhan purhan commented Jun 5, 2021

Copy link
Copy Markdown
Contributor

Closes #2, closes #64, closes #65
Resolves part of #60

@purhan

purhan commented Jun 5, 2021

Copy link
Copy Markdown
Contributor Author

@pandafy @nemesisdesign In the last commit (d46b568), I've made the to_dict and to_json compliant with NetJSON DeviceMonitoring. Please take a look.

  • I have not defined any new methods yet, but all the required fields in the schema are there.
  • A lot of methods we were using are no longer required in DeviceMonitoring schema but I have not removed them yet.

Todo: We need to define new methods to collect as much monitoring info as possible.

@pandafy

pandafy commented Jun 8, 2021

Copy link
Copy Markdown
Member

A lot of methods we were using are no longer required in DeviceMonitoring schema but I have not removed them ye

You can go ahead with this, since it will make it clear for us what methods are to be removed. We can always revert the commit if something went wrong.

Todo: We need to define new methods to collect as much monitoring info as possible.

instead of being exhaustive, let's start with minimal data. Check what data is being collected in openwrt-openwisp-monitoring and add support for it here. Otherwise we may spend time collecting every possible metric many of which might not be used in OpenWISP. We can always iterate to make it better.

@purhan

purhan commented Jun 10, 2021

Copy link
Copy Markdown
Contributor Author

Thanks @pandafy , I have updated the OpenWRT backend for now (will do AirOS later). Below you can see the comparison between netengine and openwrt-openwisp-monitoring. I've added a few more metrics but it may not be possible to add all. Now I need to fix some tests.

output from openwrt-openwisp-monitoring

{
  "type": "DeviceMonitoring",
  "general": {
    "local_time": 1623319682,
    "uptime": 178,
    "hostname": "OpenWrt"
  },
  "dhcp_leases": [
    {
      "mac": "04:0e:3c:ca:55:5f",
      "client_id": "01:04:0e:3c:ca:55:5f",
      "client_name": "purhan",
      "ip": "192.168.2.140",
      "expiry": 1623362752
    }
  ],
  "interfaces": [
    {
      "type": "vlan",
      "up": false,
      "name": "eth0.2"
    },
    {
      "mac": "1c:3b:f3:10:0a:42",
      "type": "bridge",
      "stp": false,
      "up": true,
      "txqueuelen": 1000,
      "name": "br-lan",
      "multicast": true,
      "addresses": [
        {
          "proto": "static",
          "address": "192.168.2.1",
          "family": "ipv4",
          "mask": 24,
          "gateway": "192.168.0.1"
        },
        {
          "family": "ipv6",
          "mask": 64,
          "proto": "static",
          "address": "fe80::1e3b:f3ff:fe10:a42"
        }
      ],
      "bridge_members": [
        "eth0.1"
      ],
      "mtu": 1500
    },
    {
      "mac": "1c:3b:f3:10:0a:42",
      "type": "wireless",
      "up": true,
      "txqueuelen": 1000,
      "name": "wlan0",
      "wireless": {
        "country": "00",
        "noise": 0,
        "ssid": "TP-Link",
        "channel": 2,
        "tx_power": 20,
        "mode": "station",
        "signal": -46,
        "frequency": 2417
      },
      "multicast": true,
      "addresses": [
        {
          "proto": "dhcp",
          "address": "192.168.0.100",
          "family": "ipv4",
          "mask": 24,
          "gateway": "192.168.0.1"
        },
        {
          "family": "ipv6",
          "mask": 64,
          "proto": "static",
          "address": "fe80::1e3b:f3ff:fe10:a42"
        }
      ],
      "mtu": 1500
    },
    {
      "mac": "1c:3b:f3:10:0a:42",
      "type": "other",
      "up": true,
      "txqueuelen": 1000,
      "name": "eth0",
      "multicast": true,
      "addresses": [
        {
          "family": "ipv6",
          "mask": 64,
          "proto": "static",
          "address": "fe80::1e3b:f3ff:fe10:a42"
        }
      ],
      "mtu": 1500
    },
    {
      "mac": "1c:3b:f3:10:0a:42",
      "type": "vlan",
      "up": true,
      "txqueuelen": 1000,
      "name": "eth0.1",
      "multicast": true,
      "mtu": 1500
    }
  ],
  "resources": {
    "memory": {
      "total": 61452288,
      "shared": 278528,
      "free": 33243136,
      "cached": 7983104,
      "available": 26767360,
      "buffered": 2465792
    },
    "cpus": 1,
    "disk": [
      {
        "filesystem": "/dev/root",
        "available_bytes": 0,
        "mount_point": "/rom",
        "used_percent": 100,
        "size_bytes": 2621440,
        "used_bytes": 2621440
      },
      {
        "filesystem": "/dev/mtdblock5",
        "available_bytes": 192512,
        "mount_point": "/overlay",
        "used_percent": 95,
        "size_bytes": 3735552,
        "used_bytes": 3543040
      }
    ],
    "load": [
      0.33,
      0.27,
      0.11
    ],
    "swap": {
      "free": 0,
      "total": 0
    }
  },
  "dns_servers": [
    "8.8.8.8",
    "8.8.4.4"
  ],
  "neighbors": [
    {
      "mac": "04:0e:3c:ca:55:5f",
      "state": "REACHABLE",
      "interface": "br-lan",
      "ip": "192.168.2.140"
    },
    {
      "mac": "84:d8:1b:62:a3:55",
      "state": "REACHABLE",
      "interface": "wlan0",
      "ip": "192.168.0.1"
    },
    {
      "mac": "04:0e:3c:ca:55:5f",
      "state": "STALE",
      "interface": "br-lan",
      "ip": "fe80::2ed4:5d74:941f:ce1e"
    }
  ]
}

output from netengine

{
  "type": "DeviceMonitoring",
  "general": {
    "uptime": 30,
    "local_time": 1623319570
  },
  "resources": {
    "cpus": 0,
    "memory": {
      "total": 60012,
      "shared": 88,
      "free": 27340,
      "cached": 7160
    },
    "swap": {
      "total": 0,
      "free": 0
    }
  },
  "interfaces": [
    {
      "name": "lo",
      "statistics": {
        "mac": "",
        "type": "softwareLoopback",
        "up": true,
        "rx_bytes": 2437,
        "tx_bytes": 2437,
        "mtu": 65536
      }
    },
    {
      "name": "eth0",
      "statistics": {
        "mac": "1c:3b:c3:b3:10:0a",
        "type": "ethernetCsmacd",
        "up": true,
        "rx_bytes": 160673,
        "tx_bytes": 615795,
        "mtu": 1500
      }
    },
    {
      "name": "Device 14c3:7662",
      "statistics": {
        "mac": "",
        "type": "ethernetCsmacd",
        "up": false,
        "rx_bytes": 0,
        "tx_bytes": 0,
        "mtu": 1500
      }
    },
    {
      "name": "br-lan",
      "statistics": {
        "mac": "1c:3b:c3:b3:10:0a",
        "type": "ethernetCsmacd",
        "up": true,
        "rx_bytes": 147924,
        "tx_bytes": 601205,
        "mtu": 1500
      }
    },
    {
      "name": "eth0.1",
      "statistics": {
        "mac": "1c:3b:c3:b3:10:0a",
        "type": "ethernetCsmacd",
        "up": true,
        "rx_bytes": 147924,
        "tx_bytes": 614999,
        "mtu": 1500
      }
    },
    {
      "name": "wlan0",
      "statistics": {
        "mac": "1c:3b:c3:b3:10:0a",
        "type": "ethernetCsmacd",
        "up": true,
        "rx_bytes": 604709,
        "tx_bytes": 158937,
        "mtu": 1500
      }
    }
  ],
  "neighbors": [
    {
      "mac": "04:0e:3c:c3:8a:55",
      "state": "REACHABLE",
      "interface": "br-lan"
    },
    {
      "mac": "04:0e:3c:c3:8a:55",
      "state": "REACHABLE",
      "interface": "br-lan"
    },
    {
      "mac": "c2:84:c3:98:1b:62",
      "state": "STALE",
      "interface": "wlan0"
    }
  ]
}

@purhan
purhan force-pushed the netjson-compliance branch from 701f1df to b8c427f Compare June 11, 2021 06:34
@purhan
purhan force-pushed the netjson-compliance branch from b8c427f to cfa6650 Compare June 11, 2021 06:42
- Fix failing tests
- Pass more realistic values in mocks
- Remove redundant code
@coveralls

coveralls commented Jun 13, 2021

Copy link
Copy Markdown

Pull Request Test Coverage Report for Build 938832440

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 212 of 219 (96.8%) changed or added relevant lines in 6 files are covered.
  • 12 unchanged lines in 2 files lost coverage.
  • Overall coverage increased (+6.6%) to 95.847%

Changes Missing Coverage Covered Lines Changed/Added Lines %
netengine/backends/snmp/airos.py 75 78 96.15%
netengine/backends/snmp/openwrt.py 104 108 96.3%
Files with Coverage Reduction New Missed Lines %
netengine/backends/snmp/airos.py 4 96.15%
netengine/backends/snmp/openwrt.py 8 94.22%
Totals Coverage Status
Change from base Build 900223237: 6.6%
Covered Lines: 600
Relevant Lines: 626

💛 - Coveralls

@purhan
purhan force-pushed the netjson-compliance branch from 01de297 to 8cd7089 Compare June 15, 2021 09:47
@purhan purhan mentioned this pull request Jun 15, 2021
4 tasks

@pandafy pandafy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got following errors while running tests again OpenWrt device. Is there anything that needs to be configured on the device? Is there any extra snmpd cofnigguration needed?

(venv-netengine) pandafy@FRIDAY:~/openwisp/netengine$ DISABLE_MOCKS=1 TEST_SETTINGS_FILE=./test-settings.json  nose2 tests.test_snmp.test_openwrt
........EE....E.......E..
======================================================================
ERROR: test_interfaces_to_dict (tests.test_snmp.test_openwrt.TestSNMPOpenWRT)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/pandafy/openwisp/netengine/tests/test_snmp/test_openwrt.py", line 80, in test_interfaces_to_dict
    self.assertIsInstance(self.device.interfaces_to_dict, list)
  File "/home/pandafy/openwisp/netengine/netengine/backends/snmp/openwrt.py", line 335, in interfaces_to_dict
    if_type = self.interfaces_type[i]['type']
  File "/home/pandafy/openwisp/netengine/netengine/backends/snmp/openwrt.py", line 281, in interfaces_type
    'type': types[self.get_value(types_oid + str(i))],
KeyError: '1'

======================================================================
ERROR: test_interfaces_type (tests.test_snmp.test_openwrt.TestSNMPOpenWRT)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/pandafy/openwisp/netengine/tests/test_snmp/test_openwrt.py", line 71, in test_interfaces_type
    self.assertIsInstance(self.device.interfaces_type, list)
  File "/home/pandafy/openwisp/netengine/netengine/backends/snmp/openwrt.py", line 281, in interfaces_type
    'type': types[self.get_value(types_oid + str(i))],
KeyError: '1'

======================================================================
ERROR: test_netjson_compliance (tests.test_snmp.test_openwrt.TestSNMPOpenWRT)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/pandafy/openwisp/netengine/tests/test_snmp/test_openwrt.py", line 120, in test_netjson_compliance
    device_dict = self.device.to_dict()
  File "/home/pandafy/openwisp/netengine/netengine/backends/snmp/openwrt.py", line 532, in to_dict
    'interfaces': self.interfaces_to_dict,
  File "/home/pandafy/openwisp/netengine/netengine/backends/snmp/openwrt.py", line 335, in interfaces_to_dict
    if_type = self.interfaces_type[i]['type']
  File "/home/pandafy/openwisp/netengine/netengine/backends/snmp/openwrt.py", line 281, in interfaces_type
    'type': types[self.get_value(types_oid + str(i))],
KeyError: '1'

======================================================================
ERROR: test_to_dict (tests.test_snmp.test_openwrt.TestSNMPOpenWRT)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/pandafy/openwisp/netengine/tests/test_snmp/test_openwrt.py", line 113, in test_to_dict
    device_dict = self.device.to_dict()
  File "/home/pandafy/openwisp/netengine/netengine/backends/snmp/openwrt.py", line 532, in to_dict
    'interfaces': self.interfaces_to_dict,
  File "/home/pandafy/openwisp/netengine/netengine/backends/snmp/openwrt.py", line 335, in interfaces_to_dict
    if_type = self.interfaces_type[i]['type']
  File "/home/pandafy/openwisp/netengine/netengine/backends/snmp/openwrt.py", line 281, in interfaces_type
    'type': types[self.get_value(types_oid + str(i))],
KeyError: '1'

----------------------------------------------------------------------
Ran 25 tests in 23.801s

FAILED (errors=4)

Comment thread netengine/backends/snmp/airos.py Outdated
Comment thread tests/test_snmp/test_openwrt.py Outdated
Comment thread netengine/backends/snmp/openwrt.py Outdated
Comment thread netengine/backends/snmp/airos.py Outdated
Comment thread netengine/backends/snmp/airos.py Outdated
- Swap time with datetime in local_time
- Remove unused warning
- Use a single call for collecting load information
@purhan
purhan force-pushed the netjson-compliance branch from 0b12396 to da39965 Compare June 18, 2021 06:31
Comment thread netengine/backends/snmp/airos.py Outdated

@pandafy pandafy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work Purhan! Please see my comments below.

Comment thread netengine/backends/snmp/openwrt.py Outdated
Comment thread netengine/backends/snmp/openwrt.py Outdated
@purhan
purhan force-pushed the netjson-compliance branch from b5940b2 to cda0295 Compare July 5, 2021 17:07
Comment thread netengine/backends/snmp/openwrt.py Outdated
Comment thread netengine/backends/snmp/base.py
Comment thread netengine/backends/snmp/base.py Outdated

@pandafy pandafy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While testing it was found that to_dict methods are taking too long to fetch the data since it is making multiple queries. To make this method quicker, we can do a single query to device for a complete SNMP Walk and then process the data as needed locally by netengine.

I have been thinking about how we can bring this change and here is one of my idea.

Instead of changing complete implementation we can update the signature of current methods to accept another optional parameter snmpdump which will be the output from snmpwalk. The methods will locally fetch the required OIDs from snmpdump and then the existing code for further processing can be used.

Here's a pseudocode for example:

def local_time(self, snmpdump=None):
    if snmpdump:
        epoch = get_local_time_from_snmp_dump
    else:
        epoch = str(self.get('1.3.6.1.4.1.41112.1.4.8.1.0')[3][0][1])
     timestamp = int(datetime.strptime(epoch, '%Y-%m-%d %H:%M:%S').timestamp())
     return timestamp

@nemesifier nemesifier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @purhan, with that fix I can now extract the info without errors.

For sure we'll have find a way to get all the info at once to resolve the performance issue when generating the JSON output, in the current form it would not be usable.

In the output of type I see ethernet Csmacd, but to be compliant with the NetJSON spec it should be just ethernet.

One thing I noticed is that wifi interfaces are also detected as ethernet and I don't see any wireless info is that the max we can do?
Looking at the snmpd package for OpenWRT, I do see a patch that seems to add support for WiFi information: https://github.com/openwrt/packages/blob/38f01ad2c9738c6c5a5dbda85019bb9cf5bc10f8/net/net-snmp/patches/750-ieee802dot11.patch.

I just asked on the OpenWRT forum if the know anything about it.

Comment thread netengine/backends/snmp/utils.py Outdated
@nemesifier

Copy link
Copy Markdown
Member

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@netengine/backends/snmp/airos.py`:
- Around line 473-477: Update to_dict in netengine/backends/snmp/airos.py at
lines 473-477 to default autowalk to None and perform walks for 1.3.6 and
1.2.840.10036 only when snmpdump is None. Apply the same change to to_dict in
netengine/backends/snmp/openwrt.py at lines 565-583, using walks for 1.3.6.1 and
1.2.840.10036; preserve caller-supplied dumps without issuing live SNMP
requests.
- Around line 297-324: Update interfaces_to_dict to skip entries whose interface
name is empty, immediately after retrieving name from interfaces_MAC and before
collecting or appending the remaining interface data. Preserve the existing
processing for named interfaces so the returned results contain no records with
an empty "name".

In `@netengine/backends/snmp/openwrt.py`:
- Around line 529-552: Update the neighbor processing around the neighbors and
neighbor_states collections to key state entries by their shared
ipNetToPhysicalTable OID suffix rather than list position. Build a lookup from
each neighbor-state row’s suffix, then retrieve the matching state while
processing each neighbor; preserve the existing UNKNOWN fallback and avoid
dropping valid trailing neighbors when a state row is missing.

In `@tests/static/test-openwrt-snmp-oid.json`:
- Around line 69-72: Update the DateAndTime fixture value in the JSON entry for
OID 1.3.6.1.2.1.25.1.2.0 so the seconds byte uses the doubled-backslash escaped
form expected by codecs.escape_decode, matching the representation used by
test_local_time_offset and the rest of the fixture without embedding a literal
carriage-return character.

In `@tests/test_snmp/test_airos.py`:
- Around line 43-46: Update test_get_value_error to avoid stopping
getcmd_patcher and issuing a live SNMP request; verify whether get_value(".")
can deterministically produce NoSuchObjectError without network I/O, and if not,
mock get_cmd with the same deterministic return-value pattern used by
test_validate_negative_result while preserving the expected exception assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5f9b0c6b-05da-40bb-ba7f-c37463205af0

📥 Commits

Reviewing files that changed from the base of the PR and between 3b5f08c and a89c8fe.

📒 Files selected for processing (19)
  • .github/workflows/pypi.yml
  • docs/source/index.rst
  • docs/source/topics/backends/snmp.rst
  • docs/source/topics/snmp.rst
  • netengine/backends/base.py
  • netengine/backends/dummy.py
  • netengine/backends/schema.py
  • netengine/backends/snmp/airos.py
  • netengine/backends/snmp/base.py
  • netengine/backends/snmp/openwrt.py
  • requirements-test.txt
  • tests/static/test-airos-snmp.json
  • tests/static/test-openwrt-snmp-oid.json
  • tests/test_base.py
  • tests/test_dummy.py
  • tests/test_snmp/test_airos.py
  • tests/test_snmp/test_base.py
  • tests/test_snmp/test_openwrt.py
  • tests/utils.py
💤 Files with no reviewable changes (1)
  • docs/source/topics/snmp.rst
📜 Review details
🧰 Additional context used
🪛 ast-grep (0.45.0)
netengine/backends/base.py

[info] 29-29: use jsonify instead of json.dumps for JSON output
Context: json.dumps(dictionary, **kwargs)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

netengine/backends/schema.py

[warning] 3-3: Do not make http calls without encryption
Context: "http://json-schema.org/draft-07/schema#"
Note: [CWE-319] Cleartext Transmission of Sensitive Information.

(requests-http)

🪛 zizmor (1.28.0)
.github/workflows/pypi.yml

[warning] 20-20: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 20-20: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 22-22: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 32-32: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🔇 Additional comments (23)
netengine/backends/base.py (1)

22-30: LGTM!

netengine/backends/dummy.py (1)

61-61: LGTM!

netengine/backends/snmp/base.py (2)

1-7: LGTM!

Also applies to: 19-22, 33-56, 74-98, 113-119, 138-154


100-111: LGTM!

Also applies to: 121-136

netengine/backends/schema.py (1)

1-154: LGTM!

netengine/backends/snmp/airos.py (3)

19-29: LGTM!

Also applies to: 31-49, 51-62, 64-99, 103-123, 127-155, 166-179, 190-207, 217-237, 240-269, 271-293


326-340: LGTM!

Also applies to: 377-388, 390-438, 440-458, 460-471


478-497: LGTM!

netengine/backends/snmp/openwrt.py (2)

6-64: LGTM!

Also applies to: 69-132, 136-156, 160-204, 206-264, 268-293, 297-330, 332-375


377-508: LGTM!

.github/workflows/pypi.yml (1)

20-32: Unpinned action references and missing persist-credentials: false remain unresolved.

Lines 20, 22, and 32 still use mutable tags (@v7, @v7, @v1.14.1) instead of immutable commit SHAs, while the job grants id-token: write (lines 17-18). The actions/checkout step at line 20 still does not set persist-credentials: false. Both concerns were raised in a previous review round on this file and remain unaddressed in this reformatted version.

Source: Linters/SAST tools

docs/source/topics/backends/snmp.rst (2)

40-42: 📐 Maintainability & Code Quality | ⚡ Quick win

Terminology: "firmwares" should be "backends".

Line 40 still calls AirOS and OpenWRT "firmwares." A previous reviewer already flagged this exact wording ("it's not a firmware, it's a backend") elsewhere in this document's history, and the concern is still open here.


76-83: 📐 Maintainability & Code Quality

No change needed.

Indented lines directly after :: are recognized as an RST literal block without a preceding blank line; add blank lines for readability, not correctness.

			> Likely an incorrect or invalid review comment.
docs/source/index.rst (1)

67-72: LGTM!

requirements-test.txt (1)

3-3: LGTM!

tests/test_dummy.py (1)

26-29: LGTM!

tests/static/test-airos-snmp.json (1)

11-34: LGTM!

Also applies to: 64-73

tests/test_base.py (1)

3-8: LGTM!

Also applies to: 93-106

tests/test_snmp/test_airos.py (1)

48-259: LGTM!

tests/test_snmp/test_base.py (1)

4-18: LGTM!

Also applies to: 46-66

tests/test_snmp/test_openwrt.py (1)

46-334: LGTM!

tests/utils.py (2)

11-97: LGTM!


98-101: 📐 Maintainability & Code Quality

No leftover references remain.

Comment thread netengine/backends/snmp/airos.py
Comment thread netengine/backends/snmp/airos.py Outdated
Comment thread netengine/backends/snmp/openwrt.py
Comment thread tests/static/test-openwrt-snmp-oid.json
Comment thread tests/test_snmp/test_airos.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@netengine/backends/snmp/airos.py`:
- Around line 475-477: Update the to_dict method signatures in
netengine/backends/snmp/airos.py lines 475-477 and
netengine/backends/snmp/openwrt.py lines 572-574 so autowalk remains the first
positional parameter and snmpdump follows it, preserving existing to_dict(False)
calls while retaining snmpdump support.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9b36d787-7881-4f30-bd64-da66cf3c5d8c

📥 Commits

Reviewing files that changed from the base of the PR and between a89c8fe and 436b1e3.

📒 Files selected for processing (4)
  • netengine/backends/snmp/airos.py
  • netengine/backends/snmp/openwrt.py
  • tests/test_snmp/test_airos.py
  • tests/test_snmp/test_openwrt.py
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Kilo Code Review
🔇 Additional comments (4)
netengine/backends/snmp/airos.py (1)

19-473: LGTM!

netengine/backends/snmp/openwrt.py (1)

24-570: LGTM!

tests/test_snmp/test_airos.py (1)

109-113: LGTM!

Also applies to: 148-185, 187-266

tests/test_snmp/test_openwrt.py (1)

89-120: LGTM!

Also applies to: 131-165, 191-265, 267-366

Comment thread netengine/backends/snmp/airos.py Outdated
@nemesifier

Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
netengine/backends/snmp/airos.py (1)

86-99: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Scope all memoized SNMP results by snmpdump.

Both backends accept snapshot data but cache results without recording the snapshot. A later call with a different dump can return stale data. Resetting caches inside to_dict does not protect direct accessor calls.

  • netengine/backends/snmp/airos.py#L86-L99: scope _interfaces and the other AirOS memoized accessors by snmpdump, or bypass caching for supplied dumps.
  • netengine/backends/snmp/openwrt.py#L69-L83: apply the same rule to _interfaces, _interfaces_MAC, _interfaces_mtu, _interfaces_up, _interfaces_bytes, _interfaces_type, _wireless_interfaces, and _interface_addr_and_mask.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@netengine/backends/snmp/airos.py` around lines 86 - 99, Scope all memoized
SNMP accessor results by the supplied snmpdump, or bypass caching whenever a
dump is provided, so direct calls cannot return data from another snapshot. In
netengine/backends/snmp/airos.py lines 86-99, update get_interfaces and the
other AirOS memoized accessors; in netengine/backends/snmp/openwrt.py lines
69-83, apply the same handling to _interfaces, _interfaces_MAC, _interfaces_mtu,
_interfaces_up, _interfaces_bytes, _interfaces_type, _wireless_interfaces, and
_interface_addr_and_mask.
netengine/backends/snmp/openwrt.py (2)

306-326: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Key interface addresses by SNMP index and preserve all records.

Line [321] stores one address under the interface name. A second address for the same interface overwrites the first. The fixture already contains three interfaces named "Device 8086:100e" in tests/test_snmp/test_openwrt.py lines [68]-[77]. Line [354] then retrieves one value and wraps it in a one-element list, so duplicate names can receive the same final address.

Build an ifIndex-keyed address map and retain a list of addresses per index. Aggregate each interface by that same ifIndex. Add a regression test for duplicate names and multiple addresses.

Also applies to: 332-355

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@netengine/backends/snmp/openwrt.py` around lines 306 - 326, Update the
OpenWrt SNMP address aggregation around the IPv4 loop and the retrieval logic
near the interface aggregation to key addresses by ifIndex rather than interface
name, preserving a list of every address and mask for each index. Aggregate
interfaces using the same ifIndex so duplicate names remain distinct and do not
overwrite or reuse the final address. Add a regression test covering duplicate
interface names with multiple addresses.

572-576: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Make the OpenWRT autowalk of 1.2.840.10036 best-effort.

to_dict() calls SNMP.walk("1.2.840.10036") before get_wireless_interfaces(), but SNMP.walk raises NetEngineError when next() returns a non-zero SNMP error status. On OpenWRT devices that handle unsupported wireless MIB queries with an SNMP error, default serialization fails before the per-object NetEngineError fallback can skip wireless OID records. Catch this autowalk with one log entry, or make the backend ignore NetEngineError from this second walk.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@netengine/backends/snmp/openwrt.py` around lines 572 - 576, Update
OpenWRTBackend.to_dict around the secondary walk for “1.2.840.10036” so
NetEngineError is handled as a best-effort failure rather than aborting
serialization. Keep the primary “1.3.6.1” walk behavior unchanged, and emit at
most one log entry when the wireless walk fails before continuing with the
existing snmpdump processing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/pypi.yml:
- Line 32: Update the uses reference for pypa/gh-action-pypi-publish to the full
immutable commit SHA corresponding to v1.14.2, retaining “# v1.14.2” as the
version comment.

---

Outside diff comments:
In `@netengine/backends/snmp/airos.py`:
- Around line 86-99: Scope all memoized SNMP accessor results by the supplied
snmpdump, or bypass caching whenever a dump is provided, so direct calls cannot
return data from another snapshot. In netengine/backends/snmp/airos.py lines
86-99, update get_interfaces and the other AirOS memoized accessors; in
netengine/backends/snmp/openwrt.py lines 69-83, apply the same handling to
_interfaces, _interfaces_MAC, _interfaces_mtu, _interfaces_up,
_interfaces_bytes, _interfaces_type, _wireless_interfaces, and
_interface_addr_and_mask.

In `@netengine/backends/snmp/openwrt.py`:
- Around line 306-326: Update the OpenWrt SNMP address aggregation around the
IPv4 loop and the retrieval logic near the interface aggregation to key
addresses by ifIndex rather than interface name, preserving a list of every
address and mask for each index. Aggregate interfaces using the same ifIndex so
duplicate names remain distinct and do not overwrite or reuse the final address.
Add a regression test covering duplicate interface names with multiple
addresses.
- Around line 572-576: Update OpenWRTBackend.to_dict around the secondary walk
for “1.2.840.10036” so NetEngineError is handled as a best-effort failure rather
than aborting serialization. Keep the primary “1.3.6.1” walk behavior unchanged,
and emit at most one log entry when the wireless walk fails before continuing
with the existing snmpdump processing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c30208a7-37ef-4d7a-bd1a-172422cbe8dc

📥 Commits

Reviewing files that changed from the base of the PR and between 436b1e3 and 6f0e676.

📒 Files selected for processing (5)
  • .github/workflows/pypi.yml
  • netengine/backends/snmp/airos.py
  • netengine/backends/snmp/openwrt.py
  • tests/test_snmp/test_airos.py
  • tests/test_snmp/test_openwrt.py
📜 Review details
🧰 Additional context used
🪛 zizmor (1.28.0)
.github/workflows/pypi.yml

[error] 32-32: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🔇 Additional comments (6)
netengine/backends/snmp/airos.py (2)

6-9: LGTM!

Also applies to: 19-55, 64-82


379-499: LGTM!

netengine/backends/snmp/openwrt.py (2)

24-64: LGTM!


377-570: LGTM!

tests/test_snmp/test_airos.py (1)

1-14: LGTM!

Also applies to: 31-31, 41-55, 73-147, 149-267

tests/test_snmp/test_openwrt.py (1)

1-13: LGTM!

Also applies to: 34-34, 44-293, 295-326, 350-366

Comment thread .github/workflows/pypi.yml Outdated
@nemesifier

Copy link
Copy Markdown
Member

@coderabbitai resolve

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
✅ Action performed

Comments resolved and changes approved.

@nemesifier

Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
netengine/backends/snmp/airos.py (2)

490-492: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Tolerate a failing vendor walk, as the OpenWRT backend does.

walk raises NetEngineError when the agent reports an error indication or error status. If a device does not expose the 1.2.840.10036 subtree, to_dict fails completely, and the general, resource, and interface data is lost. netengine/backends/snmp/openwrt.py lines 592-597 already log a warning and continue in this case. Align the two backends.

♻️ Proposed change
         if snmpdump is None and autowalk:
             snmpdump = self.walk("1.3.6")
-            snmpdump.update(self.walk("1.2.840.10036"))
+            try:
+                snmpdump.update(self.walk("1.2.840.10036"))
+            except NetEngineError as exc:
+                logger.warning("Unable to collect optional vendor SNMP data: %s", exc)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@netengine/backends/snmp/airos.py` around lines 490 - 492, Update the autowalk
branch in AirosBackend.to_dict to catch NetEngineError from the vendor-specific
self.walk("1.2.840.10036") call, log a warning consistent with OpenWRT’s
handling, and continue using the general walk results so device data is still
returned. Keep failures from the primary self.walk("1.3.6") path unchanged.

438-448: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Fix CPU_count’s CPU-entry formula.

loadNumber == 3 and the fixture has only the three load-average OIDs (1.3.6.1.4.1.10002.1.1.1.4.2.1.3.*), so CPU_count() returns 0. Load entries and core identifiers are separate OIDs; returning 0 from resources_to_dict() makes this output assert incorrectly when cores exist.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@netengine/backends/snmp/airos.py` around lines 438 - 448, Update CPU_count to
derive the CPU core count from the load-entry data without dividing by
loadNumber or assuming each entry contains three fields. Use the core/load index
OIDs returned by self.next, ensuring fixtures with three load-average entries
and loadNumber equal to 3 return the existing core count rather than zero.
netengine/backends/snmp/openwrt.py (1)

312-313: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the unused _interface_dict state.

_interface_dict is only written and reset in netengine/backends/snmp/openwrt.py and asserted in tests/test_snmp/test_openwrt.py; no code reads it. After interfaces_to_dict keys addresses by SNMP index, drop the attribute initialization, loop, reset, and matching test assertion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@netengine/backends/snmp/openwrt.py` around lines 312 - 313, Remove the unused
_interface_dict state from the OpenWrt SNMP implementation: delete its
initialization, the loop assigning interface names by index, and any reset or
cleanup references. Remove the corresponding assertion from the OpenWrt SNMP
tests, while preserving interfaces_to_dict behavior and its SNMP-index
addressing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_snmp/test_openwrt.py`:
- Around line 239-281: Flatten the nested patch.object blocks in the test around
interfaces_to_dict by importing and using contextlib.ExitStack; register the
same nine patches with the stack and keep the call to
self.device.interfaces_to_dict() at the reduced indentation while preserving all
mocked return values.

---

Outside diff comments:
In `@netengine/backends/snmp/airos.py`:
- Around line 490-492: Update the autowalk branch in AirosBackend.to_dict to
catch NetEngineError from the vendor-specific self.walk("1.2.840.10036") call,
log a warning consistent with OpenWRT’s handling, and continue using the general
walk results so device data is still returned. Keep failures from the primary
self.walk("1.3.6") path unchanged.
- Around line 438-448: Update CPU_count to derive the CPU core count from the
load-entry data without dividing by loadNumber or assuming each entry contains
three fields. Use the core/load index OIDs returned by self.next, ensuring
fixtures with three load-average entries and loadNumber equal to 3 return the
existing core count rather than zero.

In `@netengine/backends/snmp/openwrt.py`:
- Around line 312-313: Remove the unused _interface_dict state from the OpenWrt
SNMP implementation: delete its initialization, the loop assigning interface
names by index, and any reset or cleanup references. Remove the corresponding
assertion from the OpenWrt SNMP tests, while preserving interfaces_to_dict
behavior and its SNMP-index addressing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 26952c8c-ff75-4073-98f6-97d5179c50e6

📥 Commits

Reviewing files that changed from the base of the PR and between 436b1e3 and 78c93e1.

📒 Files selected for processing (5)
  • .github/workflows/pypi.yml
  • netengine/backends/snmp/airos.py
  • netengine/backends/snmp/openwrt.py
  • tests/test_snmp/test_airos.py
  • tests/test_snmp/test_openwrt.py
📜 Review details
🔇 Additional comments (5)
netengine/backends/snmp/airos.py (1)

88-88: LGTM!

Also applies to: 131-131, 171-171, 196-196, 224-224, 250-250, 280-280, 483-486

netengine/backends/snmp/openwrt.py (1)

71-71: LGTM!

Also applies to: 115-115, 141-141, 166-166, 215-215, 248-248, 277-277, 523-527, 590-597

tests/test_snmp/test_airos.py (1)

88-101: LGTM!

Also applies to: 156-156

tests/test_snmp/test_openwrt.py (1)

80-93: LGTM!

Also applies to: 184-229, 413-413, 465-486

.github/workflows/pypi.yml (1)

32-32: 🔒 Security & Privacy

No change needed.

The v1.14.2 dereferenced tag commit matches the pinned SHA.

Comment thread tests/test_snmp/test_openwrt.py Outdated
@nemesifier

Copy link
Copy Markdown
Member

@coderabbitai resolve

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
✅ Action performed

Comments resolved and changes approved.

@openwisp-companion

Copy link
Copy Markdown

Proposed change log entry:

[change] Make SNMP backends NetJSON DeviceMonitoring compliant #2

Updated SNMP backends (AirOS and OpenWRT) to be compliant with the
NetJSON DeviceMonitoring specification. This includes changes to the
`to_dict` and `to_json` methods to correctly format collected data. Also
addressed a `KeyError` when calling `to_dict` and improved the
efficiency of OID collection. Documentation for the SNMP backend has
been updated to reflect these changes.

Closes #2
Closes #64
Closes #65

@nemesifier
nemesifier merged commit 01c58cb into master Aug 1, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] Collect all OIDs at once [bug] KeyError: '1' when calling to_dict() [change] NetJSON DeviceMonitoring compliance

4 participants