Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

module enhancement #38

Merged
merged 1 commit into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,4 @@ cython_debug/
data.yaml
config.yaml
.ceph
.k8s
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ fabric==3.2.2
google-auth==2.28.1
idna==3.6
invoke==2.2.0
Jinja2==3.1.3
kubernetes==29.0.0
MarkupSafe==2.1.5
oauthlib==3.2.2
paramiko==3.4.0
pyasn1==0.5.1
Expand Down
10 changes: 8 additions & 2 deletions src/config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@ ssh:
user: dragon

kubernetes:
host: 192.168.22.10
api_key: abc
config: ../.k8s/config

rook:
cluster:
name: osism-ceph
namespace: rook-ceph
ceph:
image: quay.io/ceph/ceph:v18.2.1

migration_modules:
- migrate_osds
Expand Down
22 changes: 18 additions & 4 deletions src/rookify/__main__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-

import os
import rookify.modules

from types import MappingProxyType
Expand All @@ -21,21 +22,34 @@ def main() -> None:
except FileNotFoundError:
pass

# Get absolute path of the rookify instance
rookify_path = os.path.dirname(__file__)

# Run preflight requirement modules
for preflight_module in preflight_modules:
module_path = os.path.join(
rookify_path, "modules", preflight_module.MODULE_NAME
)
handler = preflight_module.HANDLER_CLASS(
config=MappingProxyType(config), data=MappingProxyType(module_data)
config=MappingProxyType(config),
data=MappingProxyType(module_data),
module_path=module_path,
)
result = handler.run()
module_data[preflight_module.MODULE_NAME] = result

# Run preflight checks and append handlers to list
# Run preflight and append handlers to list
handlers = list()
for migration_module in migration_modules:
module_path = os.path.join(
rookify_path, "modules", migration_module.MODULE_NAME
)
handler = migration_module.HANDLER_CLASS(
config=MappingProxyType(config), data=MappingProxyType(module_data)
config=MappingProxyType(config),
data=MappingProxyType(module_data),
module_path=module_path,
)
handler.preflight_check()
handler.preflight()
handlers.append((migration_module, handler))

# Run migration modules
Expand Down
2 changes: 1 addition & 1 deletion src/rookify/modules/analyze_ceph/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


class AnalyzeCephHandler(ModuleHandler):
def run(self) -> Dict[str, Any]:
def run(self) -> Any:
commands = ["mon dump", "osd dump", "device ls", "fs dump", "node ls"]

results: Dict[str, Any] = dict()
Expand Down
6 changes: 3 additions & 3 deletions src/rookify/modules/example/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

from ..module import ModuleHandler, ModuleException

from typing import Any, Dict
from typing import Any


class ExampleHandler(ModuleHandler):
def preflight_check(self) -> None:
def preflight(self) -> None:
# Do something for checking if all needed preconditions are met else throw ModuleException
raise ModuleException("Example module was loaded, so aborting!")

def run(self) -> Dict[str, Any]:
def run(self) -> Any:
# Run the migration tasks
return {}
4 changes: 2 additions & 2 deletions src/rookify/modules/migrate_osds/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@


class MigrateOSDsHandler(ModuleHandler):
def preflight_check(self) -> None:
def preflight(self) -> None:
pass
# result = self.ceph.mon_command("osd dump")
# raise ModuleException('test error')

def run(self) -> Dict[str, Any]:
def run(self) -> Any:
osd_config: Dict[str, Any] = dict()
for node, osds in self._data["analyze_ceph"]["node"]["ls"]["osd"].items():
osd_config[node] = {"osds": {}}
Expand Down
94 changes: 84 additions & 10 deletions src/rookify/modules/module.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-

import os
import yaml
import json
import abc
import rados
import kubernetes
import fabric
import jinja2
from typing import Any, Dict, List, Optional


Expand All @@ -21,7 +24,7 @@ class __Ceph:
def __init__(self, config: Dict[str, Any]):
try:
self.__ceph = rados.Rados(
conffile=config["conf_file"], conf={"keyring": config["keyring"]}
conffile=config["config"], conf={"keyring": config["keyring"]}
)
self.__ceph.connect()
except rados.ObjectNotFound as err:
Expand All @@ -41,23 +44,61 @@ def mon_command(

class __K8s:
def __init__(self, config: Dict[str, Any]):
k8s_config = kubernetes.client.Configuration()
k8s_config.api_key = config["api_key"]
k8s_config.host = config["host"]
k8s_config = kubernetes.config.load_kube_config(
config_file=config["config"]
)
self.__client = kubernetes.client.ApiClient(k8s_config)
self.__dynamic_client: Optional[kubernetes.dynamic.DynamicClient] = None

@property
def CoreV1Api(self) -> kubernetes.client.CoreV1Api:
def core_v1_api(self) -> kubernetes.client.CoreV1Api:
return kubernetes.client.CoreV1Api(self.__client)

@property
def AppsV1Api(self) -> kubernetes.client.AppsV1Api:
def apps_v1_api(self) -> kubernetes.client.AppsV1Api:
return kubernetes.client.AppsV1Api(self.__client)

@property
def NodeV1Api(self) -> kubernetes.client.NodeV1Api:
def node_v1_api(self) -> kubernetes.client.NodeV1Api:
return kubernetes.client.NodeV1Api(self.__client)

@property
def custom_objects_api(self) -> kubernetes.client.CustomObjectsApi:
return kubernetes.client.CustomObjectsApi(self.__client)

@property
def dynamic_client(self) -> kubernetes.dynamic.DynamicClient:
if not self.__dynamic_client:
self.__dynamic_client = kubernetes.dynamic.DynamicClient(self.__client)
return self.__dynamic_client

def crd_api(
self, api_version: str, kind: str
) -> kubernetes.dynamic.resource.Resource:
return self.dynamic_client.resources.get(api_version=api_version, kind=kind)

def crd_api_apply(
self, manifest: Dict[Any, Any]
) -> kubernetes.dynamic.resource.ResourceInstance:
"""
This applies a manifest for custom CRDs
See https://github.com/kubernetes-client/python/issues/1792 for more information
:param manifest: Dict of the kubernetes manifest
"""
api_version = manifest["apiVersion"]
kind = manifest["kind"]
resource_name = manifest["metadata"]["name"]
namespace = manifest["metadata"]["namespace"]
crd_api = self.crd_api(api_version=api_version, kind=kind)

try:
crd_api.get(namespace=namespace, name=resource_name)
return crd_api.patch(
body=manifest, content_type="application/merge-patch+json"
)
except kubernetes.dynamic.exceptions.NotFoundError:
return crd_api.create(body=manifest, namespace=namespace)

class __SSH:
def __init__(self, config: Dict[str, Any]):
self.__config = config
Expand All @@ -82,21 +123,48 @@ def command(self, host: str, command: str) -> fabric.runners.Result:
).run(command, hide=True)
return result

def __init__(self, config: Dict[str, Any], data: Dict[str, Any]):
class __Template:
def __init__(self, template_path: str):
self.__result_raw: Optional[str] = None
self.__result_yaml: Optional[Any] = None
self.__template_path: str = template_path
with open(template_path) as file:
self.__template = jinja2.Template(file.read())

def render(self, **variables: Any) -> None:
self.__result_raw = self.__template.render(**variables)
self.__result_yaml = None

@property
def raw(self) -> str:
if not self.__result_raw:
raise ModuleException("Template was not rendered")
return self.__result_raw

@property
def yaml(self) -> Any:
if not self.__result_yaml:
self.__result_yaml = yaml.safe_load(self.raw)
return self.__result_yaml

def __init__(self, config: Dict[str, Any], data: Dict[str, Any], module_path: str):
"""
Construct a new 'ModuleHandler' object.

:param module_data: The config and results from modules
:param config: The global config file
:param data: The output of modules required by this module
:param module_path: The filesystem path of this module
:return: returns nothing
"""
self._config = config
self._data = data
self.__module_path = module_path
self.__ceph: Optional[ModuleHandler.__Ceph] = None
self.__k8s: Optional[ModuleHandler.__K8s] = None
self.__ssh: Optional[ModuleHandler.__SSH] = None

@abc.abstractmethod
def preflight_check(self) -> None:
def preflight(self) -> None:
"""
Run the modules preflight check
"""
Expand Down Expand Up @@ -128,3 +196,9 @@ def ssh(self) -> __SSH:
if self.__ssh is None:
self.__ssh = ModuleHandler.__SSH(self._config["ssh"])
return self.__ssh

def load_template(self, filename: str, **variables: Any) -> __Template:
template_path = os.path.join(self.__module_path, "templates", filename)
template = ModuleHandler.__Template(template_path)
template.render(**variables)
return template
Loading