Skip to content

Commit 98b011c

Browse files
add SNMP API method wrappers in InventoryApp, engine ID validation in global SNMP model, and getter/setter in InventoryDevice for global SNMP config
1 parent fa9aea5 commit 98b011c

3 files changed

Lines changed: 181 additions & 24 deletions

File tree

src/videoipath_automation_tool/apps/inventory/app/app.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
from videoipath_automation_tool.apps.inventory.app.get_device import InventoryGetDeviceMixin
1111
from videoipath_automation_tool.apps.inventory.inventory_api import InventoryAPI
1212
from videoipath_automation_tool.apps.inventory.model.drivers import CustomSettings, CustomSettingsType, DriverLiteral
13+
from videoipath_automation_tool.apps.inventory.model.global_snmp_config import SnmpConfiguration
1314
from videoipath_automation_tool.apps.inventory.model.inventory_device import InventoryDevice
1415
from videoipath_automation_tool.apps.inventory.model.inventory_device_configuration_compare import (
1516
InventoryDeviceComparison,
1617
)
1718
from videoipath_automation_tool.apps.inventory.model.inventory_discovered_device import DiscoveredInventoryDevice
19+
from videoipath_automation_tool.connector.models.response_rpc import ResponseRPC
1820
from videoipath_automation_tool.connector.vip_connector import VideoIPathConnector
1921
from videoipath_automation_tool.validators.device_id import validate_device_id
2022

@@ -349,6 +351,83 @@ def parse_configuration(config: dict) -> InventoryDevice:
349351
"""
350352
return InventoryDevice.parse_configuration(config)
351353

354+
# --- Global SNMP Configuration Helpers ---
355+
def get_global_snmp_config_id_by_label(self, label: str) -> Optional[str | List[str]]:
356+
"""Method to get the global SNMP configuration id by label.
357+
Note: If multiple SNMP configurations with the same label exist, a list of ids is returned.
358+
359+
Args:
360+
label (str): Label of the SNMP configuration
361+
362+
Returns:
363+
Optional[str | List[str]]: SNMP configuration id, None if not found, List of ids if multiple configurations with the same label exist
364+
"""
365+
return self._inventory_api.get_global_snmp_config_id_by_label(label=label)
366+
367+
def get_global_snmp_config_label_by_id(self, snmp_config_id: str) -> Optional[str]:
368+
"""Method to get the global SNMP configuration label by id.
369+
370+
Args:
371+
snmp_config_id (str): SNMP configuration id
372+
373+
Returns:
374+
Optional[str]: SNMP configuration label, None if not found
375+
"""
376+
return self._inventory_api.get_global_snmp_config_label_by_id(snmp_config_id=snmp_config_id)
377+
378+
def get_all_global_snmp_config_ids(self) -> dict[str, str]:
379+
"""Method to list all global SNMP configuration ids with their labels.
380+
381+
Returns:
382+
dict: {snmp_config_id: snmp_config_label}
383+
"""
384+
return self._inventory_api.get_all_global_snmp_config_ids()
385+
386+
# --- Global SNMP Configuration CRUD Methods ---
387+
def get_global_snmp_config(self, snmp_config_id: str) -> SnmpConfiguration:
388+
"""Method to get a global SNMP configuration by id from VideoIPath-Inventory
389+
390+
Args:
391+
snmp_config_id (str): SNMP configuration id
392+
393+
Returns:
394+
GlobalSnmpConfig: Global SNMP configuration object
395+
"""
396+
return self._inventory_api.get_global_snmp_config(snmp_config_id=snmp_config_id)
397+
398+
def add_global_snmp_config(self, snmp_config: SnmpConfiguration) -> SnmpConfiguration:
399+
"""Method to add a new global SNMP configuration
400+
401+
Args:
402+
snmp_config (SnmpConfiguration): SNMP configuration object to add
403+
404+
Returns:
405+
SnmpConfiguration: Added SNMP configuration object
406+
"""
407+
return self._inventory_api.add_global_snmp_config(snmp_config=snmp_config)
408+
409+
def update_global_snmp_config(self, snmp_config: SnmpConfiguration) -> SnmpConfiguration:
410+
"""Method to update a global SNMP configuration
411+
412+
Args:
413+
snmp_config (SnmpConfiguration): SNMP configuration object to update
414+
415+
Returns:
416+
SnmpConfiguration: Updated SNMP configuration object
417+
"""
418+
return self._inventory_api.update_global_snmp_config(snmp_config=snmp_config)
419+
420+
def remove_global_snmp_config(self, snmp_config_id: str) -> ResponseRPC:
421+
"""Method to remove a global SNMP configuration by id from VideoIPath-Inventory
422+
423+
Args:
424+
snmp_config_id (str): SNMP configuration id
425+
426+
Returns:
427+
ResponseRPC: Response object
428+
"""
429+
return self._inventory_api.remove_global_snmp_config(snmp_config_id=snmp_config_id)
430+
352431
# --- Deprecated Methods ---
353432
@deprecated(
354433
"This method is deprecated and will be removed in future versions.",

src/videoipath_automation_tool/apps/inventory/model/global_snmp_config.py

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,34 @@
22
from typing import Literal, cast
33
from uuid import uuid4
44

5-
from pydantic import BaseModel, Field
5+
from pydantic import BaseModel, Field, field_validator
6+
7+
8+
def _validate_engine_id_format(engine_id: str) -> str:
9+
"""
10+
Basic format check for SNMPv3 Engine ID.
11+
- Allows empty string
12+
- Must be valid hex string (after cleaning)
13+
- Length: 5–32 bytes (10–64 hex digits)
14+
- No RFC compliance guaranteed
15+
"""
16+
if not engine_id:
17+
return engine_id # allowed for v1/v2c
18+
19+
cleaned = engine_id.replace(":", "").replace(" ", "").lower()
20+
21+
if len(cleaned) % 2 != 0:
22+
raise ValueError("Engine ID must contain full bytes (even number of hex digits)")
23+
24+
try:
25+
raw = bytes.fromhex(cleaned)
26+
except ValueError:
27+
raise ValueError("Engine ID must be a valid hex string")
28+
29+
if not (5 <= len(raw) <= 32):
30+
raise ValueError("Engine ID must be 5–32 bytes long")
31+
32+
return cleaned
633

734

835
# --- User Enum Classes ---
@@ -44,7 +71,7 @@ class SnmpVersion(int, Enum):
4471

4572

4673
# --- Data Model Classes ---
47-
class SnmpUser(BaseModel):
74+
class SnmpUser(BaseModel, validate_assignment=True):
4875
level: SecurityLevel = SecurityLevel.NO_AUTH_NO_PRIV
4976
name: str = "New User"
5077
authProtocol: AuthProtocol = AuthProtocol.MD5
@@ -53,32 +80,47 @@ class SnmpUser(BaseModel):
5380
privPassword: str = ""
5481
authPassword: str = ""
5582

83+
@field_validator("engineId")
84+
def validate_engine_id(cls, value: str) -> str:
85+
"""
86+
Validates the SNMPv3 Engine ID format.
87+
- Allows empty string
88+
- Must be valid hex string (after cleaning)
89+
- Length: 5–32 bytes (10–64 hex digits)
90+
"""
91+
return _validate_engine_id_format(value)
92+
5693

57-
class SnmpDescriptor(BaseModel):
94+
class SnmpDescriptor(BaseModel, validate_assignment=True):
5895
label: str = ""
5996
desc: str = ""
6097

6198

62-
class SnmpSecurityEntry(BaseModel):
99+
class SnmpSecurityEntry(BaseModel, validate_assignment=True):
63100
user: str = "" # User ID from "Users" section. Must be a valid UUID of an existing user.
64101
community: str = "" # Value from "Protocol Settings => SNMP v1/v2c Security => Write / Read community"
65102

66103

67-
class SnmpSecurity(BaseModel):
104+
class SnmpSecurity(BaseModel, validate_assignment=True):
68105
read: SnmpSecurityEntry = SnmpSecurityEntry(community="public")
69106
write: SnmpSecurityEntry = SnmpSecurityEntry(community="private")
70107

71108

72-
class SnmpProtocolSettings(BaseModel):
109+
class SnmpProtocolSettings(BaseModel, validate_assignment=True):
73110
preferredVersion: SnmpVersion = SnmpVersion.V2C
74-
retries: int = 1
75-
maxRepetitions: int = 10
111+
retries: int = Field(default=1, ge=0)
112+
maxRepetitions: int = Field(default=10, ge=0)
76113
useGetBulk: bool = True
77-
timeout: int = 5000
114+
timeout: int = Field(default=5000, ge=0)
78115
localEngineId: str = ""
79116

117+
@field_validator("localEngineId")
118+
@classmethod
119+
def validate_engine_id(cls, value: str) -> str:
120+
return _validate_engine_id_format(value)
121+
80122

81-
class SnmpConfiguration(BaseModel):
123+
class SnmpConfiguration(BaseModel, validate_assignment=True):
82124
id: str = Field(alias="_id")
83125
descriptor: SnmpDescriptor = Field(default_factory=SnmpDescriptor)
84126
users: dict[str, SnmpUser] = Field(default_factory=dict)
@@ -161,8 +203,6 @@ def retries(self) -> int:
161203
@retries.setter
162204
def retries(self, value: int):
163205
"""Sets the number of retries for SNMP requests."""
164-
if not isinstance(value, int) or value < 0:
165-
raise ValueError("Retries must be a non-negative integer.")
166206
self.protocol.retries = value
167207

168208
@property
@@ -173,8 +213,6 @@ def timeout(self) -> int:
173213
@timeout.setter
174214
def timeout(self, value: int):
175215
"""Sets the timeout for SNMP requests in milliseconds."""
176-
if not isinstance(value, int) or value < 0:
177-
raise ValueError("Timeout must be a non-negative integer.")
178216
self.protocol.timeout = value
179217

180218
@property
@@ -185,8 +223,6 @@ def local_engine_id(self) -> str:
185223
@local_engine_id.setter
186224
def local_engine_id(self, value: str):
187225
"""Sets the local engine ID for SNMP."""
188-
if not isinstance(value, str) or not value:
189-
raise ValueError("Local Engine ID must be a non-empty string.")
190226
self.protocol.localEngineId = value
191227

192228
@property
@@ -197,8 +233,6 @@ def use_get_bulk(self) -> bool:
197233
@use_get_bulk.setter
198234
def use_get_bulk(self, value: bool):
199235
"""Sets whether to use GetBulk for SNMP requests."""
200-
if not isinstance(value, bool):
201-
raise ValueError("Use GetBulk must be a boolean value.")
202236
self.protocol.useGetBulk = value
203237

204238
@property
@@ -209,8 +243,6 @@ def max_repetitions(self) -> int:
209243
@max_repetitions.setter
210244
def max_repetitions(self, value: int):
211245
"""Sets the maximum number of repetitions for SNMP requests."""
212-
if not isinstance(value, int) or value < 0:
213-
raise ValueError("Max Repetitions must be a non-negative integer.")
214246
self.protocol.maxRepetitions = value
215247

216248
@property
@@ -221,8 +253,6 @@ def read_community(self) -> str:
221253
@read_community.setter
222254
def read_community(self, value: str):
223255
"""Sets the read community for SNMP."""
224-
if not isinstance(value, str) or not value:
225-
raise ValueError("Read Community must be a non-empty string.")
226256
self.security.read.community = value
227257

228258
@property
@@ -233,8 +263,6 @@ def write_community(self) -> str:
233263
@write_community.setter
234264
def write_community(self, value: str):
235265
"""Sets the write community for SNMP."""
236-
if not isinstance(value, str) or not value:
237-
raise ValueError("Write Community must be a non-empty string.")
238266
self.security.write.community = value
239267

240268
def list_usernames(self) -> list[str]:

src/videoipath_automation_tool/apps/inventory/model/inventory_device_configuration.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing_extensions import deprecated
55

66
from videoipath_automation_tool.apps.inventory.model.drivers import CustomSettingsType
7+
from videoipath_automation_tool.validators.uuid_4 import validate_uuid_4
78

89

910
class CinfoOverridesSNMP(BaseModel, validate_assignment=True):
@@ -180,6 +181,55 @@ def metadata(self):
180181
def metadata(self, value):
181182
self.meta = value
182183

184+
@property
185+
def use_global_snmp_settings(self) -> bool:
186+
"""Use (activate) global SNMP settings."""
187+
return self.cinfoOverrides.snmp.useDefault
188+
189+
@use_global_snmp_settings.setter
190+
def use_global_snmp_settings(self, value: bool):
191+
"""Use (activate) global SNMP settings."""
192+
self.cinfoOverrides.snmp.useDefault = value
193+
194+
def get_global_snmp_setting_id(self) -> str:
195+
"""
196+
Returns the ID of the global SNMP setting currently in use.
197+
198+
Returns:
199+
str: The ID of the global SNMP setting.
200+
- "default" if the system's default configuration is used.
201+
- Otherwise, returns the UUID of the configured global SNMP setting.
202+
Note: Retrieve the Label of the global SNMP setting using:
203+
`app.inventory.get_global_snmp_config_label_by_id(id)`
204+
"""
205+
return self.cinfoOverrides.snmp.id
206+
207+
def set_global_snmp_setting(self, setting_id: str, activate: bool = True):
208+
"""
209+
Sets the global SNMP setting by ID.
210+
211+
Args:
212+
setting_id (str): The ID of the global SNMP setting to use.
213+
- Use "default" to apply the system's default configuration.
214+
- For other configurations, retrieve the ID by label using:
215+
`app.inventory.get_global_snmp_config_id_by_label(label)`
216+
217+
activate (bool): Whether to activate the global SNMP settings (`use_global_snmp_settings = True`).
218+
Defaults to True. Set to False if you only want to store the ID without activating it yet.
219+
220+
Raises:
221+
ValueError: If the setting ID is empty or not a valid UUID (unless "default").
222+
"""
223+
if not setting_id:
224+
raise ValueError("Setting ID cannot be empty.")
225+
if setting_id != "default":
226+
setting_id = validate_uuid_4(setting_id)
227+
228+
self.cinfoOverrides.snmp.id = setting_id
229+
230+
if activate:
231+
self.cinfoOverrides.snmp.useDefault = True
232+
183233
# --- Deprecated properties ---
184234
@property
185235
@deprecated("The property `custom` is deprecated, use `custom_settings` instead.")

0 commit comments

Comments
 (0)