Alexandru Coman 9622714668
Fix the refresh_model method
For the moment the refresh_model method doesn't recreate the
initial structure of the model and some submodels are not
created anymore.

In order to avoid this scenario all the submodels will be
created in process_raw_data insted of the from_raw_data
method.

This patch also add some debug messages in order to ease
the debuging process.
2017-02-16 14:29:32 +02:00

211 lines
7.7 KiB
Python

# Copyright 2017 Cloudbase Solutions Srl
#
# 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.
"""Utilities used across the project."""
import json
import sys
import time
from oslo_log import log as logging
import requests
import requests_ntlm
import six
from hnv.common import constant
from hnv.common import exception
from hnv import config as hnv_config
LOG = logging.getLogger(__name__)
CONFIG = hnv_config.CONFIG
class _HNVClient(object):
"""Minimalistic client for the Network Controller REST API.
:param url: The base URL where the agent looks for
Network Controller API.
:param username: The username required for connecting to the
Network Controller API.
:param password: The password required for connecting to the
Network Controller API.
:param allow_insecure: Whether to disable the validation of
HTTPS certificates.
:param ca_bundle: The path to a CA_BUNDLE file or directory
with certificates of trusted CAs.
"""
def __init__(self, url, username=None, password=None, allow_insecure=False,
ca_bundle=None):
self._base_url = url
self._credentials = (username, password)
self._https_allow_insecure = allow_insecure
self._https_ca_bundle = ca_bundle
self._http_session = None
@property
def _session(self):
"""The current session used by the client.
The Session object allows you to persist certain parameters across
requests. It also persists cookies across all requests made from
the Session instance, and will use urllib3's connection pooling.
So if you're making several requests to the same host, the underlying
TCP connection will be reused, which can result in a significant
performance increase.
"""
if self._http_session is None:
self._http_session = requests.Session()
self._http_session.headers.update(self._get_headers())
self._http_session.verify = self._verify_https_request()
if all(self._credentials):
username, password = self._credentials
self._http_session.auth = requests_ntlm.HttpNtlmAuth(
username=username, password=password)
return self._http_session
@staticmethod
def _get_headers():
"""Prepare the HTTP headers for the current request."""
# TODO(alexcoman): Add the x-ms-client-ip-address header in order
# to improve the Network Controller requests logging.
return {
"Accept": "application/json",
"Connection": "keep-alive",
"Content-Type": "application/json; charset=UTF-8",
}
def _verify_https_request(self):
"""Whether to disable the validation of HTTPS certificates.
.. notes::
When `https_allow_insecure` option is `True` the SSL certificate
validation for the connection with the Network Controller API will
be disabled (please don't use it if you don't know the
implications of this behaviour).
"""
if self._https_ca_bundle:
return self._https_ca_bundle
else:
return not self._https_allow_insecure
def _http_request(self, resource, method=constant.GET, body=None,
if_match=False):
if not resource.startswith("http"):
url = requests.compat.urljoin(self._base_url, resource)
else:
url = resource
headers = self._get_headers()
if method in (constant.PUT, constant.PATCH):
if if_match:
etag = (body or {}).get("etag", None)
if etag is not None:
headers["If-Match"] = etag
attemts = 0
while True:
try:
response = self._session.request(
method=method, url=url, headers=headers,
data=json.dumps(body) if body else None,
timeout=CONFIG.HNV.http_request_timeout
)
break
except (requests.ConnectionError,
requests.RequestException) as exc:
attemts += 1
self._http_session = None
LOG.debug("Request failed: %s", exc)
if attemts > CONFIG.HNV.retry_count:
if isinstance(exc, requests.exceptions.SSLError):
raise exception.CertificateVerifyFailed(
"HTTPS certificate validation failed.")
raise
time.sleep(CONFIG.HNV.retry_interval)
try:
response.raise_for_status()
except requests.HTTPError as exc:
status_code = exc.response.status_code
content = exc.response.text
LOG.debug("HTTP Error %(status_code)r: %(details)r",
{"status_code": status_code, "details": content})
if status_code == 400:
raise exception.ServiceException(
("HNV Client failed to communicate with the API. "
"Please open an issue with the following information: "
"%(resource)r: %(details)r"),
resource=resource, details=content
)
if status_code == 404:
raise exception.NotFound(
"Resource %(resource)r was not found.", resource=resource)
raise
return response
def get_resource(self, path):
"""Getting the required information from the API."""
response = self._http_request(path)
try:
return response.json()
except ValueError:
raise exception.ServiceException("Invalid service response.")
def update_resource(self, path, data, if_match=None):
"""Update the required resource."""
response = self._http_request(resource=path, method="PUT", body=data,
if_match=if_match)
try:
return response.json()
except ValueError:
raise exception.ServiceException("Invalid service response.")
def remove_resource(self, path):
"""Delete the received resource."""
return self._http_request(path, method="DELETE")
# pylint: disable=dangerous-default-value
def run_once(function, state={}, errors={}):
"""A memoization decorator, whose purpose is to cache calls."""
@six.wraps(function)
def _wrapper(*args, **kwargs):
if function in errors:
# Deliberate use of LBYL.
six.reraise(*errors[function])
try:
return state[function]
except KeyError:
try:
state[function] = result = function(*args, **kwargs)
return result
except Exception:
errors[function] = sys.exc_info()
raise
return _wrapper
@run_once
def get_client(url, username, password, allow_insecure, ca_bundle):
"""Create a new client for the HNV REST API."""
return _HNVClient(url, username, password, allow_insecure, ca_bundle)