Sergiy Markin 108a3c1ee4 Fix deprecated code
This PS replaces deprecared module pkg_resources,
also fixes the schema validation by adding
specific schema draft to choose in order to
prevent the processor to fall back to use the
latest draft that may potentially cause issues.

Also switched to quay.io/airshipit for base ubuntu
image

Change-Id: I687ef267ee3b027e80815e8852c8edcab5b5b727
2024-12-23 20:55:29 +00:00

290 lines
11 KiB
Python

# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Client for submitting authenticated requests to Promenade API."""
import logging
import requests
from urllib.parse import urlparse
from keystoneauth1 import exceptions as exc
import drydock_provisioner.error as errors
from drydock_provisioner.util import KeystoneUtils
# TODO: Remove this local implementation of Promenade Session and client once
# Promenade api client is available as part of Promenade project.
class PromenadeSession(object):
"""
A session to the Promenade API maintaining credentials and API options
:param string marker: (optional) external context marker
:param tuple timeout: (optional) a tuple of connect, read timeout values
to use as the default for invocations using this session. A single
value may also be supplied instead of a tuple to indicate only the
read timeout to use
"""
def __init__(self, scheme='http', marker=None, timeout=None):
self.logger = logging.getLogger(__name__)
self.__session = requests.Session()
self.set_auth()
self.marker = marker
self.__session.headers.update({'X-Context-Marker': marker})
self.prom_url = self._get_prom_url()
self.port = self.prom_url.port
self.host = self.prom_url.hostname
self.scheme = scheme
if self.port:
self.base_url = "%s://%s:%s/api/" % (self.scheme, self.host,
self.port)
else:
# assume default port for scheme
self.base_url = "%s://%s/api/" % (self.scheme, self.host)
self.default_timeout = self._calc_timeout_tuple((20, 30), timeout)
def set_auth(self):
auth_header = self._auth_gen()
self.__session.headers.update(auth_header)
def get(self, route, query=None, timeout=None):
"""
Send a GET request to Promenade.
:param string route: The URL string following the hostname and API prefix
:param dict query: A dict of k, v pairs to add to the query string
:param timeout: A single or tuple value for connect, read timeout.
A single value indicates the read timeout only
:return: A requests.Response object
"""
auth_refresh = False
while True:
url = self.base_url + route
self.logger.debug('GET ' + url)
self.logger.debug('Query Params: ' + str(query))
resp = self.__session.get(url,
params=query,
timeout=self._timeout(timeout))
if resp.status_code == 401 and not auth_refresh:
self.set_auth()
auth_refresh = True
else:
break
return resp
def put(self, endpoint, query=None, body=None, data=None, timeout=None):
"""
Send a PUT request to Promenade. If both body and data are specified,
body will be used.
:param string endpoint: The URL string following the hostname and API prefix
:param dict query: A dict of k, v parameters to add to the query string
:param string body: A string to use as the request body. Will be treated as raw
:param data: Something json.dumps(s) can serialize. Result will be used as the request body
:param timeout: A single or tuple value for connect, read timeout.
A single value indicates the read timeout only
:return: A requests.Response object
"""
auth_refresh = False
url = self.base_url + endpoint
while True:
self.logger.debug('PUT ' + url)
self.logger.debug('Query Params: ' + str(query))
if body is not None:
self.logger.debug("Sending PUT with explicit body: \n%s" %
body)
resp = self.__session.put(self.base_url + endpoint,
params=query,
data=body,
timeout=self._timeout(timeout))
else:
self.logger.debug("Sending PUT with JSON body: \n%s" %
str(data))
resp = self.__session.put(self.base_url + endpoint,
params=query,
json=data,
timeout=self._timeout(timeout))
if resp.status_code == 401 and not auth_refresh:
self.set_auth()
auth_refresh = True
else:
break
return resp
def post(self, endpoint, query=None, body=None, data=None, timeout=None):
"""
Send a POST request to Drydock. If both body and data are specified,
body will be used.
:param string endpoint: The URL string following the hostname and API prefix
:param dict query: A dict of k, v parameters to add to the query string
:param string body: A string to use as the request body. Will be treated as raw
:param data: Something json.dumps(s) can serialize. Result will be used as the request body
:param timeout: A single or tuple value for connect, read timeout.
A single value indicates the read timeout only
:return: A requests.Response object
"""
auth_refresh = False
url = self.base_url + endpoint
while True:
self.logger.debug('POST ' + url)
self.logger.debug('Query Params: ' + str(query))
if body is not None:
self.logger.debug("Sending POST with explicit body: \n%s" %
body)
resp = self.__session.post(self.base_url + endpoint,
params=query,
data=body,
timeout=self._timeout(timeout))
else:
self.logger.debug("Sending POST with JSON body: \n%s" %
str(data))
resp = self.__session.post(self.base_url + endpoint,
params=query,
json=data,
timeout=self._timeout(timeout))
if resp.status_code == 401 and not auth_refresh:
self.set_auth()
auth_refresh = True
else:
break
return resp
def _timeout(self, timeout=None):
"""Calculate the default timeouts for this session
:param timeout: A single or tuple value for connect, read timeout.
A single value indicates the read timeout only
:return: the tuple of the default timeouts used for this session
"""
return self._calc_timeout_tuple(self.default_timeout, timeout)
def _calc_timeout_tuple(self, def_timeout, timeout=None):
"""Calculate the default timeouts for this session
:param def_timeout: The default timeout tuple to be used if no specific
timeout value is supplied
:param timeout: A single or tuple value for connect, read timeout.
A single value indicates the read timeout only
:return: the tuple of the timeouts calculated
"""
connect_timeout, read_timeout = def_timeout
try:
if isinstance(timeout, tuple):
if all(isinstance(v, int)
for v in timeout) and len(timeout) == 2:
connect_timeout, read_timeout = timeout
else:
raise ValueError("Tuple non-integer or wrong length")
elif isinstance(timeout, int):
read_timeout = timeout
elif timeout is not None:
raise ValueError("Non integer timeout value")
except ValueError:
self.logger.warning(
"Timeout value must be a tuple of integers or a "
"single integer. Proceeding with values of "
"(%s, %s)", connect_timeout, read_timeout)
return (connect_timeout, read_timeout)
def _get_ks_session(self):
# Get keystone session object
try:
ks_session = KeystoneUtils.get_session()
except exc.AuthorizationFailure as aferr:
self.logger.error('Could not authorize against Keystone: %s',
str(aferr))
raise errors.DriverError(
'Could not authorize against Keystone: %s', str(aferr))
return ks_session
def _get_prom_url(self):
# Get promenade url from Keystone session object
ks_session = self._get_ks_session()
try:
prom_endpoint = ks_session.get_endpoint(
interface='internal', service_type='kubernetesprovisioner')
except exc.EndpointNotFound:
self.logger.error("Could not find an internal interface"
" defined in Keystone for Promenade")
raise errors.DriverError("Could not find an internal interface"
" defined in Keystone for Promenade")
prom_url = urlparse(prom_endpoint)
return prom_url
def _auth_gen(self):
# Get auth token from Keystone session
token = self._get_ks_session().get_auth_headers().get('X-Auth-Token')
return [('X-Auth-Token', token)]
class PromenadeClient(object):
""""
A client for the Promenade API
"""
def __init__(self):
self.session = PromenadeSession()
self.logger = logging.getLogger(__name__)
def relabel_node(self, node_id, node_labels):
""" Relabel kubernetes node
:param string node_id: Node id for node to be relabeled.
:param dict node_labels: The dictionary representation of node labels
that needs be re-applied to the node.
:return: response
"""
route = 'v1.0/node-labels/{}'.format(node_id)
self.logger.debug("promenade_client is calling %s API: body is %s" %
(route, str(node_labels)))
resp = self.session.put(route, data=node_labels)
self._check_response(resp)
return resp.json()
def _check_response(self, resp):
if resp.status_code == 401:
raise errors.ClientUnauthorizedError(
"Unauthorized access to %s, include valid token." % resp.url)
elif resp.status_code == 403:
raise errors.ClientForbiddenError("Forbidden access to %s" %
resp.url)
elif not resp.ok:
raise errors.ClientError("Error - received %d: %s" %
(resp.status_code, resp.text),
code=resp.status_code)