umnetdb-utils 0.1.4__py3-none-any.whl → 0.1.5__py3-none-any.whl
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.
- umnetdb_utils/__init__.py +1 -1
- umnetdb_utils/base.py +28 -17
- umnetdb_utils/umnetdb.py +31 -27
- umnetdb_utils/umnetdisco.py +84 -64
- umnetdb_utils/umnetequip.py +90 -83
- umnetdb_utils/umnetinfo.py +3 -6
- umnetdb_utils/utils.py +9 -6
- {umnetdb_utils-0.1.4.dist-info → umnetdb_utils-0.1.5.dist-info}/METADATA +1 -1
- umnetdb_utils-0.1.5.dist-info/RECORD +10 -0
- umnetdb_utils-0.1.4.dist-info/RECORD +0 -10
- {umnetdb_utils-0.1.4.dist-info → umnetdb_utils-0.1.5.dist-info}/WHEEL +0 -0
umnetdb_utils/__init__.py
CHANGED
umnetdb_utils/base.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
from typing import Union
|
3
2
|
from os import getenv
|
4
3
|
import re
|
@@ -10,14 +9,16 @@ from sqlalchemy.orm import Session
|
|
10
9
|
|
11
10
|
logger = logging.getLogger(__name__)
|
12
11
|
|
12
|
+
|
13
13
|
class UMnetdbBase:
|
14
14
|
"""
|
15
15
|
Base helper class
|
16
16
|
"""
|
17
|
+
|
17
18
|
# set in child classes - you can use environment variables within curly braces here
|
18
19
|
URL = None
|
19
20
|
|
20
|
-
def __init__(self, env_file:str=".env"):
|
21
|
+
def __init__(self, env_file: str = ".env"):
|
21
22
|
"""
|
22
23
|
Initiate a umnetdb object. Optionally provide a path to a file with environment variables
|
23
24
|
containing the credentials for the database. If no file is provided and there's no ".env",
|
@@ -73,14 +74,24 @@ class UMnetdbBase:
|
|
73
74
|
def __exit__(self, fexc_type, exc_val, exc_tb):
|
74
75
|
self.close()
|
75
76
|
|
76
|
-
def __getattr__(self, val:str):
|
77
|
+
def __getattr__(self, val: str):
|
77
78
|
if self.session:
|
78
79
|
return getattr(self.session, val)
|
79
80
|
|
80
81
|
raise AttributeError(self)
|
81
82
|
|
82
|
-
def _build_select(
|
83
|
-
|
83
|
+
def _build_select(
|
84
|
+
self,
|
85
|
+
select,
|
86
|
+
table,
|
87
|
+
joins=None,
|
88
|
+
where=None,
|
89
|
+
order_by=None,
|
90
|
+
limit=None,
|
91
|
+
group_by=None,
|
92
|
+
distinct=False,
|
93
|
+
) -> str:
|
94
|
+
"""
|
84
95
|
Generic 'select' query string builder built from standard query components as input.
|
85
96
|
The user is required to generate substrings for the more complex inputs
|
86
97
|
(eg joins, where statements), this function just puts all the components
|
@@ -93,7 +104,7 @@ class UMnetdbBase:
|
|
93
104
|
ex: "node_ip nip"
|
94
105
|
:joins: a list of strings representing join statements. Include the actual 'join' part!
|
95
106
|
ex: ["join node n on nip.mac = n.mac", "join device d on d.ip = n.switch"]
|
96
|
-
:where: For a single where statement, provide a string. For multiple provide a list.
|
107
|
+
:where: For a single where statement, provide a string. For multiple provide a list.
|
97
108
|
The list of statements are "anded". If you need "or", embed it in one of your list items
|
98
109
|
DO NOT provide the keyword 'where' - it is auto-added.
|
99
110
|
ex: ["node_ip.ip = '1.2.3.4'", "node.switch = '10.233.0.5'"]
|
@@ -101,10 +112,10 @@ class UMnetdbBase:
|
|
101
112
|
:group_by: A string representing a column name (or names) to group by
|
102
113
|
:limit: An integer
|
103
114
|
|
104
|
-
|
115
|
+
"""
|
105
116
|
|
106
117
|
# First part of the sql statement is the 'select'
|
107
|
-
distinct =
|
118
|
+
distinct = "distinct " if distinct else ""
|
108
119
|
sql = f"select {distinct}" + ", ".join(select) + "\n"
|
109
120
|
|
110
121
|
# Next is the table
|
@@ -118,7 +129,6 @@ class UMnetdbBase:
|
|
118
129
|
|
119
130
|
# Next are the filters. They are 'anded'
|
120
131
|
if where and isinstance(where, list):
|
121
|
-
|
122
132
|
sql += "where\n"
|
123
133
|
sql += " and\n".join(where) + "\n"
|
124
134
|
elif where:
|
@@ -133,20 +143,20 @@ class UMnetdbBase:
|
|
133
143
|
|
134
144
|
if limit:
|
135
145
|
sql += f"limit {limit}\n"
|
136
|
-
|
146
|
+
|
137
147
|
logger.debug(f"Generated SQL command:\n****\n{sql}\n****\n")
|
138
148
|
|
139
149
|
return sql
|
140
150
|
|
141
|
-
def _execute(self, sql:str, rows_as_dict:bool=True, fetch_one:bool=False):
|
142
|
-
|
151
|
+
def _execute(self, sql: str, rows_as_dict: bool = True, fetch_one: bool = False):
|
152
|
+
"""
|
143
153
|
Generic sqlalchemy "open a session, execute this sql command and give me all the results"
|
144
154
|
|
145
155
|
NB This function is defined for legacy database classes that came from umnet-scripts.
|
146
156
|
It's encouraged to use "self.session.execute" in other child methods, allowing
|
147
157
|
scripts that import the child class to use the context manager and execute multiple
|
148
158
|
mehtods within the same session.
|
149
|
-
|
159
|
+
"""
|
150
160
|
with self.engine.begin() as c:
|
151
161
|
r = c.execute(text(sql))
|
152
162
|
|
@@ -158,8 +168,9 @@ class UMnetdbBase:
|
|
158
168
|
else:
|
159
169
|
return []
|
160
170
|
|
161
|
-
|
162
|
-
|
171
|
+
def execute(
|
172
|
+
self, sql: str, rows_as_dict: bool = True, fetch_one: bool = False
|
173
|
+
) -> Union[list[dict], dict]:
|
163
174
|
"""
|
164
175
|
Executes a sqlalchemy command and gives all the results as a list of dicts, or as a dict
|
165
176
|
if 'fetch_one' is set to true.
|
@@ -172,5 +183,5 @@ class UMnetdbBase:
|
|
172
183
|
|
173
184
|
if fetch_one:
|
174
185
|
return dict(result.fetchone())
|
175
|
-
|
176
|
-
return [dict(r) for r in result.fetchall()]
|
186
|
+
|
187
|
+
return [dict(r) for r in result.fetchall()]
|
umnetdb_utils/umnetdb.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
from typing import List
|
3
2
|
import logging
|
4
3
|
import re
|
@@ -9,12 +8,9 @@ from .base import UMnetdbBase
|
|
9
8
|
|
10
9
|
|
11
10
|
class UMnetdb(UMnetdbBase):
|
11
|
+
URL = "postgresql+psycopg://{UMNETDB_USER}:{UMNETDB_PASSWORD}@wintermute.umnet.umich.edu/umnetdb"
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
def get_neighbors(
|
16
|
-
self, device: str, known_devices_only: bool = True
|
17
|
-
) -> List[dict]:
|
13
|
+
def get_neighbors(self, device: str, known_devices_only: bool = True) -> List[dict]:
|
18
14
|
"""
|
19
15
|
Gets a list of the neighbors of a particular device. If the port
|
20
16
|
has a parent in the LAG table that is included as well.
|
@@ -35,12 +31,12 @@ class UMnetdb(UMnetdbBase):
|
|
35
31
|
"n_d.name as remote_device",
|
36
32
|
"n.remote_port",
|
37
33
|
"l.parent",
|
38
|
-
"n_l.parent as remote_parent"
|
39
|
-
|
34
|
+
"n_l.parent as remote_parent",
|
35
|
+
]
|
40
36
|
joins = [
|
41
|
-
|
42
|
-
|
43
|
-
|
37
|
+
"join device n_d on n_d.hostname=n.remote_device",
|
38
|
+
"left outer join lag l on l.device=n.device and l.member=n.port",
|
39
|
+
"left outer join lag n_l on n_l.device=n_d.name and n_l.member=n.remote_port",
|
44
40
|
]
|
45
41
|
else:
|
46
42
|
select = [
|
@@ -48,7 +44,7 @@ class UMnetdb(UMnetdbBase):
|
|
48
44
|
"coalesce(n_d.name, n.remote_device) as remote_device",
|
49
45
|
"n.remote_port",
|
50
46
|
"l.parent",
|
51
|
-
"n_l.parent as remote_parent"
|
47
|
+
"n_l.parent as remote_parent",
|
52
48
|
]
|
53
49
|
joins = [
|
54
50
|
"left outer join device n_d on n_d.hostname=n.remote_device",
|
@@ -64,9 +60,7 @@ class UMnetdb(UMnetdbBase):
|
|
64
60
|
|
65
61
|
return self.execute(query)
|
66
62
|
|
67
|
-
|
68
|
-
|
69
|
-
def get_dlzone(self, zone_name:str) -> List[dict]:
|
63
|
+
def get_dlzone(self, zone_name: str) -> List[dict]:
|
70
64
|
"""
|
71
65
|
Gets all devices within a DL zone based on walking the 'neighbors'
|
72
66
|
table.
|
@@ -77,42 +71,52 @@ class UMnetdb(UMnetdbBase):
|
|
77
71
|
device_cols = ["name", "ip", "version", "vendor", "model", "serial"]
|
78
72
|
|
79
73
|
# step 1 is to find DLs in the database - we'll seed our zone with them
|
80
|
-
query = self._build_select(
|
74
|
+
query = self._build_select(
|
75
|
+
select=device_cols,
|
76
|
+
table="device",
|
77
|
+
where=f"name similar to '(d-|dl-){zone_name}-(1|2)'",
|
78
|
+
)
|
81
79
|
dls = self.execute(query)
|
82
80
|
|
83
81
|
if not dls:
|
84
82
|
raise ValueError(f"No DLs found in umnetdb for zone {zone_name}")
|
85
83
|
|
86
|
-
devices_by_name = {d[
|
84
|
+
devices_by_name = {d["name"]: d for d in dls}
|
87
85
|
|
88
86
|
# now we'll look for neighbors on each device within the zone.
|
89
87
|
# Note that outside of the DLs we only expect to find devices that start with
|
90
88
|
# "s-" anything else is considered 'outside the zone'
|
91
89
|
todo = list(devices_by_name.keys())
|
92
|
-
while
|
93
|
-
|
90
|
+
while len(todo) != 0:
|
94
91
|
device = todo.pop()
|
95
92
|
|
93
|
+
print(f"Processing device {device}")
|
96
94
|
# note that by default this method only returns neighbors in the 'device' table,
|
97
95
|
# any others are ignored
|
98
96
|
neighs = self.get_neighbors(device)
|
99
97
|
devices_by_name[device]["neighbors"] = {}
|
100
98
|
for neigh in neighs:
|
101
|
-
|
102
|
-
# only want 'd- or 'dl-' or 's-' devices
|
103
|
-
if re.match(r"(dl
|
104
|
-
|
99
|
+
print(f"EVALUATING {neigh}")
|
100
|
+
# only want 'd- or 'dl-' or 's-' devices, and we don't want out of band devices
|
101
|
+
if re.match(r"(dl?-|s-)", neigh["remote_device"]) and not re.match(
|
102
|
+
r"s-oob-", neigh["remote_device"]
|
103
|
+
):
|
105
104
|
# adding neighbor to local device's neighbor list
|
106
|
-
devices_by_name[device]["neighbors"][neigh["port"]] = {
|
105
|
+
devices_by_name[device]["neighbors"][neigh["port"]] = {
|
106
|
+
k: v for k, v in neigh.items() if k != "port"
|
107
|
+
}
|
107
108
|
|
108
109
|
# if we haven't seen this neighbor yet, pull data from our device table for it, and
|
109
110
|
# add it to our 'to do' list to pull its neighbors.
|
110
111
|
if neigh["remote_device"] not in devices_by_name:
|
111
|
-
|
112
|
-
|
112
|
+
query = self._build_select(
|
113
|
+
select=device_cols,
|
114
|
+
table="device",
|
115
|
+
where=f"name = '{neigh['remote_device']}'",
|
116
|
+
)
|
113
117
|
neigh_device = self.execute(query, fetch_one=True)
|
114
118
|
devices_by_name[neigh_device["name"]] = neigh_device
|
115
|
-
|
119
|
+
|
116
120
|
todo.append(neigh_device["name"])
|
117
121
|
|
118
122
|
return list(devices_by_name.values())
|
umnetdb_utils/umnetdisco.py
CHANGED
@@ -1,24 +1,31 @@
|
|
1
|
-
|
2
|
-
|
3
1
|
from .base import UMnetdbBase
|
4
2
|
from .utils import is_ip_address, is_mac_address
|
5
3
|
|
6
4
|
|
7
5
|
class Umnetdisco(UMnetdbBase):
|
8
|
-
URL="postgresql+psycopg://{NETDISCO_DB_USER}:{NETDISCO_DB_PASSWORD}@netdisco.umnet.umich.edu:5432/netdisco"
|
6
|
+
URL = "postgresql+psycopg://{NETDISCO_DB_USER}:{NETDISCO_DB_PASSWORD}@netdisco.umnet.umich.edu:5432/netdisco"
|
9
7
|
|
10
|
-
def host_arp(
|
11
|
-
|
12
|
-
|
8
|
+
def host_arp(
|
9
|
+
self, host, start_time=None, end_time=None, limit=1, active_only=False
|
10
|
+
):
|
11
|
+
"""
|
12
|
+
Does an ARP query against the netdisco db for a single host.
|
13
13
|
:host: A string representing a MAC address or an IPv4 address
|
14
|
-
|
14
|
+
"""
|
15
15
|
|
16
16
|
# what table are we querying?
|
17
|
-
table =
|
18
|
-
joins = [
|
17
|
+
table = "node_ip nip"
|
18
|
+
joins = ["join node n on nip.mac = n.mac"]
|
19
19
|
|
20
20
|
# define select statements
|
21
|
-
select = [
|
21
|
+
select = [
|
22
|
+
"nip.mac",
|
23
|
+
"nip.ip",
|
24
|
+
"n.switch",
|
25
|
+
"n.port",
|
26
|
+
"nip.time_first",
|
27
|
+
"nip.time_last",
|
28
|
+
]
|
22
29
|
|
23
30
|
# First determine if this host is a MAC address or an IP
|
24
31
|
where = []
|
@@ -31,7 +38,9 @@ class Umnetdisco(UMnetdbBase):
|
|
31
38
|
|
32
39
|
# filter for specific start/end times if specified
|
33
40
|
if start_time and end_time:
|
34
|
-
where.append(
|
41
|
+
where.append(
|
42
|
+
f"n.time_last between timestamp '{start_time}' and timestamp '{end_time}'"
|
43
|
+
)
|
35
44
|
elif start_time:
|
36
45
|
where.append(f"n.time_last > timestamp '{start_time}'")
|
37
46
|
|
@@ -40,63 +49,73 @@ class Umnetdisco(UMnetdbBase):
|
|
40
49
|
where.append("nip.active = true")
|
41
50
|
|
42
51
|
# order by last seen
|
43
|
-
sql = self._build_select(
|
52
|
+
sql = self._build_select(
|
53
|
+
select, table, joins=joins, where=where, order_by="time_last", limit=limit
|
54
|
+
)
|
44
55
|
result = self._execute(sql)
|
45
56
|
|
46
57
|
return result
|
47
58
|
|
48
59
|
def arp_count(self, prefix=None, start_time=None, end_time=None, active_only=False):
|
49
|
-
|
60
|
+
"""
|
50
61
|
Queries the host data in netdisco based on prefix and start/end time.
|
51
62
|
First any prefix greater than or equal to the inputted prefix is searched for
|
52
63
|
(in the 'device_ip' table).
|
53
64
|
|
54
65
|
Then the host table is queried.
|
55
|
-
|
56
|
-
|
66
|
+
"""
|
67
|
+
|
57
68
|
# We're counting all the host IPs by subnet
|
58
|
-
select = [
|
59
|
-
table =
|
69
|
+
select = ["count(distinct nip.ip)", "dip.subnet"]
|
70
|
+
table = "node_ip nip"
|
60
71
|
|
61
72
|
# postgres allows us to do a join based on an IPs (type inet) membership
|
62
73
|
# of a subnet (type cidr)
|
63
|
-
joins = [
|
74
|
+
joins = ["join device_ip dip on nip.ip <<= dip.subnet"]
|
64
75
|
|
65
76
|
# grouping by subnet is what gives us per-subnet host counts
|
66
|
-
group_by =
|
77
|
+
group_by = "dip.subnet"
|
67
78
|
|
68
79
|
# append all cli filtering options
|
69
80
|
where = [f"dip.subnet <<= inet '{prefix}'"]
|
70
81
|
if start_time and end_time:
|
71
|
-
where.append(
|
82
|
+
where.append(
|
83
|
+
f"nip.time_last between timestamp '{start_time}' and timestamp '{end_time}'"
|
84
|
+
)
|
72
85
|
elif start_time:
|
73
86
|
where.append(f"nip.time_last > timestamp '{start_time}'")
|
74
87
|
|
75
88
|
if active_only:
|
76
89
|
where.append("nip.active = true")
|
77
90
|
|
78
|
-
sql = self._build_select(
|
91
|
+
sql = self._build_select(
|
92
|
+
select, table, joins=joins, where=where, group_by=group_by
|
93
|
+
)
|
79
94
|
return self._execute(sql)
|
80
95
|
|
81
96
|
def neighbors(self, device=None):
|
82
|
-
|
97
|
+
"""
|
83
98
|
Queries the device_ip table in netdisco to get neighbors of a device.
|
84
99
|
If no device is specified, all neighbors are pulled.
|
85
100
|
The device input can be an IPv4 address or an FQDN.
|
86
|
-
|
87
|
-
|
88
|
-
select = [
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
101
|
+
"""
|
102
|
+
|
103
|
+
select = [
|
104
|
+
"dp.ip as local_ip",
|
105
|
+
"ld.dns as local_dns",
|
106
|
+
"dp.port as local_port",
|
107
|
+
"dp.remote_ip",
|
108
|
+
"rd.dns as remote_dns",
|
109
|
+
"dp.remote_port",
|
110
|
+
]
|
111
|
+
table = "device d"
|
112
|
+
joins = [
|
113
|
+
"join device_port dp on d.ip = dp.ip",
|
114
|
+
"join device ld on dp.ip = ld.ip",
|
115
|
+
"join device rd on dp.remote_ip = rd.ip",
|
116
|
+
]
|
117
|
+
|
118
|
+
where = ["dp.remote_ip is not null"]
|
100
119
|
if is_ip_address(device):
|
101
120
|
where.append(f"d.ip = '{device}'")
|
102
121
|
elif device:
|
@@ -106,37 +125,39 @@ class Umnetdisco(UMnetdbBase):
|
|
106
125
|
return self._execute(sql)
|
107
126
|
|
108
127
|
def get_devices(self, match_subnets=None, exclude_subnets=None):
|
109
|
-
|
128
|
+
"""
|
110
129
|
Queries netdisco for a list of devices.
|
111
130
|
Optionally, limit it by a list of prefixes
|
112
|
-
|
131
|
+
"""
|
113
132
|
|
114
|
-
select = [
|
115
|
-
table =
|
133
|
+
select = ["ip", "dns", "serial", "model", "vendor", "os"]
|
134
|
+
table = "device"
|
116
135
|
|
117
136
|
where = []
|
118
137
|
if match_subnets:
|
119
138
|
where.append(" or\n".join([f"ip << inet '{s}'" for s in match_subnets]))
|
120
|
-
|
139
|
+
|
121
140
|
if exclude_subnets:
|
122
|
-
where.append(
|
141
|
+
where.append(
|
142
|
+
"not\n" + " or\n".join([f"ip << inet '{s}'" for s in exclude_subnets])
|
143
|
+
)
|
123
144
|
|
124
145
|
sql = self._build_select(select, table, where=where)
|
125
146
|
return self._execute(sql)
|
126
147
|
|
127
148
|
def get_device(self, name_or_ip):
|
128
|
-
|
149
|
+
"""
|
129
150
|
Pulls the device name, ip, model, and description (where version info often is)
|
130
151
|
from the netinfo device table
|
131
|
-
|
152
|
+
"""
|
132
153
|
select = [
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
154
|
+
"replace(lower(name), '.umnet.umich.edu', '') as name",
|
155
|
+
"device.ip as ip",
|
156
|
+
"model",
|
157
|
+
"description",
|
158
|
+
]
|
138
159
|
table = "device"
|
139
|
-
|
160
|
+
|
140
161
|
if is_ip_address(name_or_ip):
|
141
162
|
joins = ["join device_ip on device.ip=device_ip.alias"]
|
142
163
|
where = [f"device_ip.alias='{name_or_ip}'"]
|
@@ -153,7 +174,7 @@ class Umnetdisco(UMnetdbBase):
|
|
153
174
|
return None
|
154
175
|
|
155
176
|
def get_port_and_host_data(self, name_or_ip):
|
156
|
-
|
177
|
+
"""
|
157
178
|
Pulls a list of all ports on the switch and all hosts seen on those ports.
|
158
179
|
You will see multiple rows for an interface where multiple hosts have been seen.
|
159
180
|
|
@@ -168,21 +189,21 @@ class Umnetdisco(UMnetdbBase):
|
|
168
189
|
- vlan the host was seen on
|
169
190
|
- date last mac was seen
|
170
191
|
|
171
|
-
|
192
|
+
"""
|
172
193
|
|
173
194
|
# getting device by name or IP
|
174
195
|
if is_ip_address(name_or_ip):
|
175
196
|
device_ip = name_or_ip
|
176
197
|
else:
|
177
198
|
nd_device = self.get_device(name_or_ip)
|
178
|
-
if not(nd_device):
|
179
|
-
raise LookupError(f
|
180
|
-
device_ip = nd_device[
|
199
|
+
if not (nd_device):
|
200
|
+
raise LookupError(f"Could not find {nd_device} in Netdisco")
|
201
|
+
device_ip = nd_device["ip"]
|
181
202
|
|
182
203
|
# The port description is in different fields depending on the platform, so we need to do
|
183
204
|
# some if-else stuff that's not supported in our basic sql builer in the base class.
|
184
205
|
# That's why we're just doing a long sting here
|
185
|
-
sql = f
|
206
|
+
sql = f"""
|
186
207
|
select
|
187
208
|
dp.port,
|
188
209
|
case
|
@@ -202,7 +223,7 @@ from device_port dp
|
|
202
223
|
left outer join node on dp.port = node.port and dp.ip = node.switch
|
203
224
|
left outer join node_ip on node.mac = node_ip.mac
|
204
225
|
where dp.ip='{device_ip}'
|
205
|
-
|
226
|
+
"""
|
206
227
|
|
207
228
|
result = self._execute(sql)
|
208
229
|
|
@@ -210,7 +231,7 @@ where dp.ip='{device_ip}'
|
|
210
231
|
|
211
232
|
def get_poe_data(self, name_or_ip):
|
212
233
|
"""
|
213
|
-
Pulls PoE admin, status, class, and power columns from device_port_pwer
|
234
|
+
Pulls PoE admin, status, class, and power columns from device_port_pwer
|
214
235
|
"""
|
215
236
|
|
216
237
|
# getting device by name or IP
|
@@ -218,16 +239,15 @@ where dp.ip='{device_ip}'
|
|
218
239
|
device_ip = name_or_ip
|
219
240
|
else:
|
220
241
|
nd_device = self.get_device(name_or_ip)
|
221
|
-
if not(nd_device):
|
222
|
-
raise LookupError(f
|
223
|
-
device_ip = nd_device[
|
224
|
-
|
242
|
+
if not (nd_device):
|
243
|
+
raise LookupError(f"Could not find {nd_device} in Netdisco")
|
244
|
+
device_ip = nd_device["ip"]
|
245
|
+
|
225
246
|
select = ["port", "admin", "status", "class", "power"]
|
226
247
|
table = "device_port_power"
|
227
248
|
where = [f"ip='{device_ip}'"]
|
228
249
|
|
229
|
-
|
230
250
|
sql = self._build_select(select, table, where=where)
|
231
251
|
result = self._execute(sql)
|
232
252
|
|
233
|
-
return result
|
253
|
+
return result
|
umnetdb_utils/umnetequip.py
CHANGED
@@ -3,25 +3,28 @@ from typing import Union, Optional
|
|
3
3
|
from .base import UMnetdbBase
|
4
4
|
from .utils import is_ip_address
|
5
5
|
|
6
|
+
|
6
7
|
class UMnetequip(UMnetdbBase):
|
7
|
-
|
8
|
+
"""
|
8
9
|
This class wraps helpful equip db queries in python.
|
9
|
-
|
10
|
+
"""
|
11
|
+
|
10
12
|
URL = "oracle+oracledb://{EQUIP_DB_USERNAME}:{EQUIP_DB_PASSWORD}@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=equipdb-prod.umnet.umich.edu)(PORT=1521))(CONNECT_DATA=(SID=KANNADA)))"
|
11
13
|
|
12
14
|
def get_devices_by_category(self, category, active_only=False):
|
13
|
-
|
15
|
+
"""
|
14
16
|
Queries equip db for devices by category. You can also
|
15
17
|
specify if you only want active devices.
|
16
|
-
|
18
|
+
"""
|
17
19
|
|
18
|
-
select = [
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
select = [
|
21
|
+
"eq.monitored_device",
|
22
|
+
"eq.rancid",
|
23
|
+
"eq.off_line",
|
24
|
+
"eq.dns_name",
|
25
|
+
"eq.ip_address",
|
26
|
+
]
|
27
|
+
table = "ARADMIN1.UMNET_EQUIPMENT eq"
|
25
28
|
|
26
29
|
where = [f"eq.category = '{category}'"]
|
27
30
|
|
@@ -29,54 +32,55 @@ class UMnetequip(UMnetdbBase):
|
|
29
32
|
# 1: RESERVE, 2:ACTIVE, 3:RETIRED
|
30
33
|
if active_only:
|
31
34
|
where.append("eq.status = 2")
|
32
|
-
|
35
|
+
|
33
36
|
sql = self._build_select(select, table, where=where, distinct=True)
|
34
37
|
return self._execute(sql)
|
35
38
|
|
36
|
-
|
37
39
|
def get_device_type(self, ip):
|
38
|
-
|
40
|
+
"""
|
39
41
|
Queries equip db for a device by ip, returns the 'type' of the device.
|
40
42
|
By type we mean the UMNET_EQUIPMENT_TYPE: ACCESS LAYER, DISTRIBUTION LAYER, UMD, etc
|
41
|
-
|
42
|
-
select = [
|
43
|
-
table =
|
43
|
+
"""
|
44
|
+
select = ["types as type"]
|
45
|
+
table = "ARADMIN1.UMNET_EQUIPMENT"
|
44
46
|
where = [f"ip_address='{ip}'"]
|
45
47
|
|
46
48
|
sql = self._build_select(select, table, where=where)
|
47
49
|
return self._execute(sql)
|
48
50
|
|
49
|
-
def get_devices_by_building_no(
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
):
|
51
|
+
def get_devices_by_building_no(
|
52
|
+
self,
|
53
|
+
building_no: Union[int, list],
|
54
|
+
active_only: bool = False,
|
55
|
+
types: Optional[list] = None,
|
56
|
+
location_info: bool = False,
|
57
|
+
):
|
57
58
|
"""
|
58
59
|
Queries equipdb for devices by building no. You can provide a single
|
59
60
|
building number, or a list of numbers. You can specify if you want 'active only' devices
|
60
61
|
(based on the 'status' field, defalut false) or you can limit to a certain device type (default all).
|
61
62
|
You can also get the location info for each device via 'location_info' (default false)
|
62
63
|
"""
|
63
|
-
select = [
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
64
|
+
select = [
|
65
|
+
"dns_name",
|
66
|
+
"ip_address",
|
67
|
+
"model_no_ as model",
|
68
|
+
"types as device_type",
|
69
|
+
"rancid",
|
70
|
+
"off_line",
|
71
|
+
]
|
70
72
|
if location_info:
|
71
|
-
select.extend(
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
73
|
+
select.extend(
|
74
|
+
[
|
75
|
+
"bldg as building_name",
|
76
|
+
"address as building_address",
|
77
|
+
"room as room_no",
|
78
|
+
"floor as floor",
|
79
|
+
"bldg_code__ as building_no",
|
80
|
+
]
|
81
|
+
)
|
82
|
+
|
83
|
+
table = "ARADMIN1.UMNET_EQUIPMENT eq"
|
80
84
|
|
81
85
|
where = []
|
82
86
|
if isinstance(building_no, int):
|
@@ -99,59 +103,62 @@ class UMnetequip(UMnetdbBase):
|
|
99
103
|
sql = self._build_select(select, table, where=where)
|
100
104
|
return self._execute(sql)
|
101
105
|
|
102
|
-
def get_device(self, name_or_ip:str, location_info:bool=False):
|
103
|
-
|
106
|
+
def get_device(self, name_or_ip: str, location_info: bool = False):
|
104
107
|
if is_ip_address(name_or_ip):
|
105
108
|
where = [f"ip_address='{name_or_ip}'"]
|
106
109
|
else:
|
107
|
-
name_or_ip = name_or_ip.replace(".umnet.umich.edu","")
|
110
|
+
name_or_ip = name_or_ip.replace(".umnet.umich.edu", "")
|
108
111
|
where = [f"dns_name='{name_or_ip}'"]
|
109
112
|
|
110
|
-
select = [
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
113
|
+
select = [
|
114
|
+
"dns_name",
|
115
|
+
"ip_address",
|
116
|
+
"model_no_ as model",
|
117
|
+
"types as device_type",
|
118
|
+
"rancid",
|
119
|
+
"off_line",
|
120
|
+
]
|
117
121
|
if location_info:
|
118
|
-
select.extend(
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
122
|
+
select.extend(
|
123
|
+
[
|
124
|
+
"bldg as building_name",
|
125
|
+
"address as building_address",
|
126
|
+
"room as room_no",
|
127
|
+
"floor as floor",
|
128
|
+
"bldg_code__ as building_no",
|
129
|
+
]
|
130
|
+
)
|
131
|
+
table = "ARADMIN1.UMNET_EQUIPMENT eq"
|
126
132
|
|
127
133
|
sql = self._build_select(select, table, where=where)
|
128
134
|
return self._execute(sql)
|
129
135
|
|
130
136
|
def get_all_devices(self):
|
131
|
-
select = [
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
137
|
+
select = [
|
138
|
+
"bldg_code__ as bldg_code",
|
139
|
+
"bldg",
|
140
|
+
"room",
|
141
|
+
"ip_address",
|
142
|
+
"dns_name",
|
143
|
+
"category",
|
144
|
+
"types",
|
145
|
+
"manufacturer",
|
146
|
+
"serial_number",
|
147
|
+
"billing_code_ as billing_code",
|
148
|
+
"warehouse_item__ as warehouse_item",
|
149
|
+
"st.descr as status",
|
150
|
+
"sla_network",
|
151
|
+
"address",
|
152
|
+
"floor",
|
153
|
+
"model_no_ as model_no",
|
154
|
+
"customer_name",
|
155
|
+
"mat_l_item_description",
|
156
|
+
"rancid",
|
157
|
+
"off_line",
|
158
|
+
]
|
159
|
+
table = "ARADMIN1.UMNET_EQUIPMENT eq"
|
153
160
|
joins = ["join ARADMIN1.UMNET_EQUIPMENT_STATUS st on st.idnum=eq.status"]
|
154
161
|
where = ["eq.status != 3"]
|
155
162
|
|
156
|
-
sql = self._build_select(select, table,where=where, joins=joins)
|
157
|
-
return self._execute(sql)
|
163
|
+
sql = self._build_select(select, table, where=where, joins=joins)
|
164
|
+
return self._execute(sql)
|
umnetdb_utils/umnetinfo.py
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
1
|
import ipaddress
|
4
2
|
import re
|
5
3
|
from .base import UMnetdbBase
|
6
4
|
from .utils import is_ip_address, is_ip_network, is_mac_address
|
7
5
|
|
6
|
+
|
8
7
|
class UMnetinfo(UMnetdbBase):
|
9
8
|
"""
|
10
9
|
Wraps helpful netinfo db queries into python
|
11
10
|
"""
|
11
|
+
|
12
12
|
URL = "oracle+oracledb://{NETINFO_USERNAME}:{NETINFO_PASSWORD}@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=kannada.web.itd.umich.edu)(PORT=1521))(CONNECT_DATA=(SID=KANNADA)))"
|
13
13
|
|
14
14
|
def get_network_by_name(self, netname, active_only=False):
|
@@ -365,7 +365,6 @@ class UMnetinfo(UMnetdbBase):
|
|
365
365
|
|
366
366
|
# dns names in the device table are fqdn and end in a dot (eg 'dl-arbl-1.umnet.umich.edu.')
|
367
367
|
else:
|
368
|
-
|
369
368
|
name_or_ip = name_or_ip.lower()
|
370
369
|
if "." not in name_or_ip:
|
371
370
|
name_or_ip += ".umnet.umich.edu"
|
@@ -417,7 +416,6 @@ class UMnetinfo(UMnetdbBase):
|
|
417
416
|
# UMNET.ARPHIST stores MACs as strings without separators .:-, ie
|
418
417
|
# 0010.abcd.1010 => '0010abcd1010'
|
419
418
|
if is_mac_address(query):
|
420
|
-
|
421
419
|
arphist_mac = re.sub(r"[\.\:\-]", "", query)
|
422
420
|
where.append(f"arp.mac_addr = '{arphist_mac}'")
|
423
421
|
|
@@ -456,7 +454,6 @@ class UMnetinfo(UMnetdbBase):
|
|
456
454
|
# isn't throwing anyone :-/
|
457
455
|
processed_results = []
|
458
456
|
for r in results:
|
459
|
-
|
460
457
|
processed = {
|
461
458
|
"ip": ipaddress.ip_address(r["address32bit"]),
|
462
459
|
"mac": f"{r['mac_addr'][0:4]}.{r['mac_addr'][4:8]}.{r['mac_addr'][8:12]}",
|
@@ -486,4 +483,4 @@ class UMnetinfo(UMnetdbBase):
|
|
486
483
|
sql = self._build_select(select, table, where=where, order_by="1,2")
|
487
484
|
results = self._execute(sql)
|
488
485
|
|
489
|
-
return results
|
486
|
+
return results
|
umnetdb_utils/utils.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
import ipaddress
|
2
2
|
import re
|
3
|
+
|
4
|
+
|
3
5
|
def is_ip_address(input_str, version=None):
|
4
6
|
try:
|
5
7
|
ip = ipaddress.ip_address(input_str)
|
@@ -11,8 +13,8 @@ def is_ip_address(input_str, version=None):
|
|
11
13
|
|
12
14
|
return True
|
13
15
|
|
16
|
+
|
14
17
|
def is_ip_network(input_str, version=None):
|
15
|
-
|
16
18
|
# First check that this is a valid IP or network
|
17
19
|
try:
|
18
20
|
net = ipaddress.ip_network(input_str)
|
@@ -21,19 +23,20 @@ def is_ip_network(input_str, version=None):
|
|
21
23
|
|
22
24
|
if version and version != net.version:
|
23
25
|
return False
|
24
|
-
|
26
|
+
|
25
27
|
return True
|
26
28
|
|
29
|
+
|
27
30
|
def is_mac_address(input_str):
|
28
|
-
|
31
|
+
"""
|
29
32
|
Validates the input string as a mac address. Valid formats are
|
30
33
|
XX:XX:XX:XX:XX:XX, XX-XX-XX-XX-XX-XX, XXXX.XXXX.XXXX
|
31
34
|
where 'X' is a hexadecimal digit (upper or lowercase).
|
32
|
-
|
35
|
+
"""
|
33
36
|
mac = input_str.lower()
|
34
|
-
if re.match(r
|
37
|
+
if re.match(r"[0-9a-f]{2}([-:])[0-9a-f]{2}(\1[0-9a-f]{2}){4}$", mac):
|
35
38
|
return True
|
36
|
-
if re.match(r
|
39
|
+
if re.match(r"[0-9a-f]{4}\.[0-9a-f]{4}\.[0-9a-f]{4}$", mac):
|
37
40
|
return True
|
38
41
|
|
39
42
|
return False
|
@@ -0,0 +1,10 @@
|
|
1
|
+
umnetdb_utils/__init__.py,sha256=YrT7drJ2MlU287z4--8EQI3fKLQZBlVxaCacU1yoDtg,132
|
2
|
+
umnetdb_utils/base.py,sha256=LWVhrbShhuYekXZxwvhhVUkH6DHd_wnJ2QZ_zw3fKqM,6139
|
3
|
+
umnetdb_utils/umnetdb.py,sha256=jHifl5eJXzye28mjLnfn7A7zwN2Y4YuKgrFvjV5MQBI,4953
|
4
|
+
umnetdb_utils/umnetdisco.py,sha256=D9MiF_QrYznYL_ozLUmD1ASzkGDs1jqZ_XksNV7wW3o,8018
|
5
|
+
umnetdb_utils/umnetequip.py,sha256=Nnh_YCcqGpYRp2KsS8qbAGJ9AgTeYB4umeYeP9_B-6A,5440
|
6
|
+
umnetdb_utils/umnetinfo.py,sha256=vqFvLbIu7Wu650RTqzaELpbKhq6y91fgBXM5Q_elRl4,18443
|
7
|
+
umnetdb_utils/utils.py,sha256=VLxfBKq1iOQVxTuySvxV4goE_jtX4jCshjf9a5ky1vI,989
|
8
|
+
umnetdb_utils-0.1.5.dist-info/METADATA,sha256=fPpx67_VnM0Xp6JGk13mRrxOte8wPAkau0O0PtM9vW0,1555
|
9
|
+
umnetdb_utils-0.1.5.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
10
|
+
umnetdb_utils-0.1.5.dist-info/RECORD,,
|
@@ -1,10 +0,0 @@
|
|
1
|
-
umnetdb_utils/__init__.py,sha256=QJaytbr4ccKESiwaKjpf1b4b8s2cHNfCDdnCOs1tmoI,131
|
2
|
-
umnetdb_utils/base.py,sha256=aC5oKSB5Ox9QtylmHV2uvEUVUx4CRLZTgH6HpQJ6DZo,6064
|
3
|
-
umnetdb_utils/umnetdb.py,sha256=IxqpAcPZn71HVV_oL3ryvbKS7cupUsyM6rJSGk6NLaU,4554
|
4
|
-
umnetdb_utils/umnetdisco.py,sha256=Z2XwT79jKO_avd3w_z99DDEdAikrfKxYm1JYHRWqvG4,7841
|
5
|
-
umnetdb_utils/umnetequip.py,sha256=jOW5kvk0FXtdHv8PA4rYT_PWfLdMiq83Mjwmy-c1DN8,5701
|
6
|
-
umnetdb_utils/umnetinfo.py,sha256=MH1YDW4OWtHD46qfYb5Pv40vPSbL0GrMNW5gAhdlihE,18445
|
7
|
-
umnetdb_utils/utils.py,sha256=wU6QMYfofj7trX3QeqXty0btbGdhP_RUaSqA7QTflFM,991
|
8
|
-
umnetdb_utils-0.1.4.dist-info/METADATA,sha256=JI2hwHkX3B8y-GLwS0yywAiFszcqUlkiWUyga0A2KaY,1555
|
9
|
-
umnetdb_utils-0.1.4.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
10
|
-
umnetdb_utils-0.1.4.dist-info/RECORD,,
|
File without changes
|