From c4875f4cf4dab2841ac1a138ed3b564cd827a77c Mon Sep 17 00:00:00 2001 From: Guillaume Abrioux Date: Wed, 25 Oct 2023 15:07:09 +0000 Subject: [PATCH] node-proxy: implement http_query() helper function so we can drop the dependency to `requests` and use same helper function from both reporter.py and redfish_client.py Signed-off-by: Guillaume Abrioux --- src/cephadm/cephadmlib/node_proxy/main.py | 5 +- .../cephadmlib/node_proxy/redfish_client.py | 52 +++++++++---------- src/cephadm/cephadmlib/node_proxy/reporter.py | 39 ++++++++++---- src/cephadm/cephadmlib/node_proxy/util.py | 40 +++++++++++++- 4 files changed, 97 insertions(+), 39 deletions(-) diff --git a/src/cephadm/cephadmlib/node_proxy/main.py b/src/cephadm/cephadmlib/node_proxy/main.py index 82f941a085125..e9c99f0983dcb 100644 --- a/src/cephadm/cephadmlib/node_proxy/main.py +++ b/src/cephadm/cephadmlib/node_proxy/main.py @@ -219,7 +219,10 @@ class NodeProxy(Thread): try: self.reporter_agent = Reporter(self.system, self.__dict__['cephx'], - f"https://{self.__dict__['mgr_target_ip']}:{self.__dict__['mgr_target_port']}/node-proxy/data") + reporter_scheme=self.__dict__.get('reporter_scheme', 'https'), + reporter_hostname=self.__dict__['mgr_target_ip'], + reporter_port=self.__dict__['mgr_target_port'], + reporter_endpoint=self.__dict__.get('reporter_endpoint', '/node-proxy/data')) except RuntimeError: self.log.logger.error("Can't initialize the reporter.") raise diff --git a/src/cephadm/cephadmlib/node_proxy/redfish_client.py b/src/cephadm/cephadmlib/node_proxy/redfish_client.py index 3046a634c58e5..4107fb57c8f09 100644 --- a/src/cephadm/cephadmlib/node_proxy/redfish_client.py +++ b/src/cephadm/cephadmlib/node_proxy/redfish_client.py @@ -1,10 +1,9 @@ -import ssl import json -from urllib.error import URLError -from urllib.request import urlopen, Request +from urllib.error import HTTPError, URLError from .baseclient import BaseClient -from .util import Logger +from .util import Logger, http_req from typing import Dict, Any, Tuple, Optional +from http.client import HTTPMessage class RedFishClient(BaseClient): @@ -18,14 +17,16 @@ class RedFishClient(BaseClient): super().__init__(host, username, password) self.log: Logger = Logger(__name__) self.log.logger.info(f"Initializing redfish client {__name__}") - self.host: str = f"https://{host}:{str(port)}" + self.host: str = host + self.port: int = port + self.url: str = f"https://{self.host}:{self.port}" self.token: str = '' self.location: str = '' def login(self) -> None: if not self.is_logged_in(): self.log.logger.info("Logging in to " - f"{self.host} as '{self.username}'") + f"{self.url} as '{self.username}'") idrac_credentials = json.dumps({"UserName": self.username, "Password": self.password}) headers = {"Content-Type": "application/json"} @@ -35,19 +36,19 @@ class RedFishClient(BaseClient): headers=headers, endpoint='/redfish/v1/SessionService/Sessions/') if _status_code != 201: - self.log.logger.error(f"Can't log in to {self.host} as '{self.username}': {_status_code}") + self.log.logger.error(f"Can't log in to {self.url} as '{self.username}': {_status_code}") raise RuntimeError except URLError as e: - msg = f"Can't log in to {self.host} as '{self.username}': {e}" + msg = f"Can't log in to {self.url} as '{self.username}': {e}" self.log.logger.error(msg) raise RuntimeError self.token = _headers['X-Auth-Token'] self.location = _headers['Location'] def is_logged_in(self) -> bool: - self.log.logger.debug(f"Checking token validity for {self.host}") + self.log.logger.debug(f"Checking token validity for {self.url}") if not self.location or not self.token: - self.log.logger.debug(f"No token found for {self.host}.") + self.log.logger.debug(f"No token found for {self.url}.") return False headers = {"X-Auth-Token": self.token} try: @@ -55,7 +56,7 @@ class RedFishClient(BaseClient): endpoint=self.location) except URLError as e: self.log.logger.error("Can't check token " - f"validity for {self.host}: {e}") + f"validity for {self.url}: {e}") raise RuntimeError return _status_code == 200 @@ -65,7 +66,7 @@ class RedFishClient(BaseClient): headers={"X-Auth-Token": self.token}, endpoint=self.location) except URLError: - self.log.logger.error(f"Can't log out from {self.host}") + self.log.logger.error(f"Can't log out from {self.url}") return {} response_str = _data @@ -88,25 +89,24 @@ class RedFishClient(BaseClient): headers: Dict[str, str] = {}, method: Optional[str] = None, endpoint: str = '', - timeout: int = 10) -> Tuple[Dict[str, str], str, int]: - url = f'{self.host}{endpoint}' + timeout: int = 10) -> Tuple[HTTPMessage, str, int]: _headers = headers.copy() if headers else {} if self.token: _headers['X-Auth-Token'] = self.token if not _headers.get('Content-Type') and method in ['POST', 'PUT', 'PATCH']: _headers['Content-Type'] = 'application/json' - # ssl_ctx = ssl.create_default_context() - # ssl_ctx.check_hostname = True - # ssl_ctx.verify_mode = ssl.CERT_REQUIRED - ssl_ctx = ssl._create_unverified_context() - _data = bytes(data, 'ascii') if data else None try: - req = Request(url, _data, headers=_headers, method=method) - with urlopen(req, context=ssl_ctx, timeout=timeout) as response: - response_str = response.read() - response_headers = response.headers - except URLError as e: + (response_headers, + response_str, + response_status) = http_req(hostname=self.host, + port=self.port, + endpoint=endpoint, + headers=_headers, + method=method, + data=data, + timeout=timeout) + + return response_headers, response_str, response_status + except (HTTPError, URLError) as e: self.log.logger.debug(f"{e}") raise - - return response_headers, response_str, response.status diff --git a/src/cephadm/cephadmlib/node_proxy/reporter.py b/src/cephadm/cephadmlib/node_proxy/reporter.py index 5089dd1319798..aa0ecdf93f987 100644 --- a/src/cephadm/cephadmlib/node_proxy/reporter.py +++ b/src/cephadm/cephadmlib/node_proxy/reporter.py @@ -1,18 +1,32 @@ from threading import Thread -import requests import time -from .util import Logger +import json +from .util import Logger, http_req +from urllib.error import HTTPError, URLError from typing import Dict, Any class Reporter: - def __init__(self, system: Any, cephx: Dict[str, Any], observer_url: str) -> None: + def __init__(self, + system: Any, + cephx: Dict[str, Any], + reporter_scheme: str = 'https', + reporter_hostname: str = '', + reporter_port: int = 443, + reporter_endpoint: str = '/node-proxy/data') -> None: self.system = system - self.observer_url = observer_url + self.data: Dict[str, Any] = {} self.finish = False self.cephx = cephx + self.data['cephx'] = self.cephx + self.reporter_scheme: str = reporter_scheme + self.reporter_hostname: str = reporter_hostname + self.reporter_port: int = reporter_port + self.reporter_endpoint: str = reporter_endpoint self.log = Logger(__name__) - self.log.logger.info(f'Observer url set to {self.observer_url}') + self.reporter_url: str = (f"{reporter_scheme}:{reporter_hostname}:" + f"{reporter_port}{reporter_endpoint}") + self.log.logger.info(f'Reporter url set to {self.reporter_url}') def stop(self) -> None: self.finish = True @@ -36,15 +50,18 @@ class Reporter: self.log.logger.info('data ready to be sent to the mgr.') if not self.system.get_system() == self.system.previous_data: self.log.logger.info('data has changed since last iteration.') - self.data = {} - self.data['cephx'] = self.cephx self.data['patch'] = self.system.get_system() try: # TODO: add a timeout parameter to the reporter in the config file - self.log.logger.info(f"sending data to {self.observer_url}") - r = requests.post(f"{self.observer_url}", json=self.data, timeout=5, verify=False) - except (requests.exceptions.RequestException, - requests.exceptions.ConnectionError) as e: + self.log.logger.info(f"sending data to {self.reporter_url}") + http_req(hostname=self.reporter_hostname, + port=self.reporter_port, + method='POST', + headers={'Content-Type': 'application/json'}, + endpoint=self.reporter_endpoint, + scheme=self.reporter_scheme, + data=json.dumps(self.data)) + except (HTTPError, URLError) as e: self.log.logger.error(f"The reporter couldn't send data to the mgr: {e}") # Need to add a new parameter 'max_retries' to the reporter if it can't # send the data for more than x times, maybe the daemon should stop altogether diff --git a/src/cephadm/cephadmlib/node_proxy/util.py b/src/cephadm/cephadmlib/node_proxy/util.py index 40b01f0c7640f..da46ebabda0dd 100644 --- a/src/cephadm/cephadmlib/node_proxy/util.py +++ b/src/cephadm/cephadmlib/node_proxy/util.py @@ -3,7 +3,10 @@ import yaml import os import time import re -from typing import Dict, List, Callable, Any +import ssl +from urllib.error import HTTPError, URLError +from urllib.request import urlopen, Request +from typing import Dict, List, Callable, Any, Optional, MutableMapping, Tuple class Logger: @@ -98,3 +101,38 @@ def retry(exceptions: Any = Exception, retries: int = 20, delay: int = 1) -> Cal return f(*args, **kwargs) return _retry return decorator + + +def http_req(hostname: str = '', + port: int = 443, + method: Optional[str] = None, + headers: MutableMapping[str, str] = {}, + data: Optional[str] = None, + endpoint: str = '/', + scheme: str = 'https', + ssl_verify: bool = False, + timeout: Optional[int] = None, + ssl_ctx: Optional[Any] = None) -> Tuple[Any, Any, Any]: + + if not ssl_ctx: + ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + if not ssl_verify: + ssl_ctx.check_hostname = False + ssl_ctx.verify_mode = ssl.CERT_NONE + else: + ssl_ctx.verify_mode = ssl.CERT_REQUIRED + + url: str = f'{scheme}://{hostname}:{str(port)}{endpoint}' + _data = bytes(data, 'ascii') if data else None + + try: + req = Request(url, _data, headers, method=method) + with urlopen(req, context=ssl_ctx, timeout=timeout) as response: + response_str = response.read() + response_headers = response.headers + response_code = response.code + return response_headers, response_str.decode(), response_code + except (HTTPError, URLError) as e: + print(f'{e}') + # handle error here if needed + raise -- 2.39.5