Skip to content

Commit

Permalink
Merge pull request #177 from fmherschel/angi
Browse files Browse the repository at this point in the history
Angi - srHooks now pylinter compatible
  • Loading branch information
angelabriel authored Apr 6, 2023
2 parents 350b7d6 + 9fb6ff8 commit 45041a7
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 189 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/ChecksAndLinters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,14 @@ jobs:
shell: bash
run: |
echo "Running flake8..."
find . -type f -name '*.py' ! -path './test/*' | xargs flake8 --ignore=E501 && echo "Everything is OK!"
continue-on-error: true
find . -type f -name '*.py' ! -path './test/*' | xargs flake8 --ignore=E501,E722 && echo "Everything is OK!"
continue-on-error: false

# Runs pylint
- name: Running pylint
shell: bash
run: |
echo "Running pylint..."
find . -type f -name '*.py' ! -path './test/*' | xargs pylint --disable=line-too-long && echo "Everything is OK!"
continue-on-error: true
find . -type f -name '*.py' ! -path './test/*' | xargs pylint --disable=duplicate-code && echo "Everything is OK!"
continue-on-error: false

30 changes: 17 additions & 13 deletions py/SAPHanaSR.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# SAPHana.py
#
# Description: Gets the overall SR status of the local site to
# a given remote site
# the script is able to work within muti tenyncy database
# installations
# pylint: disable=invalid-name,import-error
"""
SAPHana.py
Description: Gets the overall SR status of the local site to
a given remote site
the script is able to work within muti tenyncy database
installations
"""
#
##############################################################################
#
Expand All @@ -27,19 +30,20 @@
# 2 : fatal - did not got SRs answer
status2Rc = {"ACTIVE": 0, "SYNCING": 1, "INITIALIZING": 1, "UNKNOWN": 1, "ERROR": 1, "FATAL": 2}

print "SR for site: " + remSite
print("SR for site: " + remSite)

srDict = sr.getLandscapeConfiguration(remSite)[0]
for srEntry in srDict:
noAnswer = 0
print srEntry["HOST"] + " / " + str(srEntry["PORT"]) + " / " + srEntry["DATABASE"] + " / " + srEntry["REPLICATION_STATUS"]
msg = (f"{srEntry['HOST']} / {str(srEntry['PORT'])} /"
f" {srEntry['DATABASE']} / srEntry['REPLICATION_STATUS']")
print(msg)
currStatus = status2Rc[srEntry["REPLICATION_STATUS"]]
print "currStatus " + str(currStatus)
if (worstStatus < currStatus):
worstStatus = currStatus
print("currStatus " + str(currStatus))
worstStatus = max(worstStatus, currStatus)

if (noAnswer == 1):
print "No Answer "
if noAnswer == 1:
print("No Answer ")
rc = status2Rc["FATAL"]
else:
rc = worstStatus
Expand Down
164 changes: 102 additions & 62 deletions srHook/susChkSrv.py

Large diffs are not rendered by default.

72 changes: 46 additions & 26 deletions srHook/susCostOpt.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# pylint: disable=invalid-name,fixme,broad-except
"""
# susCostOpt.py
# Authors: Angela Briel, Fabian Herschel, June 2022
Expand All @@ -16,7 +17,8 @@
userKey = costoptkey
execution_order = 1
costopt_primary_global_allocation_limit = limit-in-mb # optional only for special use-cases
costopt_primary_global_allocation_limit = 0 # optional, as of global.ini documentation defines limitation by current resources
costopt_primary_global_allocation_limit = 0 # optional, as of global.ini documentation
defines limitation by current resources
[trace]
ha_dr_suscostopt = info
Expand All @@ -26,68 +28,84 @@

# loading classes and libraries
import os

# pylint: enable=invalid-name
try:
from hdbcli import dbapi
except ImportError as e:
print("Module dbapi not found - install the missing SAP Python Driver 'hdbcli' - {0}".format(e))
print(f"Module dbapi not found - install the missing SAP Python Driver 'hdbcli' - {e}")

try:
from hdb_ha_dr.client import HADRBase
except ImportError as e:
print("Module HADRBase not found - running outside of SAP HANA? - {0}".format(e))
print(f"Module HADRBase not found - running outside of SAP HANA? - {e}")

# parameter section
fhSRHookVersion = "0.160.1"
userkey_dflt = "saphanasr_<sid>_costopt"
FHSRHOOKVERSION = "1.001.1"
USERKEY_DFLT = "saphanasr_<sid>_costopt"
#


# pylint: disable-next=invalid-name
class susCostOpt(HADRBase):
""" class for HADR hook script to handle postTakeover events """

def __init__(self, *args, **kwargs):
# delegate construction to base class
super(susCostOpt, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
method = "init"
mySID = os.environ.get('SAPSYSTEMNAME')
mysid = mySID.lower()
my_sid_upper = os.environ.get('SAPSYSTEMNAME')
mysid = my_sid_upper.lower()

# read settings from global.ini
# read userkey
if self.config.hasKey("userkey"):
self.userkey = self.config.get("userkey")
else:
self.userkey = userkey_dflt.replace("<sid>", mysid)
self.userkey = USERKEY_DFLT.replace("<sid>", mysid)

# read costopt_primary_global_allocation_limit
costopt_primary_global_allocation_limit = 0
primary_global_alloc_limit = 0
if self.config.hasKey("costopt_primary_global_allocation_limit"):
# parameter costopt_primary_global_allocation_limit is set
# so adjust global_allocation_limit to the defined value
costopt_primary_global_allocation_limit = self.config.get("costopt_primary_global_allocation_limit")
self.sql_set_memory = "ALTER SYSTEM ALTER CONFIGURATION ('global.ini','SYSTEM') SET ('memorymanager','global_allocation_limit') = '%s' WITH RECONFIGURE" % (costopt_primary_global_allocation_limit)
primary_global_alloc_limit = self.config.get("costopt_primary_global_allocation_limit")
self.sql_set_memory = ("ALTER SYSTEM ALTER CONFIGURATION ('global.ini','SYSTEM')"
" SET ('memorymanager','global_allocation_limit') ="
f"'{primary_global_alloc_limit}' WITH RECONFIGURE")
else:
# parameter costopt_primary_global_allocation_limit is NOT set
# so just unset global_allocation_limit
self.sql_set_memory = "ALTER SYSTEM ALTER CONFIGURATION ('global.ini','SYSTEM') UNSET ('memorymanager','global_allocation_limit') WITH RECONFIGURE"

self.sql_set_memory = ("ALTER SYSTEM ALTER CONFIGURATION ('global.ini','SYSTEM')"
" UNSET ('memorymanager','global_allocation_limit')"
" WITH RECONFIGURE")
# unset preload_column_tables
self.sql_set_preload = "ALTER SYSTEM ALTER CONFIGURATION ('global.ini','SYSTEM') UNSET ('system_replication','preload_column_tables') WITH RECONFIGURE"
self.sql_set_preload = ("ALTER SYSTEM ALTER CONFIGURATION ('global.ini','SYSTEM')"
" UNSET ('system_replication','preload_column_tables')"
" WITH RECONFIGURE")

self.tracer.info("{0}.{1}() version {2}, userkey {3}, sid {4}, costopt_primary_global_allocation_limit {5}".format(self.__class__.__name__, method, fhSRHookVersion, self.userkey, mysid, costopt_primary_global_allocation_limit))
self.tracer.info(f"{self.__class__.__name__}.{method}() version {FHSRHOOKVERSION},"
f" userkey {self.userkey}, sid {mysid},"
f" primary_global_alloc_limit {primary_global_alloc_limit}")

def about(self):
""" tell something about the HADR hook script """
method = "about"
self.tracer.info("{0}.{1}() version {2}".format(self.__class__.__name__, method, fhSRHookVersion))
self.tracer.info(f"{self.__class__.__name__}.{method}() version {FHSRHOOKVERSION}")
desc = ("postTakeover script to reset parameters to default or set parameters as"
" defined in global.ini.")
return {"provider_company": "SUSE",
"provider_name": "susCostOpt", # class name
"provider_description": "postTakeover script to reset parameters to default or set parameters as defined in global.ini.",
"provider_description": desc,
"provider_version": "1.0"}

# pylint: disable-next=unused-argument, invalid-name
def postTakeover(self, rc, **kwargs):
method = "postTakeover"
"""Post takeover hook."""
self.tracer.info("{0}.{1}() method called with rc={2}".format(self.__class__.__name__, method, rc))
# TODO PRIO4: How to handle return code (rc) not equal to 0 or 1? And to we need to differ rc==0 and rc==1
method = "postTakeover"
self.tracer.info(f"{self.__class__.__name__}.{method}() method called with rc={rc}")
# TODO PRIO4: How to handle return code (rc) not equal to 0 or 1?
# And to we need to differ rc==0 and rc==1
if rc in (0, 1):
# takeover finished with returnocde 0 or 1
# open database connection
Expand All @@ -97,7 +115,7 @@ def postTakeover(self, rc, **kwargs):
# address='localhost',port=dbport,user=dbuser,passwort=dbpwd,
)
except Exception as exerr:
self.tracer.info("error during database connection - {0}.".format(exerr))
self.tracer.info(f"error during database connection - {exerr}.")
return 1

# check, if database connection was successfull
Expand All @@ -107,15 +125,17 @@ def postTakeover(self, rc, **kwargs):

cursor = connection.cursor()
try:
self.tracer.info("sqlstatement: {0}".format(self.sql_set_memory))
self.tracer.info(f"sqlstatement: {self.sql_set_memory}")
cursor.execute(self.sql_set_memory)
except Exception as exerr:
self.tracer.info("error during execution of the sql statement {0} - {1}.".format(self.sql_set_memory, exerr))
self.tracer.info("error during execution of the sql statement"
f" {self.sql_set_memory} - {exerr}.")
try:
self.tracer.info("sqlstatement: {0}".format(self.sql_set_preload))
self.tracer.info(f"sqlstatement: {self.sql_set_preload}")
cursor.execute(self.sql_set_preload)
except Exception as exerr:
self.tracer.info("error during execution of the sql statement {0} - {1}.".format(self.sql_set_preload, exerr))
self.tracer.info("error during execution of the sql statement"
f" {self.sql_set_preload} - {exerr}.")

# commit the changes in the database
connection.commit()
Expand Down
129 changes: 77 additions & 52 deletions srHook/susHanaSR.py
Original file line number Diff line number Diff line change
@@ -1,95 +1,120 @@
# pylint: disable=invalid-name
# remark: avoid pylint to react on module name does't conform to snake_case
"""
# SAPHana
# Author: Fabian Herschel, 2015
# License: GNU General Public License (GPL)
# Copyright: (c) 2015-2016 SUSE Linux GmbH
# Copyright: (c) 2017-2022 SUSE LLC
"""
# pylint: enable=invalid-name
# remark: switch-on now name checking
try:
from hdb_ha_dr.client import HADRBase
except ImportError as e:
print("Module HADRBase not found - running outside of SAP HANA? - {0}".format(e))
print(f"Module HADRBase not found - running outside of SAP HANA? - {e}")
import os

"""
To use this HA/DR hook provide please add the following lines to your global.ini:
[ha_dr_provider_susHanaSR]
provider = susHanaSR
path = /usr/share/SAPHanaSR-angi
execution_order = 1

[trace]
ha_dr_saphanasr = info
"""
fhSRHookVersion = "1.000.0"
# To use this HA/DR hook provide please add the following lines to your global.ini:
# [ha_dr_provider_susHanaSR]
# provider = susHanaSR
# path = /usr/share/SAPHanaSR-angi
# execution_order = 1
#
# [trace]
# ha_dr_saphanasr = info
#
FH_SR_HOOK_VERSION = "1.001.1"


try:
# remark: case style is given by external configuration
# pylint: disable-next=C0103
class susHanaSR(HADRBase):
""" class susHanaSR to handle HADR events for srConnectionChanged """

def __init__(self, *args, **kwargs):
# delegate construction to base class
super(susHanaSR, self).__init__(*args, **kwargs)
self.tracer.info("susHanaSR init()")
""" constructor - delegate construction to base class """
super().__init__(*args, **kwargs)
method = "init"
self.my_sid = os.environ.get('SAPSYSTEMNAME')
self.tracer.info(f"{self.__class__.__name__}.{method}()"
f" version {FH_SR_HOOK_VERSION}")

# pylint: disable-next=no-self-use
def about(self):
""" tell about the HADR hook """
return {"provider_company": "SUSE",
"provider_name": "susHanaSR", # class name
"provider_description": "Inform Cluster about SR state",
"provider_version": "1.0"}

# pylint: disable-next=unused-argument,invalid-name,too-many-locals
def srConnectionChanged(self, ParamDict, **kwargs):
""" finally we got the srConnection hook :) """
""" process srConnectionChanged event """
method = "srConnectionChanged"
self.tracer.info("susHanaSR {0} {1}.srConnectionChanged method called with Dict={2}".format(fhSRHookVersion, self.__class__.__name__, ParamDict))
# myHostname = socket.gethostname()
# myDatebase = ParamDict["database"]
mySystemStatus = ParamDict["system_status"]
mySID = os.environ.get('SAPSYSTEMNAME')
mysid = mySID.lower()
myInSync = ParamDict["is_in_sync"]
myReason = ParamDict["reason"]
mySite = ParamDict["siteName"]
self.tracer.info("susHanaSR {0}.srConnectionChanged mySystemStatus={1} mySID={2} myInSync={3} myReason={4}".format(self.__class__.__name__, mySystemStatus, mySID, myInSync, myReason))
if mySystemStatus == 15:
mySRS = "SOK"
self.tracer.info(f"susHanaSR {FH_SR_HOOK_VERSION}"
f" {self.__class__.__name__}.srConnectionChanged"
f" method called with Dict={ParamDict}")
my_system_status = ParamDict["system_status"]
self.my_sid = os.environ.get('SAPSYSTEMNAME')
mysid_lower = self.my_sid.lower()
my_in_sync = ParamDict["is_in_sync"]
my_reason = ParamDict["reason"]
my_site = ParamDict["siteName"]
self.tracer.info(f"susHanaSR {self.__class__.__name__}.srConnectionChanged"
f" system_status={my_system_status} SID={self.my_sid}"
f" in_sync={my_in_sync} reason={my_reason}")
if my_system_status == 15:
my_srs = "SOK"
else:
if myInSync:
if my_in_sync:
# ignoring the SFAIL, because we are still in sync
self.tracer.info("susHanaSR (%s) %s.srConnectionChanged ignoring bad SR status because of is_in_sync=True (reason=%s)" % (fhSRHookVersion, self.__class__.__name__, myReason))
mySRS = ""
self.tracer.info(f"susHanaSR {FH_SR_HOOK_VERSION}"
f" {self.__class__.__name__}.srConnectionChanged ignoring bad"
f" SR status because of is_in_sync=True (reason={my_reason})")
my_srs = ""
else:
mySRS = "SFAIL"
if mySRS == "":
myMSG = "### Ignoring bad SR status because of is_in_sync=True ###"
self.tracer.info("{0}.{1}() {2}\n".format(self.__class__.__name__, method, myMSG))
elif mySite == "":
myMSG = "### Ignoring bad SR status because of empty site name in call params ###"
self.tracer.info("{0}.{1}() {2}\n".format(self.__class__.__name__, method, myMSG))
my_srs = "SFAIL"
if my_srs == "":
my_msg = "### Ignoring bad SR status because of is_in_sync=True ###"
self.tracer.info(f"{self.__class__.__name__}.{method}() {my_msg}\n")
elif my_site == "":
my_msg = "### Ignoring bad SR status because of empty site name in call params ###"
self.tracer.info(f"{self.__class__.__name__}.{method}() {my_msg}\n")
else:
myCMD = "sudo /usr/sbin/crm_attribute -n hana_%s_site_srHook_%s -v %s -t crm_config -s SAPHanaSR" % (mysid, mySite, mySRS)
rc = os.system(myCMD)
myMSG = "CALLING CRM: <{0}> rc={1}".format(myCMD, rc)
self.tracer.info("{0}.{1}() {2}\n".format(self.__class__.__name__, method, myMSG))
if rc != 0:
my_cmd = ("sudo /usr/sbin/crm_attribute"
f" -n hana_{mysid_lower}_site_srHook_{my_site}"
f" -v {my_srs} -t crm_config -s SAPHanaSR")
ret_code = os.system(my_cmd)
my_msg = f"CALLING CRM: <{my_cmd}> ret_code={ret_code}"
self.tracer.info(f"{self.__class__.__name__}.{method}() {my_msg}\n")
if ret_code != 0:
#
# FALLBACK
# sending attribute to the cluster failed - using fallback method and write status to a file - RA to pick-up the value during next SAPHanaController monitor operation
# sending attribute to the cluster failed - using fallback method and write
# status to a file - RA to pick-up the value during next SAPHanaController
# monitor operation
#
myMSG = "sending attribute to the cluster failed - using local file as fallback"
self.tracer.info("{0}.{1}() {2}\n".format(self.__class__.__name__, method, myMSG))
my_msg = "sending attribute to the cluster failed - using file as fallback"
self.tracer.info(f"{self.__class__.__name__}.{method}() {my_msg}\n")
#
# cwd of hana is /hana/shared/<SID>/HDB00/<hananode> we use a relative path to cwd this gives us a <sid>adm permitted directory
# however we go one level up (..) to have the file accessible for all SAP HANA swarm nodes
# cwd of hana is /hana/shared/<SID>/HDB00/<hananode> we use a relative path
# to cwd this gives us a <sid>adm permitted directory
# however we go one level up (..) to have the file accessible for all
# SAP HANA swarm nodes
#
fallbackFileObject = open("../.crm_attribute.stage.{0}".format(mySite), "w")
fallbackFileObject.write("hana_{0}_site_srHook_{1} = {2}".format(mysid, mySite, mySRS))
fallbackFileObject.close()
stage_file = f"../.crm_attribute.stage.{my_site}"
attribute_name = f"hana_{mysid_lower}_site_srHook_{my_site}"
with open(f"{stage_file}", "w", encoding="UTF-8") as fallback_file_obj:
fallback_file_obj.write(f"{attribute_name} = {my_srs}")
#
# release the stage file to the original name (move is used to be atomic)
# .crm_attribute.stage.<site> is renamed to .crm_attribute.<site>
#
os.rename("../.crm_attribute.stage.{0}".format(mySite), "../.crm_attribute.{0}".format(mySite))
os.rename(f"../.crm_attribute.stage.{my_site}",
f"../.crm_attribute.{my_site}")
return 0
except NameError as e:
print("Could not find base class ({0})".format(e))
print(f"Could not find base class ({e})")
Loading

0 comments on commit 45041a7

Please sign in to comment.