Skip to content

Commit

Permalink
fix: add warnings when deleting groups or profiles which are configur…
Browse files Browse the repository at this point in the history
…ed in the inventory. No frequency configuration for walk profiles. Default number of presented items changed to 20.

fix: change message while deleting profiles. Change what is displayed in Frequency column for walk profiles

fix: update tests

fix: remove unnecessary print

fix: improve logging in handle_changes.py, update config_collection in job insted of in CheckIfPreviousJobFailed

fix: fix error in celery job

fix: update request message in apply_changes

fix: typo in return message

fix: fix problem with editing walk profile

fix: refactor files and write config from mongo to yaml files on host machine

fix: change message in error thrown by SaveConfigToFileHandler
  • Loading branch information
wojtekzyla committed Jul 25, 2023
1 parent fbea3ab commit 8a6cb01
Show file tree
Hide file tree
Showing 26 changed files with 658 additions and 316 deletions.
2 changes: 2 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ RUN chmod +x /flask_start.sh
COPY ./celery_start.sh /celery_start.sh
RUN chmod +x /celery_start.sh

USER 10000:10000

EXPOSE 5000
CMD ["gunicorn", "-b", ":5000", "app:flask_app", "--log-level", "INFO"]
69 changes: 69 additions & 0 deletions backend/SC4SNMP_UI_backend/apply_changes/apply_changes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from threading import Lock
import os
from SC4SNMP_UI_backend import mongo_client
from SC4SNMP_UI_backend.apply_changes.handling_chain import CheckJobHandler, ScheduleHandler, SaveConfigToFileHandler
from SC4SNMP_UI_backend.apply_changes.config_to_yaml_utils import ProfilesToYamlDictConversion, ProfilesTempHandling, \
GroupsToYamlDictConversion, GroupsTempHandling, InventoryToYamlDictConversion, InventoryTempHandling


MONGO_URI = os.getenv("MONGO_URI")
JOB_CREATION_RETRIES = int(os.getenv("JOB_CREATION_RETRIES", 10))
mongo_config_collection = mongo_client.sc4snmp.config_collection
mongo_groups = mongo_client.sc4snmp.groups_ui
mongo_inventory = mongo_client.sc4snmp.inventory_ui
mongo_profiles = mongo_client.sc4snmp.profiles_ui



class SingletonMeta(type):
_instances = {}
_lock: Lock = Lock()

def __call__(cls, *args, **kwargs):
with cls._lock:
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]

class ApplyChanges(metaclass=SingletonMeta):
def __init__(self) -> None:
"""
ApplyChanges is a singleton responsible for creating mongo record with a current state of kubernetes job.
Structure of the record:
{
"previous_job_start_time": datetime.datetime or None if job hasn't been scheduled yet,
"currently_scheduled": bool
}
"""
self.__handling_chain = SaveConfigToFileHandler()
check_job_handler = CheckJobHandler()
schedule_handler = ScheduleHandler()
self.__handling_chain.set_next(check_job_handler).set_next(schedule_handler)
mongo_config_collection.update_one(
{
"previous_job_start_time": {"$exists": True},
"currently_scheduled": {"$exists": True}}
,{
"$set":{
"previous_job_start_time": None,
"currently_scheduled": False
}
},
upsert=True
)


def apply_changes(self):
"""
Run chain of actions to schedule new kubernetes job.
"""
yaml_sections = {
"scheduler.groups": (mongo_groups, GroupsToYamlDictConversion, GroupsTempHandling),
"scheduler.profiles": (mongo_profiles, ProfilesToYamlDictConversion, ProfilesTempHandling),
"poller.inventory": (mongo_inventory, InventoryToYamlDictConversion, InventoryTempHandling)
}
return self.__handling_chain.handle({
"yaml_sections": yaml_sections
})

180 changes: 180 additions & 0 deletions backend/SC4SNMP_UI_backend/apply_changes/config_to_yaml_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
from abc import abstractmethod
import ruamel
from ruamel.yaml.scalarstring import SingleQuotedScalarString as single_quote
from ruamel.yaml.scalarstring import DoubleQuotedScalarString as double_quote
from SC4SNMP_UI_backend.common.backend_ui_conversions import get_group_or_profile_name_from_backend
from ruamel.yaml.scalarstring import LiteralScalarString as literal_string
import os
from flask import current_app


def bool_to_str(value):
if value:
return "t"
else:
return "f"


class MongoToYamlDictConversion:
@classmethod
def yaml_escape_list(cls, *l):
ret = ruamel.yaml.comments.CommentedSeq(l)
ret.fa.set_flow_style()
return ret
@abstractmethod
def convert(self, documents: list) -> dict:
pass


class ProfilesToYamlDictConversion(MongoToYamlDictConversion):
def convert(self, documents: list) -> dict:
result = {}
for profile in documents:
profile_name = get_group_or_profile_name_from_backend(profile)
prof = profile[profile_name]
var_binds = []
condition = None
conditions = None
is_walk_profile = False

for var_bind in prof["varBinds"]:
var_binds.append(self.yaml_escape_list(*[single_quote(vb) for vb in var_bind]))

if "condition" in prof:
backend_condition = prof["condition"]
condition_type = backend_condition["type"]
is_walk_profile = True if backend_condition["type"] == "walk" else False
condition = {
"type": condition_type
}
if condition_type == "field":
condition["field"] = backend_condition["field"]
condition["patterns"] = [single_quote(pattern) for pattern in backend_condition["patterns"]]

if "conditions" in prof:
backend_conditions = prof["conditions"]
conditions = []
for cond in backend_conditions:
if cond["operation"] == "in":
value = [double_quote(v) if type(v) == str else v for v in cond["value"]]
else:
value = double_quote(cond["value"]) if type(cond["value"]) == str else cond["value"]
conditions.append({
"field": cond["field"],
"operation": double_quote(cond["operation"]),
"value": value
})

result[profile_name] = {}
if not is_walk_profile:
result[profile_name]["frequency"] = prof['frequency']
if condition is not None:
result[profile_name]["condition"] = condition
if conditions is not None:
result[profile_name]["conditions"] = conditions
result[profile_name]["varBinds"] = var_binds

return result


class GroupsToYamlDictConversion(MongoToYamlDictConversion):
def convert(self, documents: list) -> dict:
result = {}
for group in documents:
group_name = get_group_or_profile_name_from_backend(group)
gr = group[group_name]
hosts = []
for host in gr:
host_config = host
if "community" in host:
host_config["community"] = single_quote(host["community"])
if "secret" in host:
host_config["secret"] = single_quote(host["secret"])
if "version" in host:
host_config["version"] = single_quote(host["version"])
hosts.append(host_config)
result[group_name] = hosts
return result


class InventoryToYamlDictConversion(MongoToYamlDictConversion):
def convert(self, documents: list) -> dict:
inventory_string = "address,port,version,community,secret,security_engine,walk_interval,profiles,smart_profiles,delete"
for inv in documents:
smart_profiles = bool_to_str(inv['smart_profiles'])
inv_delete = bool_to_str(inv['delete'])
inventory_string += f"\n{inv['address']},{inv['port']},{inv['version']},{inv['community']}," \
f"{inv['secret']},{inv['security_engine']},{inv['walk_interval']},{inv['profiles']}," \
f"{smart_profiles},{inv_delete}"
return {
"inventory": literal_string(inventory_string)
}


class TempFileHandling:
def __init__(self, file_path: str):
self._file_path = file_path

def _save_temp(self, content):
yaml = ruamel.yaml.YAML()
with open(self._file_path, "w") as file:
yaml.dump(content, file)

def _delete_temp(self):
if os.path.exists(self._file_path):
os.remove(self._file_path)
else:
current_app.logger.info(f"Pod directory {self._file_path} doesn't exist. File wasn't removed.")

@abstractmethod
def parse_dict_to_yaml(self, document: dict, delete_tmp: bool = True):
pass


class ProfilesTempHandling(TempFileHandling):
def __init__(self, file_path: str):
super().__init__(file_path)

def parse_dict_to_yaml(self, document: dict, delete_tmp: bool = True):
self._save_temp(document)
lines = ""
with open(self._file_path, "r") as file:
line = file.readline()
while line != "":
lines += f"{line}"
line = file.readline()
if delete_tmp:
self._delete_temp()
return literal_string(lines)


class GroupsTempHandling(TempFileHandling):
def __init__(self, file_path: str):
super().__init__(file_path)

def parse_dict_to_yaml(self, document: dict, delete_tmp: bool = True):
self._save_temp(document)
lines = ""
with open(self._file_path, "r") as file:
line = file.readline()
while line != "":
lines += f"{line}"
line = file.readline()
if delete_tmp:
self._delete_temp()
return literal_string(lines)


class InventoryTempHandling(TempFileHandling):
def __init__(self, file_path: str):
super().__init__(file_path)

def parse_dict_to_yaml(self, document: dict, delete_tmp: bool = True):
self._save_temp(document)
yaml = ruamel.yaml.YAML()
with open(self._file_path, "r") as file:
inventory = yaml.load(file)
result = inventory["inventory"]
if delete_tmp:
self._delete_temp()
return literal_string(result)
Loading

0 comments on commit 8a6cb01

Please sign in to comment.