Merge pull request #62666 from ITJamie/netbox-pillar-related-devices

pillar: netbox: create "connected_devices" function
This commit is contained in:
Megan Wilhite 2022-09-28 09:26:04 -06:00 committed by GitHub
commit 68bc569bf8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 545 additions and 27 deletions

1
changelog/62761.added Normal file
View file

@ -0,0 +1 @@
Added "connected_devices" feature to netbox pillar module. It contains extra information about devices connected to the minion

View file

@ -82,6 +82,12 @@ api_query_result_limit: ``Use NetBox default``
An integer specifying how many results should be returned for each query
to the NetBox API. Leaving this unset will use NetBox's default value.
connected_devices: ``False``
.. versionadded:: 3006
Whether connected_devices key should be populated with device objects.
If set to True it will force `interfaces` to also be true as a dependency
Note that each option you enable can have a detrimental impact on pillar
performance, so use them with caution.
@ -520,6 +526,160 @@ Example output:
tags:
custom_fields:
config_context:
connected_devices:
----------
512:
----------
airflow:
None
asset_tag:
001
cluster:
None
comments:
config_context:
created:
2022-03-10T00:00:00Z
custom_fields:
device_role:
----------
display:
Network switch
id:
512
name:
Network switch
slug:
network_switch
url:
https://netbox.example.com/api/dcim/device-roles/5/
device_type:
----------
display:
Nexus 3048
id:
40
manufacturer:
----------
display:
Cisco
id:
1
name:
Cisco
slug:
cisco
url:
https://netbox.example.com/api/dcim/manufacturers/1/
model:
Nexus 3048
slug:
n3k-c3048tp-1ge
url:
https://netbox.example.com/api/dcim/device-types/40/
display:
another device (001)
face:
----------
label:
Front
value:
front
id:
1533
last_updated:
2022-08-22T13:50:15.923868Z
local_context_data:
None
location:
----------
_depth:
2
display:
Location Name
id:
2
name:
Location Name
slug:
location-name
url:
https://netbox.example.com/api/dcim/locations/2
name:
another device
parent_device:
None
platform:
None
position:
18.0
primary_ip:
----------
address:
192.168.1.1/24
display:
192.168.1.1/24
family:
4
id:
1234
url:
https://netbox.example.com/api/ipam/ip-addresses/1234/
primary_ip4:
----------
address:
192.168.1.1/24
display:
192.168.1.1/24
family:
4
id:
1234
url:
https://netbox.example.com/api/ipam/ip-addresses/1234/
primary_ip6:
None
rack:
----------
display:
RackName
id:
139
name:
RackName
url:
https://netbox.example.com/api/dcim/racks/139/
serial:
ABCD12345
site:
----------
display:
SiteName
id:
2
name:
SiteName
slug:
sitename
url:
https://netbox.example.com/api/dcim/sites/2/
status:
----------
label:
Active
value:
active
tags:
tenant:
None
url:
https://netbox.example.com/api/dcim/devices/1533/
vc_position:
None
vc_priority:
None
virtual_chassis:
None
created:
2021-02-19
last_updated:
@ -777,6 +937,40 @@ def _get_site_details(api_url, minion_id, site_name, site_id, headers):
return site_details_ret["dict"]
def _get_connected_devices(api_url, minion_id, interfaces, headers):
log.debug('Retrieving connected devices for "%s"', minion_id)
connected_devices_result = {}
connected_devices_ids = []
for int_short in interfaces:
if "connected_endpoints" in int_short.keys():
if int_short["connected_endpoints"]:
for device_short in int_short["connected_endpoints"]:
if (
"device" in device_short.keys()
and not device_short["device"]["id"] in connected_devices_ids
):
connected_devices_ids.append(device_short["device"]["id"])
log.debug("connected_devices_ids: %s", connected_devices_ids)
for dev_id in connected_devices_ids:
device_url = "{api_url}/{app}/{endpoint}/{dev_id}".format(
api_url=api_url, app="dcim", endpoint="devices", dev_id=dev_id
)
device_results = []
device_ret = salt.utils.http.query(device_url, header_dict=headers, decode=True)
if "error" in device_ret:
log.error(
'API query failed for "%s", status code: %d, error %s',
minion_id,
device_ret["status"],
device_ret["error"],
)
else:
connected_devices_result[dev_id] = dict(device_ret["dict"])
return connected_devices_result
def _get_site_prefixes(
api_url, minion_id, site_name, site_id, headers, api_query_result_limit
):
@ -876,6 +1070,13 @@ def ext_pillar(minion_id, pillar, *args, **kwargs):
site_prefixes = kwargs.get("site_prefixes", True)
proxy_username = kwargs.get("proxy_username", None)
proxy_return = kwargs.get("proxy_return", True)
connected_devices = kwargs.get("connected_devices", False)
if connected_devices and not interfaces:
# connected_devices logic requires interfaces to be populated
interfaces = True
log.debug(
"netbox pillar interfaces set to 'True' as connected_devices is 'True'"
)
api_query_result_limit = kwargs.get("api_query_result_limit")
ret = {}
@ -951,6 +1152,10 @@ def ext_pillar(minion_id, pillar, *args, **kwargs):
ret["netbox"]["site"]["prefixes"] = _get_site_prefixes(
api_url, minion_id, site_name, site_id, headers, api_query_result_limit
)
if connected_devices:
ret["netbox"]["connected_devices"] = _get_connected_devices(
api_url, minion_id, ret["netbox"]["interfaces"], headers
)
if proxy_return:
if ret["netbox"]["platform"]:
platform_id = ret["netbox"]["platform"]["id"]

View file

@ -188,8 +188,8 @@ def multiple_device_results():
{
"id": 512,
"url": "https://netbox.example.com/api/dcim/devices/512/",
"name": "minion1",
"display_name": "minion1",
"name": "minion2",
"display_name": "minion2",
"device_type": {
"id": 4,
"url": "https://netbox.example.com/api/dcim/device-types/4/",
@ -260,6 +260,82 @@ def multiple_device_results():
}
@pytest.fixture
def secondary_device_result():
return {
"dict": {
"id": 512,
"url": "https://netbox.example.com/api/dcim/devices/512/",
"name": "minion2",
"display_name": "minion2",
"device_type": {
"id": 4,
"url": "https://netbox.example.com/api/dcim/device-types/4/",
"manufacturer": {
"id": 1,
"url": "https://netbox.example.com/api/dcim/manufacturers/1/",
"name": "Cisco",
"slug": "cisco",
},
"model": "ISR2901",
"slug": "isr2901",
"display_name": "Cisco ISR2901",
},
"device_role": {
"id": 45,
"url": "https://netbox.example.com/api/dcim/device-roles/45/",
"name": "Network",
"slug": "network",
},
"node_type": "device",
"tenant": None,
"platform": {
"id": 1,
"url": "https://netbox.example.com/api/dcim/platforms/1/",
"name": "Cisco IOS",
"slug": "ios",
},
"serial": "",
"asset_tag": None,
"site": {
"id": 18,
"url": "https://netbox.example.com/api/dcim/sites/18/",
"name": "Site 1",
"slug": "site1",
},
"rack": None,
"position": None,
"face": None,
"parent_device": None,
"status": {"value": "active", "label": "Active"},
"primary_ip": {
"id": 1150,
"url": "https://netbox.example.com/api/ipam/ip-addresses/1150/",
"family": 4,
"address": "192.0.2.3/24",
},
"primary_ip4": {
"id": 1150,
"url": "https://netbox.example.com/api/ipam/ip-addresses/1150/",
"family": 4,
"address": "192.0.2.3/24",
},
"primary_ip6": None,
"cluster": None,
"virtual_chassis": None,
"vc_position": None,
"vc_priority": None,
"comments": "",
"local_context_data": None,
"tags": [],
"custom_fields": {},
"config_context": {},
"created": "2021-02-19",
"last_updated": "2021-02-19T06:12:04.171105Z",
}
}
@pytest.fixture
def virtual_machine_results():
return {
@ -490,9 +566,24 @@ def device_interface_results():
"cable": None,
"cable_peer": None,
"cable_peer_type": None,
"connected_endpoint": None,
"connected_endpoint_type": None,
"connected_endpoint_reachable": None,
"connected_endpoints": [
{
"id": 170,
"url": "https://demo.netbox.dev/api/dcim/interfaces/512/",
"display": "GigabitEthernet1/0/1",
"device": {
"id": 512,
"url": "https://demo.netbox.dev/api/dcim/devices/512/",
"display": "minion2",
"name": "minion2",
},
"name": "GigabitEthernet1/0/1",
"cable": 35,
"_occupied": True,
}
],
"connected_endpoints_type": "dcim.interface",
"connected_endpoints_reachable": True,
"tags": [],
"count_ipaddresses": 1,
},
@ -520,9 +611,9 @@ def device_interface_results():
"cable": None,
"cable_peer": None,
"cable_peer_type": None,
"connected_endpoint": None,
"connected_endpoint_type": None,
"connected_endpoint_reachable": None,
"connected_endpoints": None,
"connected_endpoints_type": None,
"connected_endpoints_reachable": None,
"tags": [],
"count_ipaddresses": 1,
},
@ -552,9 +643,24 @@ def device_interfaces_list():
"cable": None,
"cable_peer": None,
"cable_peer_type": None,
"connected_endpoint": None,
"connected_endpoint_type": None,
"connected_endpoint_reachable": None,
"connected_endpoints": [
{
"_occupied": True,
"cable": 35,
"device": {
"display": "minion2",
"id": 512,
"name": "minion2",
"url": "https://demo.netbox.dev/api/dcim/devices/512/",
},
"display": "GigabitEthernet1/0/1",
"id": 170,
"name": "GigabitEthernet1/0/1",
"url": "https://demo.netbox.dev/api/dcim/interfaces/512/",
}
],
"connected_endpoints_reachable": True,
"connected_endpoints_type": "dcim.interface",
"tags": [],
"count_ipaddresses": 1,
},
@ -576,9 +682,9 @@ def device_interfaces_list():
"cable": None,
"cable_peer": None,
"cable_peer_type": None,
"connected_endpoint": None,
"connected_endpoint_type": None,
"connected_endpoint_reachable": None,
"connected_endpoints": None,
"connected_endpoints_type": None,
"connected_endpoints_reachable": None,
"tags": [],
"count_ipaddresses": 1,
},
@ -857,9 +963,24 @@ def device_interfaces_ip_list():
"cable": None,
"cable_peer": None,
"cable_peer_type": None,
"connected_endpoint": None,
"connected_endpoint_type": None,
"connected_endpoint_reachable": None,
"connected_endpoints": [
{
"id": 170,
"url": "https://demo.netbox.dev/api/dcim/interfaces/512/",
"display": "GigabitEthernet1/0/1",
"device": {
"id": 512,
"url": "https://demo.netbox.dev/api/dcim/devices/512/",
"display": "minion2",
"name": "minion2",
},
"name": "GigabitEthernet1/0/1",
"cable": 35,
"_occupied": True,
}
],
"connected_endpoints_type": "dcim.interface",
"connected_endpoints_reachable": True,
"tags": [],
"count_ipaddresses": 1,
},
@ -901,9 +1022,9 @@ def device_interfaces_ip_list():
"cable": None,
"cable_peer": None,
"cable_peer_type": None,
"connected_endpoint": None,
"connected_endpoint_type": None,
"connected_endpoint_reachable": None,
"connected_endpoints": None,
"connected_endpoints_type": None,
"connected_endpoints_reachable": None,
"tags": [],
"count_ipaddresses": 1,
},
@ -1217,9 +1338,24 @@ def pillar_results():
"cable": None,
"cable_peer": None,
"cable_peer_type": None,
"connected_endpoint": None,
"connected_endpoint_type": None,
"connected_endpoint_reachable": None,
"connected_endpoints": [
{
"id": 170,
"url": "https://demo.netbox.dev/api/dcim/interfaces/512/",
"display": "GigabitEthernet1/0/1",
"device": {
"id": 512,
"url": "https://demo.netbox.dev/api/dcim/devices/512/",
"display": "minion2",
"name": "minion2",
},
"name": "GigabitEthernet1/0/1",
"cable": 35,
"_occupied": True,
}
],
"connected_endpoints_type": "dcim.interface",
"connected_endpoints_reachable": True,
"tags": [],
"count_ipaddresses": 1,
},
@ -1261,9 +1397,9 @@ def pillar_results():
"cable": None,
"cable_peer": None,
"cable_peer_type": None,
"connected_endpoint": None,
"connected_endpoint_type": None,
"connected_endpoint_reachable": None,
"connected_endpoints": None,
"connected_endpoints_type": None,
"connected_endpoints_reachable": None,
"tags": [],
"count_ipaddresses": 1,
},
@ -1371,6 +1507,78 @@ def pillar_results():
"tags": [],
"custom_fields": {},
"config_context": {},
"connected_devices": {
512: {
"asset_tag": None,
"cluster": None,
"comments": "",
"config_context": {},
"created": "2021-02-19",
"custom_fields": {},
"device_role": {
"id": 45,
"name": "Network",
"slug": "network",
"url": "https://netbox.example.com/api/dcim/device-roles/45/",
},
"device_type": {
"display_name": "Cisco " "ISR2901",
"id": 4,
"manufacturer": {
"id": 1,
"name": "Cisco",
"slug": "cisco",
"url": "https://netbox.example.com/api/dcim/manufacturers/1/",
},
"model": "ISR2901",
"slug": "isr2901",
"url": "https://netbox.example.com/api/dcim/device-types/4/",
},
"display_name": "minion2",
"face": None,
"id": 512,
"last_updated": "2021-02-19T06:12:04.171105Z",
"local_context_data": None,
"name": "minion2",
"node_type": "device",
"parent_device": None,
"platform": {
"id": 1,
"name": "Cisco IOS",
"slug": "ios",
"url": "https://netbox.example.com/api/dcim/platforms/1/",
},
"position": None,
"primary_ip": {
"address": "192.0.2.3/24",
"family": 4,
"id": 1150,
"url": "https://netbox.example.com/api/ipam/ip-addresses/1150/",
},
"primary_ip4": {
"address": "192.0.2.3/24",
"family": 4,
"id": 1150,
"url": "https://netbox.example.com/api/ipam/ip-addresses/1150/",
},
"primary_ip6": None,
"rack": None,
"serial": "",
"site": {
"id": 18,
"name": "Site 1",
"slug": "site1",
"url": "https://netbox.example.com/api/dcim/sites/18/",
},
"status": {"label": "Active", "value": "active"},
"tags": [],
"tenant": None,
"url": "https://netbox.example.com/api/dcim/devices/512/",
"vc_position": None,
"vc_priority": None,
"virtual_chassis": None,
}
},
"created": "2021-02-19",
"last_updated": "2021-02-19T06:12:04.171105Z",
},
@ -1378,6 +1586,82 @@ def pillar_results():
}
@pytest.fixture
def connected_devices_results():
return {
512: {
"id": 512,
"url": "https://netbox.example.com/api/dcim/devices/512/",
"name": "minion2",
"display_name": "minion2",
"device_type": {
"id": 4,
"url": "https://netbox.example.com/api/dcim/device-types/4/",
"manufacturer": {
"id": 1,
"url": "https://netbox.example.com/api/dcim/manufacturers/1/",
"name": "Cisco",
"slug": "cisco",
},
"model": "ISR2901",
"slug": "isr2901",
"display_name": "Cisco ISR2901",
},
"device_role": {
"id": 45,
"url": "https://netbox.example.com/api/dcim/device-roles/45/",
"name": "Network",
"slug": "network",
},
"node_type": "device",
"tenant": None,
"platform": {
"id": 1,
"url": "https://netbox.example.com/api/dcim/platforms/1/",
"name": "Cisco IOS",
"slug": "ios",
},
"serial": "",
"asset_tag": None,
"site": {
"id": 18,
"url": "https://netbox.example.com/api/dcim/sites/18/",
"name": "Site 1",
"slug": "site1",
},
"rack": None,
"position": None,
"face": None,
"parent_device": None,
"status": {"value": "active", "label": "Active"},
"primary_ip": {
"id": 1150,
"url": "https://netbox.example.com/api/ipam/ip-addresses/1150/",
"family": 4,
"address": "192.0.2.3/24",
},
"primary_ip4": {
"id": 1150,
"url": "https://netbox.example.com/api/ipam/ip-addresses/1150/",
"family": 4,
"address": "192.0.2.3/24",
},
"primary_ip6": None,
"cluster": None,
"virtual_chassis": None,
"vc_position": None,
"vc_priority": None,
"comments": "",
"local_context_data": None,
"tags": [],
"custom_fields": {},
"config_context": {},
"created": "2021-02-19",
"last_updated": "2021-02-19T06:12:04.171105Z",
}
}
def test_when_minion_id_is_star_then_result_should_be_empty_dict(default_kwargs):
expected_result = {}
default_kwargs["minion_id"] = "*"
@ -1636,6 +1920,29 @@ def test_when_we_retrieve_device_interface_ips_then_return_list(
assert actual_result == expected_result
def test_connected_endpoints(
default_kwargs,
headers,
connected_devices_results,
device_interfaces_list,
secondary_device_result,
):
expected_result = connected_devices_results
with patch("salt.utils.http.query", autospec=True) as query:
query.return_value = secondary_device_result
actual_result = netbox._get_connected_devices(
default_kwargs["api_url"],
default_kwargs["minion_id"],
device_interfaces_list,
headers,
)
assert actual_result == expected_result
def test_when_we_retrieve_device_interface_ips_and_get_http_error_then_return_empty_list(
default_kwargs, headers, http_error
):
@ -1968,6 +2275,7 @@ def test_when_we_retrieve_everything_successfully_then_return_dict(
site_prefixes,
proxy_details,
pillar_results,
connected_devices_results,
):
expected_result = pillar_results
@ -1978,6 +2286,7 @@ def test_when_we_retrieve_everything_successfully_then_return_dict(
default_kwargs["site_details"] = True
default_kwargs["site_prefixes"] = True
default_kwargs["proxy_return"] = True
default_kwargs["connected_devices"] = True
with patch("salt.pillar.netbox._get_devices", autospec=True) as get_devices, patch(
"salt.pillar.netbox._get_virtual_machines", autospec=True
@ -1991,7 +2300,9 @@ def test_when_we_retrieve_everything_successfully_then_return_dict(
"salt.pillar.netbox._get_site_prefixes", autospec=True
) as get_site_prefixes, patch(
"salt.pillar.netbox._get_proxy_details", autospec=True
) as get_proxy_details:
) as get_proxy_details, patch(
"salt.pillar.netbox._get_connected_devices", autospec=True
) as get_connected_decvices:
get_devices.return_value = device_results["dict"]["results"]
get_virtual_machines.return_value = no_results["dict"]["results"]
@ -2000,6 +2311,7 @@ def test_when_we_retrieve_everything_successfully_then_return_dict(
get_site_details.return_value = site_results["dict"]
get_site_prefixes.return_value = site_prefixes
get_proxy_details.return_value = proxy_details
get_connected_decvices.return_value = connected_devices_results
actual_result = netbox.ext_pillar(**default_kwargs)