mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
972 lines
39 KiB
Python
972 lines
39 KiB
Python
from __future__ import absolute_import
|
|
import os
|
|
import sys
|
|
import copy
|
|
import json
|
|
import logging
|
|
import time
|
|
from datetime import datetime, timedelta
|
|
from ssl import SSLError
|
|
|
|
|
|
class MockResponse(object):
|
|
def __init__(self, *args, **kwargs):
|
|
raise Exception("Requests library Response object not found. Using fake one.")
|
|
|
|
|
|
class MockRequestsConnectionError(Exception):
|
|
pass
|
|
|
|
|
|
class MockSession(object):
|
|
def __init__(self, *args, **kwargs):
|
|
raise Exception("Requests library Session object not found. Using fake one.")
|
|
|
|
|
|
HAS_AVI = True
|
|
try:
|
|
from requests import ConnectionError as RequestsConnectionError
|
|
from requests import Response
|
|
from requests.sessions import Session
|
|
except ImportError:
|
|
HAS_AVI = False
|
|
Response = MockResponse
|
|
RequestsConnectionError = MockRequestsConnectionError
|
|
Session = MockSession
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
sessionDict = {}
|
|
|
|
|
|
def avi_timedelta(td):
|
|
'''
|
|
This is a wrapper class to workaround python 2.6 builtin datetime.timedelta
|
|
does not have total_seconds method
|
|
:param timedelta object
|
|
'''
|
|
if type(td) != timedelta:
|
|
raise TypeError()
|
|
if sys.version_info >= (2, 7):
|
|
ts = td.total_seconds()
|
|
else:
|
|
ts = td.seconds + (24 * 3600 * td.days)
|
|
return ts
|
|
|
|
|
|
def avi_sdk_syslog_logger(logger_name='avi.sdk'):
|
|
# The following sets up syslog module to log underlying avi SDK messages
|
|
# based on the environment variables:
|
|
# AVI_LOG_HANDLER: names the logging handler to use. Only syslog is
|
|
# supported.
|
|
# AVI_LOG_LEVEL: Logging level used for the avi SDK. Default is DEBUG
|
|
# AVI_SYSLOG_ADDRESS: Destination address for the syslog handler.
|
|
# Default is /dev/log
|
|
from logging.handlers import SysLogHandler
|
|
lf = '[%(asctime)s] %(levelname)s [%(module)s.%(funcName)s:%(lineno)d] %(message)s'
|
|
log = logging.getLogger(logger_name)
|
|
log_level = os.environ.get('AVI_LOG_LEVEL', 'DEBUG')
|
|
if log_level:
|
|
log.setLevel(getattr(logging, log_level))
|
|
formatter = logging.Formatter(lf)
|
|
sh = SysLogHandler(address=os.environ.get('AVI_SYSLOG_ADDRESS', '/dev/log'))
|
|
sh.setFormatter(formatter)
|
|
log.addHandler(sh)
|
|
return log
|
|
|
|
|
|
class ObjectNotFound(Exception):
|
|
pass
|
|
|
|
|
|
class APIError(Exception):
|
|
def __init__(self, arg, rsp=None):
|
|
self.args = [arg, rsp]
|
|
self.rsp = rsp
|
|
|
|
|
|
class AviServerError(APIError):
|
|
def __init__(self, arg, rsp=None):
|
|
super(AviServerError, self).__init__(arg, rsp)
|
|
|
|
|
|
class APINotImplemented(Exception):
|
|
pass
|
|
|
|
|
|
class ApiResponse(Response):
|
|
"""
|
|
Returns copy of the requests.Response object provides additional helper
|
|
routines
|
|
1. obj: returns dictionary of Avi Object
|
|
"""
|
|
def __init__(self, rsp):
|
|
super(ApiResponse, self).__init__()
|
|
for k, v in list(rsp.__dict__.items()):
|
|
setattr(self, k, v)
|
|
|
|
def json(self):
|
|
"""
|
|
Extends the session default json interface to handle special errors
|
|
and raise Exceptions
|
|
returns the Avi object as a dictionary from rsp.text
|
|
"""
|
|
if self.status_code in (200, 201):
|
|
if not self.text:
|
|
# In cases like status_code == 201 the response text could be
|
|
# empty string.
|
|
return None
|
|
return super(ApiResponse, self).json()
|
|
elif self.status_code == 204:
|
|
# No response needed; e.g., delete operation
|
|
return None
|
|
elif self.status_code == 404:
|
|
raise ObjectNotFound('HTTP Error: %s Error Msg %s' % (
|
|
self.status_code, self.text), self)
|
|
elif self.status_code >= 500:
|
|
raise AviServerError('HTTP Error: %s Error Msg %s' % (
|
|
self.status_code, self.text), self)
|
|
else:
|
|
raise APIError('HTTP Error: %s Error Msg %s' % (
|
|
self.status_code, self.text), self)
|
|
|
|
def count(self):
|
|
"""
|
|
return the number of objects in the collection response. If it is not
|
|
a collection response then it would simply return 1.
|
|
"""
|
|
obj = self.json()
|
|
if 'count' in obj:
|
|
# this was a resposne to collection
|
|
return obj['count']
|
|
return 1
|
|
|
|
@staticmethod
|
|
def to_avi_response(resp):
|
|
if type(resp) == Response:
|
|
return ApiResponse(resp)
|
|
return resp
|
|
|
|
|
|
class AviCredentials(object):
|
|
controller = ''
|
|
username = ''
|
|
password = ''
|
|
api_version = '16.4.4'
|
|
tenant = None
|
|
tenant_uuid = None
|
|
token = None
|
|
port = None
|
|
timeout = 300
|
|
session_id = None
|
|
csrftoken = None
|
|
|
|
def __init__(self, **kwargs):
|
|
for k, v in kwargs.items():
|
|
setattr(self, k, v)
|
|
|
|
def update_from_ansible_module(self, m):
|
|
"""
|
|
:param m: ansible module
|
|
:return:
|
|
"""
|
|
if m.params.get('avi_credentials'):
|
|
for k, v in m.params['avi_credentials'].items():
|
|
if hasattr(self, k):
|
|
setattr(self, k, v)
|
|
if m.params['controller']:
|
|
self.controller = m.params['controller']
|
|
if m.params['username']:
|
|
self.username = m.params['username']
|
|
if m.params['password']:
|
|
self.password = m.params['password']
|
|
if (m.params['api_version'] and
|
|
(m.params['api_version'] != '16.4.4')):
|
|
self.api_version = m.params['api_version']
|
|
if m.params['tenant']:
|
|
self.tenant = m.params['tenant']
|
|
if m.params['tenant_uuid']:
|
|
self.tenant_uuid = m.params['tenant_uuid']
|
|
if m.params.get('session_id'):
|
|
self.session_id = m.params['session_id']
|
|
if m.params.get('csrftoken'):
|
|
self.csrftoken = m.params['csrftoken']
|
|
|
|
def __str__(self):
|
|
return 'controller %s user %s api %s tenant %s' % (
|
|
self.controller, self.username, self.api_version, self.tenant)
|
|
|
|
|
|
class ApiSession(Session):
|
|
"""
|
|
Extends the Request library's session object to provide helper
|
|
utilities to work with Avi Controller like authentication, api massaging
|
|
etc.
|
|
"""
|
|
|
|
# This keeps track of the process which created the cache.
|
|
# At anytime the pid of the process changes then it would create
|
|
# a new cache for that process.
|
|
AVI_SLUG = 'Slug'
|
|
SESSION_CACHE_EXPIRY = 20 * 60
|
|
SHARED_USER_HDRS = ['X-CSRFToken', 'Session-Id', 'Referer', 'Content-Type']
|
|
MAX_API_RETRIES = 3
|
|
|
|
def __init__(self, controller_ip=None, username=None, password=None,
|
|
token=None, tenant=None, tenant_uuid=None, verify=False,
|
|
port=None, timeout=60, api_version=None,
|
|
retry_conxn_errors=True, data_log=False,
|
|
avi_credentials=None, session_id=None, csrftoken=None,
|
|
lazy_authentication=False, max_api_retries=None):
|
|
"""
|
|
ApiSession takes ownership of avi_credentials and may update the
|
|
information inside it.
|
|
|
|
Initialize new session object with authenticated token from login api.
|
|
It also keeps a cache of user sessions that are cleaned up if inactive
|
|
for more than 20 mins.
|
|
|
|
Notes:
|
|
01. If mode is https and port is none or 443, we don't embed the
|
|
port in the prefix. The prefix would be 'https://ip'. If port
|
|
is a non-default value then we concatenate https://ip:port
|
|
in the prefix.
|
|
02. If mode is http and the port is none or 80, we don't embed the
|
|
port in the prefix. The prefix would be 'http://ip'. If port is
|
|
a non-default value, then we concatenate http://ip:port in
|
|
the prefix.
|
|
"""
|
|
super(ApiSession, self).__init__()
|
|
if not avi_credentials:
|
|
tenant = tenant if tenant else "admin"
|
|
self.avi_credentials = AviCredentials(
|
|
controller=controller_ip, username=username, password=password,
|
|
api_version=api_version, tenant=tenant, tenant_uuid=tenant_uuid,
|
|
token=token, port=port, timeout=timeout,
|
|
session_id=session_id, csrftoken=csrftoken)
|
|
else:
|
|
self.avi_credentials = avi_credentials
|
|
self.headers = {}
|
|
self.verify = verify
|
|
self.retry_conxn_errors = retry_conxn_errors
|
|
self.remote_api_version = {}
|
|
self.session_cookie_name = ''
|
|
self.user_hdrs = {}
|
|
self.data_log = data_log
|
|
self.num_session_retries = 0
|
|
self.retry_wait_time = 0
|
|
self.max_session_retries = (
|
|
self.MAX_API_RETRIES if max_api_retries is None
|
|
else int(max_api_retries))
|
|
# Refer Notes 01 and 02
|
|
k_port = port if port else 443
|
|
if self.avi_credentials.controller.startswith('http'):
|
|
k_port = 80 if not self.avi_credentials.port else k_port
|
|
if self.avi_credentials.port is None or self.avi_credentials.port\
|
|
== 80:
|
|
self.prefix = self.avi_credentials.controller
|
|
else:
|
|
self.prefix = '{x}:{y}'.format(
|
|
x=self.avi_credentials.controller,
|
|
y=self.avi_credentials.port)
|
|
else:
|
|
if port is None or port == 443:
|
|
self.prefix = 'https://{x}'.format(
|
|
x=self.avi_credentials.controller)
|
|
else:
|
|
self.prefix = 'https://{x}:{y}'.format(
|
|
x=self.avi_credentials.controller,
|
|
y=self.avi_credentials.port)
|
|
self.timeout = timeout
|
|
self.key = '%s:%s:%s' % (self.avi_credentials.controller,
|
|
self.avi_credentials.username, k_port)
|
|
# Added api token and session id to sessionDict for handle single
|
|
# session
|
|
if self.avi_credentials.csrftoken:
|
|
sessionDict[self.key] = {
|
|
'api': self,
|
|
"csrftoken": self.avi_credentials.csrftoken,
|
|
"session_id": self.avi_credentials.session_id,
|
|
"last_used": datetime.utcnow()
|
|
}
|
|
elif lazy_authentication:
|
|
sessionDict.get(self.key, {}).update(
|
|
{'api': self, "last_used": datetime.utcnow()})
|
|
else:
|
|
self.authenticate_session()
|
|
|
|
self.num_session_retries = 0
|
|
self.pid = os.getpid()
|
|
ApiSession._clean_inactive_sessions()
|
|
return
|
|
|
|
@property
|
|
def controller_ip(self):
|
|
return self.avi_credentials.controller
|
|
|
|
@controller_ip.setter
|
|
def controller_ip(self, controller_ip):
|
|
self.avi_credentials.controller = controller_ip
|
|
|
|
@property
|
|
def username(self):
|
|
return self.avi_credentials.username
|
|
|
|
@property
|
|
def connected(self):
|
|
return sessionDict.get(self.key, {}).get('connected', False)
|
|
|
|
@username.setter
|
|
def username(self, username):
|
|
self.avi_credentials.username = username
|
|
|
|
@property
|
|
def password(self):
|
|
return self.avi_credentials.password
|
|
|
|
@password.setter
|
|
def password(self, password):
|
|
self.avi_credentials.password = password
|
|
|
|
@property
|
|
def keystone_token(self):
|
|
return sessionDict.get(self.key, {}).get('csrftoken', None)
|
|
|
|
@keystone_token.setter
|
|
def keystone_token(self, token):
|
|
sessionDict[self.key]['csrftoken'] = token
|
|
|
|
@property
|
|
def tenant_uuid(self):
|
|
self.avi_credentials.tenant_uuid
|
|
|
|
@tenant_uuid.setter
|
|
def tenant_uuid(self, tenant_uuid):
|
|
self.avi_credentials.tenant_uuid = tenant_uuid
|
|
|
|
@property
|
|
def tenant(self):
|
|
return self.avi_credentials.tenant
|
|
|
|
@tenant.setter
|
|
def tenant(self, tenant):
|
|
if tenant:
|
|
self.avi_credentials.tenant = tenant
|
|
else:
|
|
self.avi_credentials.tenant = 'admin'
|
|
|
|
@property
|
|
def port(self):
|
|
self.avi_credentials.port
|
|
|
|
@port.setter
|
|
def port(self, port):
|
|
self.avi_credentials.port = port
|
|
|
|
@property
|
|
def api_version(self):
|
|
return self.avi_credentials.api_version
|
|
|
|
@api_version.setter
|
|
def api_version(self, api_version):
|
|
self.avi_credentials.api_version = api_version
|
|
|
|
@property
|
|
def session_id(self):
|
|
return sessionDict[self.key]['session_id']
|
|
|
|
def get_context(self):
|
|
return {
|
|
'session_id': sessionDict[self.key]['session_id'],
|
|
'csrftoken': sessionDict[self.key]['csrftoken']
|
|
}
|
|
|
|
@staticmethod
|
|
def clear_cached_sessions():
|
|
global sessionDict
|
|
sessionDict = {}
|
|
|
|
@staticmethod
|
|
def get_session(
|
|
controller_ip=None, username=None, password=None, token=None, tenant=None,
|
|
tenant_uuid=None, verify=False, port=None, timeout=60,
|
|
retry_conxn_errors=True, api_version=None, data_log=False,
|
|
avi_credentials=None, session_id=None, csrftoken=None,
|
|
lazy_authentication=False, max_api_retries=None):
|
|
"""
|
|
returns the session object for same user and tenant
|
|
calls init if session dose not exist and adds it to session cache
|
|
:param controller_ip: controller IP address
|
|
:param username:
|
|
:param password:
|
|
:param token: Token to use; example, a valid keystone token
|
|
:param tenant: Name of the tenant on Avi Controller
|
|
:param tenant_uuid: Don't specify tenant when using tenant_id
|
|
:param port: Rest-API may use a different port other than 443
|
|
:param timeout: timeout for API calls; Default value is 60 seconds
|
|
:param retry_conxn_errors: retry on connection errors
|
|
:param api_version: Controller API version
|
|
"""
|
|
if not avi_credentials:
|
|
tenant = tenant if tenant else "admin"
|
|
avi_credentials = AviCredentials(
|
|
controller=controller_ip, username=username, password=password,
|
|
api_version=api_version, tenant=tenant, tenant_uuid=tenant_uuid,
|
|
token=token, port=port, timeout=timeout,
|
|
session_id=session_id, csrftoken=csrftoken)
|
|
|
|
k_port = avi_credentials.port if avi_credentials.port else 443
|
|
if avi_credentials.controller.startswith('http'):
|
|
k_port = 80 if not avi_credentials.port else k_port
|
|
key = '%s:%s:%s' % (avi_credentials.controller,
|
|
avi_credentials.username, k_port)
|
|
cached_session = sessionDict.get(key)
|
|
if cached_session:
|
|
user_session = cached_session['api']
|
|
if not (user_session.avi_credentials.csrftoken or
|
|
lazy_authentication):
|
|
user_session.authenticate_session()
|
|
else:
|
|
user_session = ApiSession(
|
|
controller_ip, username, password, token=token, tenant=tenant,
|
|
tenant_uuid=tenant_uuid, verify=verify, port=port,
|
|
timeout=timeout, retry_conxn_errors=retry_conxn_errors,
|
|
api_version=api_version, data_log=data_log,
|
|
avi_credentials=avi_credentials,
|
|
lazy_authentication=lazy_authentication,
|
|
max_api_retries=max_api_retries)
|
|
ApiSession._clean_inactive_sessions()
|
|
return user_session
|
|
|
|
def reset_session(self):
|
|
"""
|
|
resets and re-authenticates the current session.
|
|
"""
|
|
sessionDict[self.key]['connected'] = False
|
|
logger.info('resetting session for %s', self.key)
|
|
self.user_hdrs = {}
|
|
for k, v in self.headers.items():
|
|
if k not in self.SHARED_USER_HDRS:
|
|
self.user_hdrs[k] = v
|
|
self.headers = {}
|
|
self.authenticate_session()
|
|
|
|
def authenticate_session(self):
|
|
"""
|
|
Performs session authentication with Avi controller and stores
|
|
session cookies and sets header options like tenant.
|
|
"""
|
|
body = {"username": self.avi_credentials.username}
|
|
if self.avi_credentials.password:
|
|
body["password"] = self.avi_credentials.password
|
|
elif self.avi_credentials.token:
|
|
body["token"] = self.avi_credentials.token
|
|
else:
|
|
raise APIError("Neither user password or token provided")
|
|
logger.debug('authenticating user %s prefix %s',
|
|
self.avi_credentials.username, self.prefix)
|
|
self.cookies.clear()
|
|
err = None
|
|
try:
|
|
rsp = super(ApiSession, self).post(
|
|
self.prefix + "/login", body, timeout=self.timeout, verify=self.verify)
|
|
|
|
if rsp.status_code == 200:
|
|
self.num_session_retries = 0
|
|
self.remote_api_version = rsp.json().get('version', {})
|
|
self.session_cookie_name = rsp.json().get('session_cookie_name', 'sessionid')
|
|
self.headers.update(self.user_hdrs)
|
|
if rsp.cookies and 'csrftoken' in rsp.cookies:
|
|
csrftoken = rsp.cookies['csrftoken']
|
|
sessionDict[self.key] = {
|
|
'csrftoken': csrftoken,
|
|
'session_id': rsp.cookies[self.session_cookie_name],
|
|
'last_used': datetime.utcnow(),
|
|
'api': self,
|
|
'connected': True
|
|
}
|
|
logger.debug("authentication success for user %s",
|
|
self.avi_credentials.username)
|
|
return
|
|
# Check for bad request and invalid credentials response code
|
|
elif rsp.status_code in [401, 403]:
|
|
logger.error('Status Code %s msg %s', rsp.status_code, rsp.text)
|
|
err = APIError('Status Code %s msg %s' % (
|
|
rsp.status_code, rsp.text), rsp)
|
|
raise err
|
|
else:
|
|
logger.error("Error status code %s msg %s", rsp.status_code,
|
|
rsp.text)
|
|
err = APIError('Status Code %s msg %s' % (
|
|
rsp.status_code, rsp.text), rsp)
|
|
except (RequestsConnectionError, SSLError) as e:
|
|
if not self.retry_conxn_errors:
|
|
raise
|
|
logger.warning('Connection error retrying %s', e)
|
|
err = e
|
|
# comes here only if there was either exception or login was not
|
|
# successful
|
|
if self.retry_wait_time:
|
|
time.sleep(self.retry_wait_time)
|
|
self.num_session_retries += 1
|
|
if self.num_session_retries > self.max_session_retries:
|
|
self.num_session_retries = 0
|
|
logger.error("giving up after %d retries connection failure %s",
|
|
self.max_session_retries, True)
|
|
ret_err = (
|
|
err if err else APIError("giving up after %d retries connection failure %s" %
|
|
(self.max_session_retries, True)))
|
|
raise ret_err
|
|
self.authenticate_session()
|
|
return
|
|
|
|
def _get_api_headers(self, tenant, tenant_uuid, timeout, headers,
|
|
api_version):
|
|
"""
|
|
returns the headers that are passed to the requests.Session api calls.
|
|
"""
|
|
api_hdrs = copy.deepcopy(self.headers)
|
|
api_hdrs.update({
|
|
"Referer": self.prefix,
|
|
"Content-Type": "application/json"
|
|
})
|
|
api_hdrs['timeout'] = str(timeout)
|
|
if self.key in sessionDict and 'csrftoken' in sessionDict.get(self.key):
|
|
api_hdrs['X-CSRFToken'] = sessionDict.get(self.key)['csrftoken']
|
|
else:
|
|
self.authenticate_session()
|
|
api_hdrs['X-CSRFToken'] = sessionDict.get(self.key)['csrftoken']
|
|
if api_version:
|
|
api_hdrs['X-Avi-Version'] = api_version
|
|
elif self.avi_credentials.api_version:
|
|
api_hdrs['X-Avi-Version'] = self.avi_credentials.api_version
|
|
if tenant:
|
|
tenant_uuid = None
|
|
elif tenant_uuid:
|
|
tenant = None
|
|
else:
|
|
tenant = self.avi_credentials.tenant
|
|
tenant_uuid = self.avi_credentials.tenant_uuid
|
|
if tenant_uuid:
|
|
api_hdrs.update({"X-Avi-Tenant-UUID": "%s" % tenant_uuid})
|
|
api_hdrs.pop("X-Avi-Tenant", None)
|
|
elif tenant:
|
|
api_hdrs.update({"X-Avi-Tenant": "%s" % tenant})
|
|
api_hdrs.pop("X-Avi-Tenant-UUID", None)
|
|
# Override any user headers that were passed by users. We don't know
|
|
# when the user had updated the user_hdrs
|
|
if self.user_hdrs:
|
|
api_hdrs.update(self.user_hdrs)
|
|
if headers:
|
|
# overwrite the headers passed via the API calls.
|
|
api_hdrs.update(headers)
|
|
return api_hdrs
|
|
|
|
def _api(self, api_name, path, tenant, tenant_uuid, data=None,
|
|
headers=None, timeout=None, api_version=None, **kwargs):
|
|
"""
|
|
It calls the requests.Session APIs and handles session expiry
|
|
and other situations where session needs to be reset.
|
|
returns ApiResponse object
|
|
:param path: takes relative path to the AVI api.
|
|
:param tenant: overrides the tenant used during session creation
|
|
:param tenant_uuid: overrides the tenant or tenant_uuid during session
|
|
creation
|
|
:param timeout: timeout for API calls; Default value is 60 seconds
|
|
:param headers: dictionary of headers that override the session
|
|
headers.
|
|
"""
|
|
if self.pid != os.getpid():
|
|
logger.info('pid %d change detected new %d. Closing session',
|
|
self.pid, os.getpid())
|
|
self.close()
|
|
self.pid = os.getpid()
|
|
if timeout is None:
|
|
timeout = self.timeout
|
|
fullpath = self._get_api_path(path)
|
|
fn = getattr(super(ApiSession, self), api_name)
|
|
api_hdrs = self._get_api_headers(tenant, tenant_uuid, timeout, headers,
|
|
api_version)
|
|
connection_error = False
|
|
err = None
|
|
cookies = {
|
|
'csrftoken': api_hdrs['X-CSRFToken'],
|
|
}
|
|
try:
|
|
if self.session_cookie_name:
|
|
cookies[self.session_cookie_name] = sessionDict[self.key]['session_id']
|
|
except KeyError:
|
|
pass
|
|
try:
|
|
if (data is not None) and (type(data) == dict):
|
|
resp = fn(fullpath, data=json.dumps(data), headers=api_hdrs,
|
|
timeout=timeout, cookies=cookies, **kwargs)
|
|
else:
|
|
resp = fn(fullpath, data=data, headers=api_hdrs,
|
|
timeout=timeout, cookies=cookies, **kwargs)
|
|
except (RequestsConnectionError, SSLError) as e:
|
|
logger.warning('Connection error retrying %s', e)
|
|
if not self.retry_conxn_errors:
|
|
raise
|
|
connection_error = True
|
|
err = e
|
|
except Exception as e:
|
|
logger.error('Error in Requests library %s', e)
|
|
raise
|
|
if not connection_error:
|
|
logger.debug('path: %s http_method: %s hdrs: %s params: '
|
|
'%s data: %s rsp: %s', fullpath, api_name.upper(),
|
|
api_hdrs, kwargs, data,
|
|
(resp.text if self.data_log else 'None'))
|
|
if connection_error or resp.status_code in (401, 419):
|
|
if connection_error:
|
|
try:
|
|
self.close()
|
|
except Exception:
|
|
# ignoring exception in cleanup path
|
|
pass
|
|
logger.warning('Connection failed, retrying.')
|
|
# Adding sleep before retrying
|
|
if self.retry_wait_time:
|
|
time.sleep(self.retry_wait_time)
|
|
else:
|
|
logger.info('received error %d %s so resetting connection',
|
|
resp.status_code, resp.text)
|
|
ApiSession.reset_session(self)
|
|
self.num_session_retries += 1
|
|
if self.num_session_retries > self.max_session_retries:
|
|
# Added this such that any code which re-tries can succeed
|
|
# eventually.
|
|
self.num_session_retries = 0
|
|
if not connection_error:
|
|
err = APIError('Status Code %s msg %s' % (
|
|
resp.status_code, resp.text), resp)
|
|
logger.error(
|
|
"giving up after %d retries conn failure %s err %s",
|
|
self.max_session_retries, connection_error, err)
|
|
ret_err = (
|
|
err if err else APIError("giving up after %d retries connection failure %s" %
|
|
(self.max_session_retries, True)))
|
|
raise ret_err
|
|
# should restore the updated_hdrs to one passed down
|
|
resp = self._api(api_name, path, tenant, tenant_uuid, data,
|
|
headers=headers, api_version=api_version,
|
|
timeout=timeout, **kwargs)
|
|
self.num_session_retries = 0
|
|
|
|
if resp.cookies and 'csrftoken' in resp.cookies:
|
|
csrftoken = resp.cookies['csrftoken']
|
|
self.headers.update({"X-CSRFToken": csrftoken})
|
|
self._update_session_last_used()
|
|
return ApiResponse.to_avi_response(resp)
|
|
|
|
def get_controller_details(self):
|
|
result = {
|
|
"controller_ip": self.controller_ip,
|
|
"controller_api_version": self.remote_api_version
|
|
}
|
|
return result
|
|
|
|
def get(self, path, tenant='', tenant_uuid='', timeout=None, params=None,
|
|
api_version=None, **kwargs):
|
|
"""
|
|
It extends the Session Library interface to add AVI API prefixes,
|
|
handle session exceptions related to authentication and update
|
|
the global user session cache.
|
|
:param path: takes relative path to the AVI api.
|
|
:param tenant: overrides the tenant used during session creation
|
|
:param tenant_uuid: overrides the tenant or tenant_uuid during session
|
|
creation
|
|
:param timeout: timeout for API calls; Default value is 60 seconds
|
|
:param params: dictionary of key value pairs to be sent as query
|
|
parameters
|
|
:param api_version: overrides x-avi-header in request header during
|
|
session creation
|
|
get method takes relative path to service and kwargs as per Session
|
|
class get method
|
|
returns session's response object
|
|
"""
|
|
return self._api('get', path, tenant, tenant_uuid, timeout=timeout,
|
|
params=params, api_version=api_version, **kwargs)
|
|
|
|
def get_object_by_name(self, path, name, tenant='', tenant_uuid='',
|
|
timeout=None, params=None, api_version=None,
|
|
**kwargs):
|
|
"""
|
|
Helper function to access Avi REST Objects using object
|
|
type and name. It behaves like python dictionary interface where it
|
|
returns None when the object is not present in the AviController.
|
|
Internally, it transforms the request to api/path?name=<name>...
|
|
:param path: relative path to service
|
|
:param name: name of the object
|
|
:param tenant: overrides the tenant used during session creation
|
|
:param tenant_uuid: overrides the tenant or tenant_uuid during session
|
|
creation
|
|
:param timeout: timeout for API calls; Default value is 60 seconds
|
|
:param params: dictionary of key value pairs to be sent as query
|
|
parameters
|
|
:param api_version: overrides x-avi-header in request header during
|
|
session creation
|
|
returns dictionary object if successful else None
|
|
"""
|
|
obj = None
|
|
if not params:
|
|
params = {}
|
|
params['name'] = name
|
|
resp = self.get(path, tenant=tenant, tenant_uuid=tenant_uuid,
|
|
timeout=timeout,
|
|
params=params, api_version=api_version, **kwargs)
|
|
if resp.status_code in (401, 419):
|
|
ApiSession.reset_session(self)
|
|
resp = self.get_object_by_name(
|
|
path, name, tenant, tenant_uuid, timeout=timeout,
|
|
params=params, **kwargs)
|
|
if resp.status_code > 499 or 'Invalid version' in resp.text:
|
|
logger.error('Error in get object by name for %s named %s. '
|
|
'Error: %s', path, name, resp.text)
|
|
raise AviServerError(resp.text, rsp=resp)
|
|
elif resp.status_code > 299:
|
|
return obj
|
|
try:
|
|
if 'results' in resp.json():
|
|
obj = resp.json()['results'][0]
|
|
else:
|
|
# For apis returning single object eg. api/cluster
|
|
obj = resp.json()
|
|
except IndexError:
|
|
logger.warning('Warning: Object Not found for %s named %s',
|
|
path, name)
|
|
obj = None
|
|
self._update_session_last_used()
|
|
return obj
|
|
|
|
def post(self, path, data=None, tenant='', tenant_uuid='', timeout=None,
|
|
force_uuid=None, params=None, api_version=None, **kwargs):
|
|
"""
|
|
It extends the Session Library interface to add AVI API prefixes,
|
|
handle session exceptions related to authentication and update
|
|
the global user session cache.
|
|
:param path: takes relative path to the AVI api.It is modified by
|
|
the library to conform to AVI Controller's REST API interface
|
|
:param data: dictionary of the data. Support for json string
|
|
is deprecated
|
|
:param tenant: overrides the tenant used during session creation
|
|
:param tenant_uuid: overrides the tenant or tenant_uuid during session
|
|
creation
|
|
:param timeout: timeout for API calls; Default value is 60 seconds
|
|
:param params: dictionary of key value pairs to be sent as query
|
|
parameters
|
|
:param api_version: overrides x-avi-header in request header during
|
|
session creation
|
|
returns session's response object
|
|
"""
|
|
if force_uuid is not None:
|
|
headers = kwargs.get('headers', {})
|
|
headers[self.AVI_SLUG] = force_uuid
|
|
kwargs['headers'] = headers
|
|
return self._api('post', path, tenant, tenant_uuid, data=data,
|
|
timeout=timeout, params=params,
|
|
api_version=api_version, **kwargs)
|
|
|
|
def put(self, path, data=None, tenant='', tenant_uuid='',
|
|
timeout=None, params=None, api_version=None, **kwargs):
|
|
"""
|
|
It extends the Session Library interface to add AVI API prefixes,
|
|
handle session exceptions related to authentication and update
|
|
the global user session cache.
|
|
:param path: takes relative path to the AVI api.It is modified by
|
|
the library to conform to AVI Controller's REST API interface
|
|
:param data: dictionary of the data. Support for json string
|
|
is deprecated
|
|
:param tenant: overrides the tenant used during session creation
|
|
:param tenant_uuid: overrides the tenant or tenant_uuid during session
|
|
creation
|
|
:param timeout: timeout for API calls; Default value is 60 seconds
|
|
:param params: dictionary of key value pairs to be sent as query
|
|
parameters
|
|
:param api_version: overrides x-avi-header in request header during
|
|
session creation
|
|
returns session's response object
|
|
"""
|
|
return self._api('put', path, tenant, tenant_uuid, data=data,
|
|
timeout=timeout, params=params,
|
|
api_version=api_version, **kwargs)
|
|
|
|
def patch(self, path, data=None, tenant='', tenant_uuid='',
|
|
timeout=None, params=None, api_version=None, **kwargs):
|
|
"""
|
|
It extends the Session Library interface to add AVI API prefixes,
|
|
handle session exceptions related to authentication and update
|
|
the global user session cache.
|
|
:param path: takes relative path to the AVI api.It is modified by
|
|
the library to conform to AVI Controller's REST API interface
|
|
:param data: dictionary of the data. Support for json string
|
|
is deprecated
|
|
:param tenant: overrides the tenant used during session creation
|
|
:param tenant_uuid: overrides the tenant or tenant_uuid during session
|
|
creation
|
|
:param timeout: timeout for API calls; Default value is 60 seconds
|
|
:param params: dictionary of key value pairs to be sent as query
|
|
parameters
|
|
:param api_version: overrides x-avi-header in request header during
|
|
session creation
|
|
returns session's response object
|
|
"""
|
|
return self._api('patch', path, tenant, tenant_uuid, data=data,
|
|
timeout=timeout, params=params,
|
|
api_version=api_version, **kwargs)
|
|
|
|
def put_by_name(self, path, name, data=None, tenant='',
|
|
tenant_uuid='', timeout=None, params=None,
|
|
api_version=None, **kwargs):
|
|
"""
|
|
Helper function to perform HTTP PUT on Avi REST Objects using object
|
|
type and name.
|
|
Internally, it transforms the request to api/path?name=<name>...
|
|
:param path: relative path to service
|
|
:param name: name of the object
|
|
:param data: dictionary of the data. Support for json string
|
|
is deprecated
|
|
:param tenant: overrides the tenant used during session creation
|
|
:param tenant_uuid: overrides the tenant or tenant_uuid during session
|
|
creation
|
|
:param timeout: timeout for API calls; Default value is 60 seconds
|
|
:param params: dictionary of key value pairs to be sent as query
|
|
parameters
|
|
:param api_version: overrides x-avi-header in request header during
|
|
session creation
|
|
returns session's response object
|
|
"""
|
|
uuid = self._get_uuid_by_name(
|
|
path, name, tenant, tenant_uuid, api_version=api_version)
|
|
path = '%s/%s' % (path, uuid)
|
|
return self.put(path, data, tenant, tenant_uuid, timeout=timeout,
|
|
params=params, api_version=api_version, **kwargs)
|
|
|
|
def delete(self, path, tenant='', tenant_uuid='', timeout=None, params=None,
|
|
data=None, api_version=None, **kwargs):
|
|
"""
|
|
It extends the Session Library interface to add AVI API prefixes,
|
|
handle session exceptions related to authentication and update
|
|
the global user session cache.
|
|
:param path: takes relative path to the AVI api.It is modified by
|
|
the library to conform to AVI Controller's REST API interface
|
|
:param tenant: overrides the tenant used during session creation
|
|
:param tenant_uuid: overrides the tenant or tenant_uuid during session
|
|
creation
|
|
:param timeout: timeout for API calls; Default value is 60 seconds
|
|
:param params: dictionary of key value pairs to be sent as query
|
|
parameters
|
|
:param data: dictionary of the data. Support for json string
|
|
is deprecated
|
|
:param api_version: overrides x-avi-header in request header during
|
|
session creation
|
|
returns session's response object
|
|
"""
|
|
return self._api('delete', path, tenant, tenant_uuid, data=data,
|
|
timeout=timeout, params=params,
|
|
api_version=api_version, **kwargs)
|
|
|
|
def delete_by_name(self, path, name, tenant='', tenant_uuid='',
|
|
timeout=None, params=None, api_version=None, **kwargs):
|
|
"""
|
|
Helper function to perform HTTP DELETE on Avi REST Objects using object
|
|
type and name.Internally, it transforms the request to
|
|
api/path?name=<name>...
|
|
:param path: relative path to service
|
|
:param name: name of the object
|
|
:param tenant: overrides the tenant used during session creation
|
|
:param tenant_uuid: overrides the tenant or tenant_uuid during session
|
|
creation
|
|
:param timeout: timeout for API calls; Default value is 60 seconds
|
|
:param params: dictionary of key value pairs to be sent as query
|
|
parameters
|
|
:param api_version: overrides x-avi-header in request header during
|
|
session creation
|
|
returns session's response object
|
|
"""
|
|
uuid = self._get_uuid_by_name(path, name, tenant, tenant_uuid,
|
|
api_version=api_version)
|
|
if not uuid:
|
|
raise ObjectNotFound("%s/?name=%s" % (path, name))
|
|
path = '%s/%s' % (path, uuid)
|
|
return self.delete(path, tenant, tenant_uuid, timeout=timeout,
|
|
params=params, api_version=api_version, **kwargs)
|
|
|
|
def get_obj_ref(self, obj):
|
|
"""returns reference url from dict object"""
|
|
if not obj:
|
|
return None
|
|
if isinstance(obj, Response):
|
|
obj = json.loads(obj.text)
|
|
if obj.get(0, None):
|
|
return obj[0]['url']
|
|
elif obj.get('url', None):
|
|
return obj['url']
|
|
elif obj.get('results', None):
|
|
return obj['results'][0]['url']
|
|
else:
|
|
return None
|
|
|
|
def get_obj_uuid(self, obj):
|
|
"""returns uuid from dict object"""
|
|
if not obj:
|
|
raise ObjectNotFound('Object %s Not found' % (obj))
|
|
if isinstance(obj, Response):
|
|
obj = json.loads(obj.text)
|
|
if obj.get(0, None):
|
|
return obj[0]['uuid']
|
|
elif obj.get('uuid', None):
|
|
return obj['uuid']
|
|
elif obj.get('results', None):
|
|
return obj['results'][0]['uuid']
|
|
else:
|
|
return None
|
|
|
|
def _get_api_path(self, path, uuid=None):
|
|
"""
|
|
This function returns the full url from relative path and uuid.
|
|
"""
|
|
if path == 'logout':
|
|
return self.prefix + '/' + path
|
|
elif uuid:
|
|
return self.prefix + '/api/' + path + '/' + uuid
|
|
else:
|
|
return self.prefix + '/api/' + path
|
|
|
|
def _get_uuid_by_name(self, path, name, tenant='admin',
|
|
tenant_uuid='', api_version=None):
|
|
"""gets object by name and service path and returns uuid"""
|
|
resp = self.get_object_by_name(
|
|
path, name, tenant, tenant_uuid, api_version=api_version)
|
|
if not resp:
|
|
raise ObjectNotFound("%s/%s" % (path, name))
|
|
return self.get_obj_uuid(resp)
|
|
|
|
def _update_session_last_used(self):
|
|
if self.key in sessionDict:
|
|
sessionDict[self.key]["last_used"] = datetime.utcnow()
|
|
|
|
@staticmethod
|
|
def _clean_inactive_sessions():
|
|
"""Removes sessions which are inactive more than 20 min"""
|
|
session_cache = sessionDict
|
|
logger.debug("cleaning inactive sessions in pid %d num elem %d",
|
|
os.getpid(), len(session_cache))
|
|
keys_to_delete = []
|
|
for key, session in list(session_cache.items()):
|
|
tdiff = avi_timedelta(datetime.utcnow() - session["last_used"])
|
|
if tdiff < ApiSession.SESSION_CACHE_EXPIRY:
|
|
continue
|
|
keys_to_delete.append(key)
|
|
for key in keys_to_delete:
|
|
del session_cache[key]
|
|
logger.debug("Removed session for : %s", key)
|
|
|
|
def delete_session(self):
|
|
""" Removes the session for cleanup"""
|
|
logger.debug("Removed session for : %s", self.key)
|
|
sessionDict.pop(self.key, None)
|
|
return
|
|
# End of file
|