File: //lib/python3.6/site-packages/agent/controller/__init__.py
# Copyright (c) 2021-2023 Alibaba Cloud Computing Ltd.
# SPDX-License-Identifier: MulanPSL-2.0
import logging
import os
import re
from importlib import import_module
import agent # ! unused import for dynamic loading
from agent import domain, feature
logger = logging.getLogger("keentune")
class Singleton(object):
def __init__(self, cls):
self._cls = cls
self._instance = {}
def __call__(self):
if self._cls not in self._instance:
self._instance[self._cls] = self._cls()
return self._instance[self._cls]
@Singleton
class Context(object):
_pattern_ = re.compile(r"class (.*)\((Feature|BaseDomain|BaseBenchmark|FileConfig|Nginx)\)")
def __init__(self):
self.debug = False
self.domain_class = {}
self.feature_class = {}
self.domain_object = {}
self.feature_object = {}
self.loadObject()
self.loadActiveParams()
def _debug(self, debug):
self.debug = debug
def loadObject(self):
"""Try to parse domain & feature file to classes"""
logger.debug(f"Load feature classes from {os.path.dirname(feature.__file__)}")
for file_path in os.listdir(os.path.dirname(feature.__file__)):
file_path = os.path.join(os.path.dirname(feature.__file__), file_path)
obj = self.__loadObjectImpl(file_path)
if obj is not None:
self.feature_class["{}:{}".format(obj.__domain__, obj.__feature__)] = obj
logger.debug(f"Find {len(self.feature_class)} parameter feature: {list(self.feature_class.keys())}")
logger.debug(f"Load domain classes from {os.path.dirname(domain.__file__)}")
for file_path in os.listdir(os.path.dirname(domain.__file__)):
file_path = os.path.join(os.path.dirname(domain.__file__), file_path)
obj = self.__loadObjectImpl(file_path)
if obj is not None:
self.domain_class["{}".format(obj.__domain__)] = obj
logger.debug(f"Find {len(self.domain_class)} parameter domain: {list(self.domain_class.keys())}")
def __loadObjectImpl(self, file_path):
"""Parse file content to checkout domain & feature class"""
# skip dir
if os.path.isdir(file_path):
return None
with open(file_path) as f:
content = f.read()
try:
class_name = re.search(self._pattern_, content).group(1)
assert re.match("^\w+$", class_name)
except Exception:
return None
folder_name = os.path.split(file_path)[0].split("/")[-1]
file_name = os.path.split(file_path)[1].split(".")[0]
try:
assert re.match("^\w+$", folder_name)
assert re.match("^\w+$", file_name)
import_module("agent.{}.{}".format(folder_name, file_name))
except Exception as e:
logger.warning(f"failed to import feature file {file_name}: {e}")
return None
else:
_object = eval("agent.{}.{}.{}".format(folder_name, file_name, class_name))
return _object
def loadActiveParams(self):
"""Check backup data and init actived domain/feature"""
for feature_name, feature_obj in self.feature_class.items():
if feature_obj.isActived():
logger.info(f"load actived feature: {feature_name}")
try:
self.feature_object[feature_name] = feature_obj()
except Exception as e:
logger.warning(f"failed to load actived feature {feature_name}: {e}")
for domain_name, domain_obj in self.domain_class.items():
if domain_obj.isActived():
logger.info(f"load actived domain: {domain_name}")
try:
self.domain_object[domain_name] = domain_obj()
except Exception as e:
logger.warning(f"failed to load actived domain {domain_name}: {e}")
def searchFeatureHandler(self, domain_name, feature_name):
"""A feature handler is wanted, init and return feature object
Raise:
Exception: object init failed
"""
feature_name = "{}:{}".format(domain_name, feature_name)
if not self.feature_class.__contains__(feature_name):
return None
if not self.feature_object.__contains__(feature_name):
self.feature_object[feature_name] = self.feature_class[feature_name]()
return self.feature_object[feature_name]
def searchDomainHandler(self, domain_name):
from agent.common.exception import InitException
"""A domain handler is wanted, init and return domain object
Raise:
Exception: object init failed
"""
if not self.domain_class.__contains__(domain_name):
#! Different with feature, domain is a guaranteed handler, raise error if miss
raise InitException("can not find domain:{}".format(domain_name))
if not self.domain_object.__contains__(domain_name):
self.domain_object[domain_name] = self.domain_class[domain_name]()
return self.domain_object[domain_name]
def rollback(self):
rollback_result = {}
for domain_name in list(self.domain_object.keys()):
domain_handler = self.domain_object[domain_name]
try:
logger.info(f"start rollback domain {domain_name}")
domain_handler.rollback()
domain_handler.post_rollback()
except Exception as e:
logger.warning(f"rollback {domain_name} failed: {e}")
rollback_result[domain_name] = f"{e}"
else:
logger.info(f"rollback domain {domain_name} done.")
self.domain_object.pop(domain_name)
sorted_feature_list = sorted(
[
(feature_handler.__priority__, feature_name, feature_handler)
for feature_name, feature_handler in self.feature_object.items()
],
key=lambda x: x[0],
reverse=True,
)
for _, feature_name, feature_handler in sorted_feature_list:
try:
logger.info(f"start rollback feature {feature_name}")
feature_handler.rollback()
except Exception as e:
logger.warning(f"rollback {feature_name} failed: {e}")
rollback_result[feature_name] = "{}".format(e)
else:
logger.info(f"rollback feature {feature_name} done.")
self.feature_object.pop(feature_name)
return rollback_result