Skip to content

Commit

Permalink
Refresh auth token when it expires (#5039)
Browse files Browse the repository at this point in the history
* Refresh auth token when it expires

* Fix style

* Add test for the new logic
  • Loading branch information
FlorianVeaux authored Nov 20, 2019
1 parent 27e0467 commit 8e8ec03
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 8 deletions.
19 changes: 15 additions & 4 deletions cisco_aci/datadog_checks/cisco_aci/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
from requests import Request, Session
from six.moves.urllib.parse import unquote

from .exceptions import APIAuthException, APIConnectionException, APIParsingException, ConfigurationException
from datadog_checks.base import ConfigurationError

from .exceptions import APIAuthException, APIConnectionException, APIParsingException


class SessionWrapper:
Expand Down Expand Up @@ -78,9 +80,11 @@ def make_request(self, path):
prepped_request.headers['Cookie'] = cookie
else:
self.log.warning("The Cisco ACI Integration requires either a cert or a username and password")
raise APIAuthException("The Cisco ACI Integration requires either a cert or a username and password")
raise ConfigurationError("The Cisco ACI Integration requires either a cert or a username and password")

response = self.session.send(prepped_request, verify=self.verify, timeout=self.timeout)
if response.status_code == 403:
raise APIAuthException("Received 403 when making request: %s", response.text)
try:
response.raise_for_status()
except Exception as e:
Expand Down Expand Up @@ -137,7 +141,7 @@ def __init__(
self.cert_key = cert_key
elif not password:
msg = "You need to have either a password or a cert"
raise ConfigurationException(msg)
raise ConfigurationError(msg)

def close(self):
for session in self.sessions:
Expand Down Expand Up @@ -210,7 +214,14 @@ def make_request(self, path):
# allow for multiple APICs in a cluster to be included in one check so that the check
# does not bombard a single APIC with dozens of requests and cause it to slow down
session = random.choice(self.sessions)
return session.make_request(path)
try:
return session.make_request(path)
except APIAuthException:
# If we get a 403 answer this may mean that the token expired. Let's refresh the token
# by login again and retry the request. If it fails again, the integration should exit.
self.login()
session = random.choice(self.sessions)
return session.make_request(path)

def get_apps(self, tenant):
path = "/api/mo/uni/tn-{}.json?query-target=subtree&target-subtree-class=fvAp".format(tenant)
Expand Down
4 changes: 0 additions & 4 deletions cisco_aci/datadog_checks/cisco_aci/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,3 @@ class APIConnectionException(APIException):

class APIParsingException(APIException):
pass


class ConfigurationException(Exception):
pass
30 changes: 30 additions & 0 deletions cisco_aci/tests/test_cisco.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import pytest
import simplejson as json
from mock import MagicMock
from requests import Session

from datadog_checks.cisco_aci import CiscoACICheck
Expand Down Expand Up @@ -71,3 +72,32 @@ def test_cisco(aggregator, session_mock):
cisco_aci_check._api_cache[hash_mutable(common.CONFIG)] = api

cisco_aci_check.check(common.CONFIG)


def test_recover_from_expired_token(aggregator):
# First api answers with 403 to force the check to re-authenticate
unauthentified_response = MagicMock(status_code=403)
# Api answer when a request is being made to the login endpoint
login_response = MagicMock()
# Third api answer, when the check retries the initial endpoint but is now authenticated
valid_response = MagicMock()
valid_response.json = MagicMock(return_value={"foo": "bar"})
session = MagicMock()
session.send = MagicMock(side_effect=[unauthentified_response, login_response, valid_response])

session_wrapper = SessionWrapper(aci_url=common.ACI_URL, log=MagicMock(), session=session, apic_cookie="cookie")

api = Api(common.ACI_URLS, common.USERNAME, password=common.PASSWORD, sessions=[session_wrapper])
api._refresh_sessions = False

data = api.make_request("")
# Assert that we retrieved the value from `valid_response.json()`
assert data == {"foo": "bar"}

session_calls = session.send._mock_call_args_list
# Assert that the first call was to the ACI_URL
assert session_calls[0].args[0].url == common.ACI_URL + "/"
# Assert that the second call was to the login endpoint
assert 'aaaLogin.xml' in session_calls[1].args[0].url
# Assert that the last call was to the ACI_URL again
assert session_calls[2].args[0].url == common.ACI_URL + "/"

0 comments on commit 8e8ec03

Please sign in to comment.