You've already forked mariadb-columnstore-engine
mirror of
https://github.com/mariadb-corporation/mariadb-columnstore-engine.git
synced 2025-08-01 06:46:55 +03:00
[add] LOCALHOSTS tuple to mcs-savebrm script [fix] detecting primary when no working CMAPI found
234 lines
7.1 KiB
Python
Executable File
234 lines
7.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import configparser
|
|
import fcntl
|
|
import json
|
|
import logging
|
|
import os
|
|
import socket
|
|
import ssl
|
|
import struct
|
|
import subprocess
|
|
import sys
|
|
import xml.etree.ElementTree as ET
|
|
from urllib.request import Request, urlopen
|
|
from urllib.error import HTTPError, URLError
|
|
|
|
|
|
MCS_SYSCONF_DIR = '@ENGINE_SYSCONFDIR@'
|
|
MCS_ETC_PATH = os.path.join(MCS_SYSCONF_DIR, 'columnstore')
|
|
CMAPI_CONFIG_PATH = os.path.join(MCS_ETC_PATH, 'cmapi_server.conf')
|
|
MCS_CONFIG_PATH = os.path.join(MCS_ETC_PATH, 'Columnstore.xml')
|
|
SM_CONFIG_PATH = os.path.join(MCS_ETC_PATH, 'storagemanager.cnf')
|
|
MCS_BIN_DIR = '@ENGINE_BINDIR@'
|
|
SAVEBRM = os.path.join(MCS_BIN_DIR, 'save_brm')
|
|
HALF_A_MINUTE = 30
|
|
LOCALHOST = '127.0.0.1'
|
|
# according to https://www.ibm.com/docs/en/storage-sentinel/1.1.2?topic=installation-map-your-local-host-loopback-address
|
|
LOCALHOSTS = (
|
|
'127.0.0.1',
|
|
'localhost', 'localhost.localdomain',
|
|
'localhost4', 'localhost4.localdomain4',
|
|
'::1',
|
|
'localhost6', 'localhost6.localdomain6',
|
|
)
|
|
API_VERSION = '0.4.0'
|
|
API_PORT = '8640'
|
|
|
|
|
|
def get_api_key():
|
|
"""Get API key from cmapi config file.
|
|
|
|
:return: return api key
|
|
:rtype: str
|
|
"""
|
|
cmapi_config = configparser.ConfigParser()
|
|
cmapi_config.read(CMAPI_CONFIG_PATH)
|
|
# dequote?
|
|
return cmapi_config.get('Authentication', 'x-api-key', fallback='')
|
|
|
|
|
|
def get_unverified_context():
|
|
ctx = ssl.create_default_context()
|
|
ctx.check_hostname = False
|
|
ctx.verify_mode = ssl.CERT_NONE
|
|
return ctx
|
|
|
|
|
|
def cmapi_available():
|
|
"""Check if CMAPI server is running.
|
|
|
|
:return: is CMAPI running or not
|
|
:rtype: bool
|
|
"""
|
|
logging.debug('Detecting CMAPI is up and running.')
|
|
url = 'https://{}:{}/notfound'.format(LOCALHOST, API_PORT)
|
|
request = Request(method='POST', url=url)
|
|
ctx = get_unverified_context()
|
|
try:
|
|
with urlopen(request, context=ctx, timeout=HALF_A_MINUTE) as req:
|
|
_ = req.read().decode('utf-8')
|
|
except HTTPError as exc:
|
|
if exc.code == 404:
|
|
return True
|
|
except URLError:
|
|
logging.info('CMAPI seems to be unavailable.')
|
|
except Exception:
|
|
logging.error(
|
|
'Undefined error while trying to check CMAPI availabale.',
|
|
exc_info=True
|
|
)
|
|
return False
|
|
|
|
|
|
def get_ip_address_by_nic(ifname):
|
|
"""Get ip address for nic.
|
|
|
|
:param ifname: network intarface name
|
|
:type ifname: str
|
|
:return: ip address
|
|
:rtype: str
|
|
"""
|
|
# doesn't work on windows,
|
|
# OpenBSD and probably doesn't on FreeBSD/pfSense either
|
|
ip_addr = ''
|
|
try:
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
ip_addr = socket.inet_ntoa(
|
|
fcntl.ioctl(
|
|
s.fileno(),
|
|
0x8915, # SIOCGIFADDR "socket ioctl get interface address"
|
|
struct.pack('256s', bytes(ifname[:15], 'utf-8'))
|
|
)[20:24]
|
|
)
|
|
except Exception as exc:
|
|
logging.debug(
|
|
'Exception while getting IP address of an "{}" interface'.format(
|
|
ifname
|
|
),
|
|
exc_info=True
|
|
)
|
|
finally:
|
|
s.close()
|
|
return ip_addr
|
|
|
|
|
|
def is_primary_fallback(current_hostname):
|
|
"""Detect is node primary or not without cmapi.
|
|
|
|
:param current_hostname: hostname or FQDN
|
|
:type current_hostname: str
|
|
:return: is node primary
|
|
:rtype: bool
|
|
"""
|
|
logging.debug(
|
|
'Current DBRM_Controller/IPAddr is {}'.format(current_hostname)
|
|
)
|
|
hostnames = set()
|
|
for _, nic_name in socket.if_nameindex():
|
|
ip_addr = get_ip_address_by_nic(nic_name)
|
|
try:
|
|
hostnames_3tuple = socket.gethostbyaddr(ip_addr)
|
|
hostnames.update([hostnames_3tuple[0], *hostnames_3tuple[1]])
|
|
except:
|
|
pass
|
|
logging.debug('Found hostnames {}.'.format(hostnames))
|
|
return current_hostname in LOCALHOSTS or current_hostname in hostnames
|
|
|
|
|
|
def is_node_primary(conf_root):
|
|
"""Detect is current node primary or not.
|
|
|
|
:param conf_root: xml config root element
|
|
:type conf_root: xml.etree.ElementTree.ElementTree
|
|
:return: primary or not
|
|
:rtype: bool
|
|
"""
|
|
if cmapi_available():
|
|
url = 'https://{}:{}/cmapi/{}/node/primary'.format(
|
|
LOCALHOST, API_PORT, API_VERSION
|
|
)
|
|
ctx = get_unverified_context()
|
|
# Actually for this endpoint no need to provide api key cause it's
|
|
# not required
|
|
request = Request(
|
|
method='GET', url=url, headers={'x-api-key': get_api_key()}
|
|
)
|
|
|
|
success = False
|
|
try:
|
|
with urlopen(request, context=ctx, timeout=HALF_A_MINUTE) as req:
|
|
response = req.read()
|
|
success = True
|
|
except HTTPError as exc:
|
|
logging.warning(
|
|
'Something goes wrong while requesting primary status ',
|
|
'through api.',
|
|
'Got response code "{}" with reason "{}".'.format(
|
|
exc.code, exc.reason
|
|
)
|
|
)
|
|
except URLError:
|
|
logging.warning(
|
|
'CMAPI became unavailable while trying',
|
|
'to request primary status.'
|
|
)
|
|
except Exception:
|
|
logging.error(
|
|
'Undefined exception while trying to request primary status.',
|
|
exc_info=True
|
|
)
|
|
|
|
if success:
|
|
dict_response = json.loads(response.decode('utf-8'))
|
|
is_primary = dict_response.get('is_primary', False)
|
|
if is_primary and is_primary in ('True', 'true'):
|
|
is_primary = True
|
|
else:
|
|
is_primary = False
|
|
return is_primary
|
|
|
|
logging.info('Trying to detect primary without cmapi running.')
|
|
# text in tag have to be hostname or FQDN
|
|
return is_primary_fallback(conf_root.find('./DBRM_Controller/IPAddr').text)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
master_addr = ''
|
|
pm_count = 0
|
|
logging.basicConfig(
|
|
format='%(levelname)s: %(message)s', level=logging.DEBUG
|
|
)
|
|
logging.debug('Loading Columnstore.xml.')
|
|
try:
|
|
cs_config = ET.parse(MCS_CONFIG_PATH)
|
|
config_root = cs_config.getroot()
|
|
master_addr = config_root.find('./DBRM_Controller/IPAddr').text
|
|
pm_count = int(
|
|
config_root.find('./SystemModuleConfig/ModuleCount3').text
|
|
)
|
|
logging.debug('Succesfully loaded Columnstore.xml.')
|
|
except (FileNotFoundError, AttributeError, ValueError) as e:
|
|
# is it correct case?
|
|
logging.error(
|
|
'Exception while loading Columnstore.xml. Continue anyway.',
|
|
exc_info=True
|
|
)
|
|
|
|
logging.debug('Reading SM config.')
|
|
sm_config = configparser.ConfigParser()
|
|
files_read = len(sm_config.read(SM_CONFIG_PATH))
|
|
storage = sm_config.get(
|
|
'ObjectStorage', 'service', fallback='LocalStorage'
|
|
)
|
|
|
|
if is_node_primary(config_root):
|
|
try:
|
|
retcode = subprocess.check_call(SAVEBRM, shell=True)
|
|
except subprocess.CalledProcessError as exc:
|
|
logging.error('{} exits with {}.'.format(exc.cmd, exc.returncode))
|
|
sys.exit(1)
|
|
except OSError:
|
|
logging.error('Os error while calling savebrm', exc_info=True)
|
|
sys.exit(0)
|
|
sys.exit(0)
|