Skip to content

Commit 808aa24

Browse files
authored
Panos error handling (#749)
* Handle errors in panos node Signed-off-by: Claudio Usai <[email protected]> * Node: fix call of not-implemented func in case of failed discovery Signed-off-by: Claudio Usai <[email protected]>
1 parent 38400fd commit 808aa24

File tree

2 files changed

+57
-24
lines changed

2 files changed

+57
-24
lines changed

suzieq/poller/worker/nodes/node.py

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,9 @@ async def _init_ssh(self, init_dev_data=True, use_lock=True) -> None:
693693
self.ssh_ready.release()
694694

695695
# Release here because init_dev_data uses this lock as well
696-
if init_dev_data:
696+
# We need to be sure that devtype is set, otherwise the
697+
# _fetch_dev_data function is not implemented and will raise.
698+
if init_dev_data and self.devtype:
697699
await self._fetch_init_dev_data()
698700

699701
@abstractmethod
@@ -1904,13 +1906,11 @@ async def _fetch_init_dev_data(self):
19041906
password=self.password, known_hosts=None) as conn:
19051907
async with conn.create_process() as process:
19061908
process.stdin.write("show system info\n")
1907-
recv_chars = False
19081909
output = ""
1909-
if not recv_chars:
1910-
output += await process.stdout.read(1)
1910+
output += await process.stdout.read(1)
19111911
try:
19121912
await asyncio.wait_for(
1913-
process.wait_closed(), timeout=0.01)
1913+
process.wait_closed(), timeout=0.1)
19141914
except asyncio.TimeoutError:
19151915
pass
19161916

@@ -1927,22 +1927,38 @@ async def _fetch_init_dev_data(self):
19271927
)
19281928
if self.api_key is None:
19291929
await self.get_api_key()
1930+
except asyncssh.misc.PermissionDenied:
1931+
self.logger.error(
1932+
f'Permission denied for {self.address}:{self.port}')
1933+
self._retry -= 1
19301934
except Exception:
1931-
pass
1935+
self.logger.warning(
1936+
f'Cannot parse device data from {self.address}:{self.port}')
19321937

19331938
async def get_api_key(self):
19341939
"""Authenticate to get the api key needed in all cmd requests"""
19351940
url = f"https://{self.address}:{self.port}/api/?type=keygen&user=" \
19361941
f"{self.username}&password={self.password}"
19371942

19381943
async with self.cmd_pacer(self.per_cmd_auth):
1944+
if not self._retry:
1945+
return
19391946
async with self._session.get(url, timeout=self.connect_timeout) \
19401947
as response:
19411948
status, xml = response.status, await response.text()
19421949
if status == 200:
19431950
data = xmltodict.parse(xml)
19441951
self.api_key = data["response"]["result"]["key"]
1945-
# need to manage errors
1952+
# reset retry count, just in case.
1953+
self._retry = self._max_retries_on_auth_fail
1954+
elif status == 403:
1955+
self.logger.error('Invalid credentials, could not get api '
1956+
f'key for {self.address}:{self.port}.')
1957+
self._retry -= 1
1958+
else:
1959+
self.logger.error('Unknown error, could not get '
1960+
'api key for '
1961+
f'{self.address}:{self.port}.')
19461962

19471963
async def _parse_init_dev_data(self, output, cb_token) -> None:
19481964
"""Parse the uptime command output"""
@@ -1995,6 +2011,12 @@ async def _init_rest(self):
19952011
)
19962012
if self.api_key is None:
19972013
await self.get_api_key()
2014+
# If the api_key is still None we can't gather any data.
2015+
# Ensure that the connection pool is closed and set it to
2016+
# None so that _rest_gather can fail gracefully.
2017+
if self.api_key is None:
2018+
self._session.close()
2019+
self._session = None
19982020
except Exception as e:
19992021
self.logger.error(
20002022
f'{self.transport}://{self.hostname}:{self.port}, '
@@ -2011,12 +2033,15 @@ async def _rest_gather(self, service_callback, cmd_list, cb_token,
20112033

20122034
now = int(datetime.now(tz=timezone.utc).timestamp() * 1000)
20132035

2014-
port = self.port or 443
2015-
2016-
url = f"https://{self.address}:{port}/api/"
2036+
url = f"https://{self.address}:{self.port}/api/"
20172037

20182038
status = 200 # status OK
20192039

2040+
# if there's no session we have failed to get init dev data
2041+
if not self._session and self._retry:
2042+
self._fetch_init_dev_data()
2043+
2044+
# if there's still no session, we need to create an error
20202045
if not self._session:
20212046
for cmd in cmd_list:
20222047
result.append(self._create_error(cmd))
@@ -2030,19 +2055,26 @@ async def _rest_gather(self, service_callback, cmd_list, cb_token,
20302055
async with self._session.get(
20312056
url_cmd, timeout=timeout) as response:
20322057
status, xml = response.status, await response.text()
2033-
json_out = json.dumps(
2034-
xmltodict.parse(xml))
2035-
2036-
result.append({
2037-
"status": status,
2038-
"timestamp": now,
2039-
"cmd": cmd,
2040-
"devtype": self.devtype,
2041-
"namespace": self.nsname,
2042-
"hostname": self.hostname,
2043-
"address": self.address,
2044-
"data": json_out,
2045-
})
2058+
if status == 200:
2059+
json_out = json.dumps(
2060+
xmltodict.parse(xml))
2061+
self.logger.info(f"{cmd} {status}")
2062+
result.append({
2063+
"status": status,
2064+
"timestamp": now,
2065+
"cmd": cmd,
2066+
"devtype": self.devtype,
2067+
"namespace": self.nsname,
2068+
"hostname": self.hostname,
2069+
"address": self.address,
2070+
"data": json_out,
2071+
})
2072+
else:
2073+
result.append(self._create_error(cmd))
2074+
self.logger.error(
2075+
f'{self.transport}://{self.hostname}:'
2076+
f'{self.port}: Command {cmd} failed with '
2077+
f'status {response.status}')
20462078
except Exception as e:
20472079
self.current_exception = e
20482080
for cmd in cmd_list:

suzieq/poller/worker/services/interfaces.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1051,5 +1051,6 @@ def _clean_panos_data(self, processed_data, _):
10511051
entry["type"] = "subinterface"
10521052

10531053
# remove mtu data cmd
1054-
processed_data.pop(0)
1054+
if len(processed_data) > 1:
1055+
processed_data.pop(0)
10551056
return processed_data

0 commit comments

Comments
 (0)