lvi-api-client 0.2.1__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lvi_api_client-0.2.1 → lvi_api_client-0.2.4}/PKG-INFO +1 -1
- lvi_api_client-0.2.4/VERSION +1 -0
- {lvi_api_client-0.2.1 → lvi_api_client-0.2.4}/src/logicvein/lvi_api_client.py +255 -9
- {lvi_api_client-0.2.1 → lvi_api_client-0.2.4}/src/lvi_api_client.egg-info/PKG-INFO +1 -1
- {lvi_api_client-0.2.1 → lvi_api_client-0.2.4}/tests/test_server_request_shapes.py +167 -0
- lvi_api_client-0.2.1/VERSION +0 -1
- {lvi_api_client-0.2.1 → lvi_api_client-0.2.4}/LICENSE +0 -0
- {lvi_api_client-0.2.1 → lvi_api_client-0.2.4}/README.md +0 -0
- {lvi_api_client-0.2.1 → lvi_api_client-0.2.4}/pyproject.toml +0 -0
- {lvi_api_client-0.2.1 → lvi_api_client-0.2.4}/setup.cfg +0 -0
- {lvi_api_client-0.2.1 → lvi_api_client-0.2.4}/src/logicvein/__init__.py +0 -0
- {lvi_api_client-0.2.1 → lvi_api_client-0.2.4}/src/lvi_api_client.egg-info/SOURCES.txt +0 -0
- {lvi_api_client-0.2.1 → lvi_api_client-0.2.4}/src/lvi_api_client.egg-info/dependency_links.txt +0 -0
- {lvi_api_client-0.2.1 → lvi_api_client-0.2.4}/src/lvi_api_client.egg-info/requires.txt +0 -0
- {lvi_api_client-0.2.1 → lvi_api_client-0.2.4}/src/lvi_api_client.egg-info/top_level.txt +0 -0
- {lvi_api_client-0.2.1 → lvi_api_client-0.2.4}/tests/test_lvi_api_client.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.2.4
|
|
@@ -3863,18 +3863,40 @@ class LogicVeinAPIClient:
|
|
|
3863
3863
|
return False
|
|
3864
3864
|
|
|
3865
3865
|
def associate_monitor_set(self, set_id: int, host_queries: List[str]) -> bool:
|
|
3866
|
-
"""
|
|
3866
|
+
"""Deploy a monitor set to the devices that match every one of the given queries.
|
|
3867
3867
|
|
|
3868
|
-
Calls Collection.associateMonitorSet.
|
|
3868
|
+
Calls Collection.associateMonitorSet. The server answers null whether or not any device
|
|
3869
|
+
matched, so check :meth:`get_monitors_for_host` to know what was deployed.
|
|
3870
|
+
|
|
3871
|
+
The server combines the queries with AND, so one entry for each device matches none of
|
|
3872
|
+
them. Name several devices in one entry, each with its network:
|
|
3873
|
+
``['ipCsv=192.0.2.1@Default,192.0.2.2@Default']``. An address with no network of its own is
|
|
3874
|
+
not looked up in the network named after a later one. Another query can narrow it:
|
|
3875
|
+
``['ipCsv=192.0.2.1@Default,192.0.2.2@Default', 'ipAddress=192.0.2.1']``.
|
|
3876
|
+
|
|
3877
|
+
With no queries the server deploys to every device, so that is refused here rather than
|
|
3878
|
+
passed on from an empty list.
|
|
3869
3879
|
|
|
3870
3880
|
Args:
|
|
3871
3881
|
set_id (int): The ID of the monitor set to associate.
|
|
3872
|
-
host_queries (List[str]):
|
|
3873
|
-
|
|
3882
|
+
host_queries (List[str]): The queries, at least one, none of them blank, and at most
|
|
3883
|
+
one of them an ``ipCsv`` query.
|
|
3874
3884
|
|
|
3875
3885
|
Returns:
|
|
3876
|
-
bool: True if
|
|
3877
|
-
|
|
3886
|
+
bool: True if the server answered without an error, otherwise False. This does not
|
|
3887
|
+
mean any device matched.
|
|
3888
|
+
|
|
3889
|
+
Raises:
|
|
3890
|
+
ValueError: If there is no query, a query is blank, or more than one is ``ipCsv``.
|
|
3891
|
+
Nothing is sent.
|
|
3892
|
+
"""
|
|
3893
|
+
if not host_queries or any(not query.strip() for query in host_queries):
|
|
3894
|
+
raise ValueError("host_queries needs at least one query and no blank ones: none would match every device")
|
|
3895
|
+
if sum(query.strip().startswith("ipCsv=") for query in host_queries) > 1:
|
|
3896
|
+
raise ValueError(
|
|
3897
|
+
"Queries combine with AND, so several ipCsv queries cannot name several devices. "
|
|
3898
|
+
"List them in one: ipCsv=IP@Network,IP@Network"
|
|
3899
|
+
)
|
|
3878
3900
|
response = self.call("Collection.associateMonitorSet", setId=set_id, hostQueries=host_queries)
|
|
3879
3901
|
|
|
3880
3902
|
if response and isinstance(response, dict) and "result" in response and response["result"] in (None, "None"):
|
|
@@ -3887,14 +3909,22 @@ class LogicVeinAPIClient:
|
|
|
3887
3909
|
Calls Collection.addMonitorsToSet. The setId must be provided both as a top-level
|
|
3888
3910
|
parameter and within each monitor object. Returns the saved monitors with assigned IDs.
|
|
3889
3911
|
|
|
3912
|
+
This always adds: the server stores a new monitor even for a name already in the
|
|
3913
|
+
set, or with the ID of an existing monitor, and leaves the old one as it was. Use
|
|
3914
|
+
:meth:`save_monitor` to change one.
|
|
3915
|
+
|
|
3916
|
+
The server answers success but stores neither ``disabled`` nor ``hostSpecific`` as true,
|
|
3917
|
+
here or in :meth:`save_monitor`. To stop a monitor on a device, use
|
|
3918
|
+
:meth:`disable_monitor_for_host`.
|
|
3919
|
+
|
|
3890
3920
|
Each monitor object should have:
|
|
3891
3921
|
{
|
|
3892
3922
|
"monitorId": int - Use -1 for new monitors; assigned ID returned in response,
|
|
3893
3923
|
"monitorName": str - Monitor name,
|
|
3894
3924
|
"setId": int - Must match set_id,
|
|
3895
3925
|
"monitorType": str - Type (e.g. "snmp", "tcp", "icmp", "telegraf", "web", "catchalltrap"),
|
|
3896
|
-
"disabled": bool -
|
|
3897
|
-
"hostSpecific": bool -
|
|
3926
|
+
"disabled": bool - Stored as false whatever is sent,
|
|
3927
|
+
"hostSpecific": bool - Stored as false whatever is sent,
|
|
3898
3928
|
"managedNetworks": list[str] - Networks the monitor belongs to,
|
|
3899
3929
|
"retentionPolicy": str - Data retention (e.g. "THREE_MONTHS"),
|
|
3900
3930
|
"json": dict - Monitor-type-specific config (OIDs, pollingPeriod, table, triggers, etc.),
|
|
@@ -3905,7 +3935,7 @@ class LogicVeinAPIClient:
|
|
|
3905
3935
|
|
|
3906
3936
|
Args:
|
|
3907
3937
|
set_id (int): The monitor set ID to add monitors to.
|
|
3908
|
-
monitors (List[Dict[str, Any]]): List of monitor objects to add
|
|
3938
|
+
monitors (List[Dict[str, Any]]): List of monitor objects to add.
|
|
3909
3939
|
|
|
3910
3940
|
Returns:
|
|
3911
3941
|
List[Dict[str, Any]]: List of saved monitor objects with assigned monitorIds.
|
|
@@ -3936,6 +3966,222 @@ class LogicVeinAPIClient:
|
|
|
3936
3966
|
return True
|
|
3937
3967
|
return False
|
|
3938
3968
|
|
|
3969
|
+
def create_monitor_set(
|
|
3970
|
+
self, set_name: str, managed_networks: List[str], smart_set: bool = False
|
|
3971
|
+
) -> Optional[Dict[str, Any]]:
|
|
3972
|
+
"""Create a monitor set.
|
|
3973
|
+
|
|
3974
|
+
A name already taken in those networks makes the server answer HTTP 500, which
|
|
3975
|
+
raises, and stores nothing.
|
|
3976
|
+
|
|
3977
|
+
Args:
|
|
3978
|
+
set_name (str): The set's name. Use :meth:`is_monitor_set_name_unique` to check it first.
|
|
3979
|
+
managed_networks (List[str]): The networks the set belongs to, at least one.
|
|
3980
|
+
smart_set (bool): Whether this is a smart set, one applied automatically to newly
|
|
3981
|
+
discovered devices.
|
|
3982
|
+
|
|
3983
|
+
Returns:
|
|
3984
|
+
Dict[str, Any]: The saved set (``setId``, ``setName``, ``smartSet``,
|
|
3985
|
+
``managedNetworks``), or None on failure.
|
|
3986
|
+
|
|
3987
|
+
Raises:
|
|
3988
|
+
ValueError: If no network is given. An empty list was not tried against a
|
|
3989
|
+
server, as a failed rule set save with no networks leaves a row that
|
|
3990
|
+
cannot be removed.
|
|
3991
|
+
"""
|
|
3992
|
+
if not managed_networks:
|
|
3993
|
+
raise ValueError("A monitor set needs at least one managed network")
|
|
3994
|
+
return self._save_monitor_set(
|
|
3995
|
+
{"setId": -1, "setName": set_name, "smartSet": smart_set, "managedNetworks": managed_networks}
|
|
3996
|
+
)
|
|
3997
|
+
|
|
3998
|
+
def update_monitor_set(
|
|
3999
|
+
self,
|
|
4000
|
+
set_id: int,
|
|
4001
|
+
set_name: Optional[str] = None,
|
|
4002
|
+
managed_networks: Optional[List[str]] = None,
|
|
4003
|
+
smart_set: Optional[bool] = None,
|
|
4004
|
+
) -> Optional[Dict[str, Any]]:
|
|
4005
|
+
"""Change a monitor set, keeping what is not given.
|
|
4006
|
+
|
|
4007
|
+
Args:
|
|
4008
|
+
set_id (int): The ID of the set.
|
|
4009
|
+
set_name (Optional[str]): A new name, or None to keep it.
|
|
4010
|
+
managed_networks (Optional[List[str]]): New networks, at least one, or None to keep them.
|
|
4011
|
+
smart_set (Optional[bool]): Whether it is a smart set, or None to keep it.
|
|
4012
|
+
|
|
4013
|
+
Returns:
|
|
4014
|
+
Dict[str, Any]: The saved set, or None on failure.
|
|
4015
|
+
|
|
4016
|
+
Raises:
|
|
4017
|
+
ValueError: If the set does not exist, or an empty list of networks is given.
|
|
4018
|
+
"""
|
|
4019
|
+
if managed_networks is not None and not managed_networks:
|
|
4020
|
+
raise ValueError("A monitor set needs at least one managed network")
|
|
4021
|
+
monitor_set = self.get_monitor_set(set_id)
|
|
4022
|
+
if not monitor_set:
|
|
4023
|
+
raise ValueError(f"Monitor set {set_id} does not exist")
|
|
4024
|
+
changes = {"setName": set_name, "managedNetworks": managed_networks, "smartSet": smart_set}
|
|
4025
|
+
return self._save_monitor_set({**monitor_set, **{k: v for k, v in changes.items() if v is not None}})
|
|
4026
|
+
|
|
4027
|
+
def _save_monitor_set(self, monitor_set: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
4028
|
+
"""Save a monitor set record.
|
|
4029
|
+
|
|
4030
|
+
Args:
|
|
4031
|
+
monitor_set (Dict[str, Any]): The whole record. A ``setId`` of -1 creates a set.
|
|
4032
|
+
|
|
4033
|
+
Returns:
|
|
4034
|
+
Dict[str, Any]: The saved set, or None on failure.
|
|
4035
|
+
"""
|
|
4036
|
+
response = self.call("Collection.saveMonitorSet", monitorSet=monitor_set)
|
|
4037
|
+
if response and isinstance(response, dict) and "result" in response:
|
|
4038
|
+
return response["result"]
|
|
4039
|
+
return None
|
|
4040
|
+
|
|
4041
|
+
def delete_monitor_sets(self, set_ids: List[int]) -> bool:
|
|
4042
|
+
"""Delete monitor sets, with their monitors.
|
|
4043
|
+
|
|
4044
|
+
This also takes those monitors off the devices they were deployed to. The server
|
|
4045
|
+
answers success for a set that does not exist, so True does not show that one did.
|
|
4046
|
+
|
|
4047
|
+
Args:
|
|
4048
|
+
set_ids (List[int]): The IDs of the sets to delete.
|
|
4049
|
+
|
|
4050
|
+
Returns:
|
|
4051
|
+
bool: True if successful (result is null), otherwise False.
|
|
4052
|
+
"""
|
|
4053
|
+
response = self.call("Collection.deleteMonitorSets", setIds=set_ids)
|
|
4054
|
+
return bool(response and isinstance(response, dict) and "result" in response and response["result"] is None)
|
|
4055
|
+
|
|
4056
|
+
def is_monitor_set_name_unique(self, set_name: str, managed_networks: List[str], ignore_set_id: int = -1) -> bool:
|
|
4057
|
+
"""Check whether a monitor set name is free in some networks.
|
|
4058
|
+
|
|
4059
|
+
Args:
|
|
4060
|
+
set_name (str): The name to check.
|
|
4061
|
+
managed_networks (List[str]): The networks to check it in.
|
|
4062
|
+
ignore_set_id (int): A set to leave out, such as the one being renamed. -1 for none.
|
|
4063
|
+
|
|
4064
|
+
Returns:
|
|
4065
|
+
bool: True if the name is free.
|
|
4066
|
+
|
|
4067
|
+
Raises:
|
|
4068
|
+
ValueError: If the server's answer is not true or false.
|
|
4069
|
+
"""
|
|
4070
|
+
response = self.call(
|
|
4071
|
+
"Collection.isMonitorSetNameUnique",
|
|
4072
|
+
setName=set_name,
|
|
4073
|
+
managedNetworks=managed_networks,
|
|
4074
|
+
ignoreSetId=ignore_set_id,
|
|
4075
|
+
)
|
|
4076
|
+
unique = response.get("result") if isinstance(response, dict) else None
|
|
4077
|
+
if not isinstance(unique, bool):
|
|
4078
|
+
raise ValueError(f"Collection.isMonitorSetNameUnique did not answer true or false for {set_name}")
|
|
4079
|
+
return unique
|
|
4080
|
+
|
|
4081
|
+
def save_monitor(self, monitor: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
4082
|
+
"""Update a monitor in place.
|
|
4083
|
+
|
|
4084
|
+
The monitor keeps its ID, and a device it is deployed to gets the change. A change to
|
|
4085
|
+
``disabled`` or ``isHostSpecific`` is answered with success and not stored, and a changed
|
|
4086
|
+
``monitorType`` is answered with HTTP 500, which raises.
|
|
4087
|
+
|
|
4088
|
+
Args:
|
|
4089
|
+
monitor (Dict[str, Any]): The whole record as :meth:`get_monitors_for_set` returns
|
|
4090
|
+
it, with the changes made.
|
|
4091
|
+
|
|
4092
|
+
Returns:
|
|
4093
|
+
Dict[str, Any]: The saved monitor, or None on failure.
|
|
4094
|
+
"""
|
|
4095
|
+
response = self.call("Collection.saveMonitor", monitor=monitor)
|
|
4096
|
+
if response and isinstance(response, dict) and "result" in response:
|
|
4097
|
+
return response["result"]
|
|
4098
|
+
return None
|
|
4099
|
+
|
|
4100
|
+
def get_monitors_for_host(self, device_uuid: str, network: str) -> List[Dict[str, Any]]:
|
|
4101
|
+
"""Get the monitors deployed to a device.
|
|
4102
|
+
|
|
4103
|
+
Args:
|
|
4104
|
+
device_uuid (str): UUID of the device.
|
|
4105
|
+
network (str): Network the device belongs to.
|
|
4106
|
+
|
|
4107
|
+
Returns:
|
|
4108
|
+
List[Dict[str, Any]]: The monitors, each with ``monitorId``, ``monitorName``, ``setId``
|
|
4109
|
+
and ``disabled``, which is the state on this device. The list is empty only when the
|
|
4110
|
+
server says the device has none,
|
|
4111
|
+
which it also says for a device that does not exist.
|
|
4112
|
+
|
|
4113
|
+
Raises:
|
|
4114
|
+
ValueError: If the server's answer is not a list. Callers check this list before
|
|
4115
|
+
deploying, so a failed read must not look like no monitors.
|
|
4116
|
+
"""
|
|
4117
|
+
response = self.call("Collection.getMonitorsForHost", uuid=device_uuid, network=network)
|
|
4118
|
+
monitors = response.get("result") if isinstance(response, dict) else None
|
|
4119
|
+
if not isinstance(monitors, list):
|
|
4120
|
+
raise ValueError(f"Collection.getMonitorsForHost did not return a list for device {device_uuid}")
|
|
4121
|
+
return monitors
|
|
4122
|
+
|
|
4123
|
+
def disable_monitor_for_host(self, monitor_id: int, device_uuid: str, network: str) -> bool:
|
|
4124
|
+
"""Stop a deployed monitor on one device, leaving it deployed and other devices as they are.
|
|
4125
|
+
|
|
4126
|
+
Deploying the set again keeps the monitor disabled, and repeating this does no harm.
|
|
4127
|
+
|
|
4128
|
+
Args:
|
|
4129
|
+
monitor_id (int): ID of the monitor.
|
|
4130
|
+
device_uuid (str): UUID of the device.
|
|
4131
|
+
network (str): Network the device belongs to.
|
|
4132
|
+
|
|
4133
|
+
Returns:
|
|
4134
|
+
bool: True if the device now shows the monitor disabled. False if it does not, as for
|
|
4135
|
+
a monitor not deployed to that device.
|
|
4136
|
+
|
|
4137
|
+
Raises:
|
|
4138
|
+
ValueError: If the device's monitors cannot be read afterwards.
|
|
4139
|
+
"""
|
|
4140
|
+
return self._set_monitor_disabled_for_host("disableMonitorForHost", monitor_id, device_uuid, network, True)
|
|
4141
|
+
|
|
4142
|
+
def enable_monitor_for_host(self, monitor_id: int, device_uuid: str, network: str) -> bool:
|
|
4143
|
+
"""Start a monitor again on a device where it was disabled.
|
|
4144
|
+
|
|
4145
|
+
Repeating this does no harm.
|
|
4146
|
+
|
|
4147
|
+
Args:
|
|
4148
|
+
monitor_id (int): ID of the monitor.
|
|
4149
|
+
device_uuid (str): UUID of the device.
|
|
4150
|
+
network (str): Network the device belongs to.
|
|
4151
|
+
|
|
4152
|
+
Returns:
|
|
4153
|
+
bool: True if the device now shows the monitor enabled. False if it does not, as for
|
|
4154
|
+
a monitor not deployed to that device.
|
|
4155
|
+
|
|
4156
|
+
Raises:
|
|
4157
|
+
ValueError: If the device's monitors cannot be read afterwards.
|
|
4158
|
+
"""
|
|
4159
|
+
return self._set_monitor_disabled_for_host("enableMonitorForHost", monitor_id, device_uuid, network, False)
|
|
4160
|
+
|
|
4161
|
+
def _set_monitor_disabled_for_host(
|
|
4162
|
+
self, method: str, monitor_id: int, device_uuid: str, network: str, disabled: bool
|
|
4163
|
+
) -> bool:
|
|
4164
|
+
"""Call a per-device enable or disable, and read back whether it took effect.
|
|
4165
|
+
|
|
4166
|
+
The server answers null to these whatever happens, even for an unknown monitor or
|
|
4167
|
+
device or the wrong network, so only the device's own monitors can show the outcome.
|
|
4168
|
+
|
|
4169
|
+
Args:
|
|
4170
|
+
method (str): The method's name under Collection.
|
|
4171
|
+
monitor_id (int): ID of the monitor.
|
|
4172
|
+
device_uuid (str): UUID of the device.
|
|
4173
|
+
network (str): Network the device belongs to.
|
|
4174
|
+
disabled (bool): The state the call should leave.
|
|
4175
|
+
|
|
4176
|
+
Returns:
|
|
4177
|
+
bool: Whether the device shows the monitor in that state.
|
|
4178
|
+
"""
|
|
4179
|
+
self.call(f"Collection.{method}", monitorId=monitor_id, uuid=device_uuid, network=network)
|
|
4180
|
+
return any(
|
|
4181
|
+
monitor.get("monitorId") == monitor_id and monitor.get("disabled") is disabled
|
|
4182
|
+
for monitor in self.get_monitors_for_host(device_uuid, network)
|
|
4183
|
+
)
|
|
4184
|
+
|
|
3939
4185
|
def validate_monitor_sets_against_device(
|
|
3940
4186
|
self, monitor_set_ids: List[int], host_queries: List[str]
|
|
3941
4187
|
) -> Dict[str, Any]:
|
|
@@ -329,6 +329,96 @@ class TestTriggers:
|
|
|
329
329
|
assert server.sent("Scheduler.deleteTrigger") == {"triggerId": 3}
|
|
330
330
|
|
|
331
331
|
|
|
332
|
+
class TestMonitorSets:
|
|
333
|
+
SET = {"setId": 5, "setName": "s1", "smartSet": False, "managedNetworks": ["n1"]}
|
|
334
|
+
|
|
335
|
+
def test_a_new_set_is_saved_with_an_id_of_minus_one(self, connect):
|
|
336
|
+
client, server = connect({"Collection.saveMonitorSet": self.SET})
|
|
337
|
+
assert client.create_monitor_set("s1", ["n1"]) == self.SET
|
|
338
|
+
assert server.sent("Collection.saveMonitorSet") == {
|
|
339
|
+
"monitorSet": {"setId": -1, "setName": "s1", "smartSet": False, "managedNetworks": ["n1"]}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
def test_a_new_smart_set_says_so(self, connect):
|
|
343
|
+
client, server = connect({"Collection.saveMonitorSet": self.SET})
|
|
344
|
+
client.create_monitor_set("s1", ["n1"], smart_set=True)
|
|
345
|
+
assert server.sent("Collection.saveMonitorSet")["monitorSet"]["smartSet"] is True
|
|
346
|
+
|
|
347
|
+
def test_a_new_set_with_no_networks_sends_nothing(self, connect):
|
|
348
|
+
client, server = connect()
|
|
349
|
+
with pytest.raises(ValueError):
|
|
350
|
+
client.create_monitor_set("s1", [])
|
|
351
|
+
assert server.requests == []
|
|
352
|
+
|
|
353
|
+
def test_an_update_keeps_what_it_is_not_given(self, connect):
|
|
354
|
+
client, server = connect({"Collection.getMonitorSet": self.SET, "Collection.saveMonitorSet": self.SET})
|
|
355
|
+
client.update_monitor_set(5, set_name="s2")
|
|
356
|
+
assert server.sent("Collection.saveMonitorSet") == {"monitorSet": {**self.SET, "setName": "s2"}}
|
|
357
|
+
|
|
358
|
+
def test_an_update_can_clear_smart_set(self, connect):
|
|
359
|
+
stored = {**self.SET, "smartSet": True}
|
|
360
|
+
client, server = connect({"Collection.getMonitorSet": stored, "Collection.saveMonitorSet": self.SET})
|
|
361
|
+
client.update_monitor_set(5, smart_set=False)
|
|
362
|
+
assert server.sent("Collection.saveMonitorSet")["monitorSet"]["smartSet"] is False
|
|
363
|
+
|
|
364
|
+
@pytest.mark.parametrize(
|
|
365
|
+
"stored, args",
|
|
366
|
+
[
|
|
367
|
+
pytest.param(None, {"set_name": "s2"}, id="no such set"),
|
|
368
|
+
pytest.param(SET, {"managed_networks": []}, id="no networks"),
|
|
369
|
+
],
|
|
370
|
+
)
|
|
371
|
+
def test_an_update_that_cannot_apply_saves_nothing(self, connect, stored, args):
|
|
372
|
+
client, server = connect({"Collection.getMonitorSet": stored})
|
|
373
|
+
with pytest.raises(ValueError):
|
|
374
|
+
client.update_monitor_set(5, **args)
|
|
375
|
+
assert "Collection.saveMonitorSet" not in [name for name, _ in server.requests]
|
|
376
|
+
|
|
377
|
+
@pytest.mark.parametrize("answer, expected", [(None, True), (False, False)])
|
|
378
|
+
def test_a_delete_is_by_set_ids_and_null_is_success(self, connect, answer, expected):
|
|
379
|
+
client, server = connect({"Collection.deleteMonitorSets": answer})
|
|
380
|
+
assert client.delete_monitor_sets([5, 6]) is expected
|
|
381
|
+
assert server.sent("Collection.deleteMonitorSets") == {"setIds": [5, 6]}
|
|
382
|
+
|
|
383
|
+
@pytest.mark.parametrize("answer", [True, False])
|
|
384
|
+
def test_a_name_check_sends_its_scope_and_returns_the_answer(self, connect, answer):
|
|
385
|
+
client, server = connect({"Collection.isMonitorSetNameUnique": answer})
|
|
386
|
+
assert client.is_monitor_set_name_unique("s1", ["n1"], ignore_set_id=5) is answer
|
|
387
|
+
assert server.sent("Collection.isMonitorSetNameUnique") == {
|
|
388
|
+
"setName": "s1",
|
|
389
|
+
"managedNetworks": ["n1"],
|
|
390
|
+
"ignoreSetId": 5,
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
def test_a_name_check_with_no_answer_raises_rather_than_reading_as_taken(self, connect):
|
|
394
|
+
client, _ = connect({"Collection.isMonitorSetNameUnique": None})
|
|
395
|
+
with pytest.raises(ValueError):
|
|
396
|
+
client.is_monitor_set_name_unique("s1", ["n1"])
|
|
397
|
+
|
|
398
|
+
def test_a_monitor_is_saved_as_a_whole_record(self, connect):
|
|
399
|
+
monitor = {"monitorId": 3, "monitorName": "m1", "setId": 5, "json": {"port": 22}}
|
|
400
|
+
client, server = connect({"Collection.saveMonitor": monitor})
|
|
401
|
+
assert client.save_monitor(monitor) == monitor
|
|
402
|
+
assert server.sent("Collection.saveMonitor") == {"monitor": monitor}
|
|
403
|
+
|
|
404
|
+
def test_a_devices_monitors_are_read_by_uuid_and_network(self, connect):
|
|
405
|
+
rows = [{"monitorId": 3, "monitorName": "m1", "setId": 5}]
|
|
406
|
+
client, server = connect({"Collection.getMonitorsForHost": rows})
|
|
407
|
+
assert client.get_monitors_for_host("u1", "n1") == rows
|
|
408
|
+
assert server.sent("Collection.getMonitorsForHost") == {"uuid": "u1", "network": "n1"}
|
|
409
|
+
|
|
410
|
+
def test_a_device_with_no_monitors_gives_an_empty_list(self, connect):
|
|
411
|
+
client, _ = connect({"Collection.getMonitorsForHost": []})
|
|
412
|
+
assert client.get_monitors_for_host("u1", "n1") == []
|
|
413
|
+
|
|
414
|
+
@pytest.mark.parametrize("answer", [None, {"result": None}, {}, "<html>error</html>"])
|
|
415
|
+
def test_an_answer_that_is_not_a_list_raises_rather_than_reading_as_no_monitors(self, connect, answer):
|
|
416
|
+
client, _ = connect()
|
|
417
|
+
with patch.object(client, "call", return_value=answer):
|
|
418
|
+
with pytest.raises(ValueError):
|
|
419
|
+
client.get_monitors_for_host("u1", "n1")
|
|
420
|
+
|
|
421
|
+
|
|
332
422
|
class TestCreateRuleSet:
|
|
333
423
|
def test_an_agreeing_document_is_sent_unchanged(self, connect):
|
|
334
424
|
client, server = connect(SAVED)
|
|
@@ -453,3 +543,80 @@ class TestCreateRuleSet:
|
|
|
453
543
|
with pytest.raises(ValueError):
|
|
454
544
|
client.create_rule_set(name, adapter, document, **arguments)
|
|
455
545
|
assert server.requests == []
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
class TestMonitorStateOnADevice:
|
|
549
|
+
"""On a real server these answer null whatever happens, so the device's monitors decide."""
|
|
550
|
+
|
|
551
|
+
@pytest.mark.parametrize(
|
|
552
|
+
"call, method, disabled",
|
|
553
|
+
[
|
|
554
|
+
("disable_monitor_for_host", "Collection.disableMonitorForHost", True),
|
|
555
|
+
("enable_monitor_for_host", "Collection.enableMonitorForHost", False),
|
|
556
|
+
],
|
|
557
|
+
)
|
|
558
|
+
def test_the_request_and_a_read_back_of_the_device(self, connect, call, method, disabled):
|
|
559
|
+
client, server = connect(
|
|
560
|
+
{method: None, "Collection.getMonitorsForHost": [{"monitorId": 7, "disabled": disabled}]}
|
|
561
|
+
)
|
|
562
|
+
assert getattr(client, call)(7, "u1", "n1") is True
|
|
563
|
+
assert server.requests == [
|
|
564
|
+
(method, {"monitorId": 7, "uuid": "u1", "network": "n1"}),
|
|
565
|
+
("Collection.getMonitorsForHost", {"uuid": "u1", "network": "n1"}),
|
|
566
|
+
]
|
|
567
|
+
|
|
568
|
+
@pytest.mark.parametrize(
|
|
569
|
+
"call, rows",
|
|
570
|
+
[
|
|
571
|
+
pytest.param("disable_monitor_for_host", [{"monitorId": 7, "disabled": False}], id="disable did not take"),
|
|
572
|
+
pytest.param("enable_monitor_for_host", [{"monitorId": 7, "disabled": True}], id="enable did not take"),
|
|
573
|
+
pytest.param("disable_monitor_for_host", [], id="not on the device"),
|
|
574
|
+
pytest.param("disable_monitor_for_host", [{"monitorId": 8, "disabled": True}], id="another monitor"),
|
|
575
|
+
pytest.param(
|
|
576
|
+
"enable_monitor_for_host", [{"monitorId": 8, "disabled": False}], id="another monitor, enable"
|
|
577
|
+
),
|
|
578
|
+
],
|
|
579
|
+
)
|
|
580
|
+
def test_a_null_answer_is_not_success(self, connect, call, rows):
|
|
581
|
+
client, _ = connect({"Collection.getMonitorsForHost": rows})
|
|
582
|
+
assert getattr(client, call)(7, "u1", "n1") is False
|
|
583
|
+
|
|
584
|
+
def test_a_failed_read_raises(self, connect):
|
|
585
|
+
client, _ = connect({"Collection.getMonitorsForHost": None})
|
|
586
|
+
with pytest.raises(ValueError):
|
|
587
|
+
client.disable_monitor_for_host(7, "u1", "n1")
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
class TestDeployingASet:
|
|
591
|
+
"""On a real server several ipCsv queries match nothing, and no query matches every device."""
|
|
592
|
+
|
|
593
|
+
@pytest.mark.parametrize(
|
|
594
|
+
"queries",
|
|
595
|
+
[
|
|
596
|
+
pytest.param(["ipCsv=192.0.2.1@n1"], id="one device"),
|
|
597
|
+
pytest.param(["ipCsv=192.0.2.1@n1,192.0.2.2@n1"], id="several devices in one entry"),
|
|
598
|
+
pytest.param(["ipCsv=192.0.2.1@n1,192.0.2.2@n1", "ipAddress=192.0.2.1"], id="narrowed by another query"),
|
|
599
|
+
pytest.param(["sysObjectID=1.3.6.1.4.1.9.1.324", "sysDescr=*C2950*"], id="other queries together"),
|
|
600
|
+
],
|
|
601
|
+
)
|
|
602
|
+
def test_queries_the_server_can_act_on_are_sent_as_given(self, connect, queries):
|
|
603
|
+
client, server = connect({"Collection.associateMonitorSet": None})
|
|
604
|
+
assert client.associate_monitor_set(2, queries) is True
|
|
605
|
+
assert server.sent("Collection.associateMonitorSet") == {"setId": 2, "hostQueries": queries}
|
|
606
|
+
|
|
607
|
+
@pytest.mark.parametrize(
|
|
608
|
+
"queries",
|
|
609
|
+
[
|
|
610
|
+
pytest.param([], id="no queries"),
|
|
611
|
+
pytest.param([""], id="a blank query"),
|
|
612
|
+
pytest.param([" "], id="a query of spaces"),
|
|
613
|
+
pytest.param(["ipCsv=192.0.2.1@n1", ""], id="a blank among others"),
|
|
614
|
+
pytest.param(["ipCsv=192.0.2.1@n1", "ipCsv=192.0.2.2@n1"], id="one entry per device"),
|
|
615
|
+
pytest.param(["ipCsv=192.0.2.1@n1", " ipCsv=192.0.2.2@n1"], id="one entry per device, spaced"),
|
|
616
|
+
],
|
|
617
|
+
)
|
|
618
|
+
def test_queries_that_would_match_everything_or_nothing_are_never_sent(self, connect, queries):
|
|
619
|
+
client, server = connect({"Collection.associateMonitorSet": None})
|
|
620
|
+
with pytest.raises(ValueError):
|
|
621
|
+
client.associate_monitor_set(2, queries)
|
|
622
|
+
assert server.requests == []
|
lvi_api_client-0.2.1/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.2.1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lvi_api_client-0.2.1 → lvi_api_client-0.2.4}/src/lvi_api_client.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|