Initial commit
This commit is contained in:
commit
55c3caa766
49 files changed
+1602
No files matched your search
+180
@@ -0,0 +1,180 @@
|
||||
.stfolder
|
||||
logs
|
||||
storage
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/python
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=python
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
### Python Patch ###
|
||||
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
||||
poetry.toml
|
||||
|
||||
# ruff
|
||||
.ruff_cache/
|
||||
|
||||
# LSP config files
|
||||
pyrightconfig.json
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/python
|
||||
Generated
+5
@@ -0,0 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
Generated
+11
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.13 (colony)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.13 (colony)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (colony)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/colony.iml" filepath="$PROJECT_DIR$/.idea/colony.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="a5f8e4e7-da52-40b9-ac09-6eedaaa69b69" name="Changes" comment="" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="Python Script" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"associatedIndex": 3
|
||||
}</component>
|
||||
<component name="ProjectId" id="37rDRQ5zG8SbPZaUPDbrYGJVv81" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"Python.main.executor": "Debug",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||
"last_opened_file_path": "/home/ciomek/Repositories/colony"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="RecentsManager">
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/colony/core" />
|
||||
<recent name="$PROJECT_DIR$/colony/core/storage" />
|
||||
<recent name="$PROJECT_DIR$/storage" />
|
||||
<recent name="$PROJECT_DIR$/src/domain" />
|
||||
<recent name="$PROJECT_DIR$/src/core" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager">
|
||||
<configuration name="main" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<module name="colony" />
|
||||
<option name="ENV_FILES" value="" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<option name="RUN_TOOL" value="" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/main.py" />
|
||||
<option name="PARAMETERS" value="--role Explorer --listen --discover" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="Python.main" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="bundled-python-sdk-f2b7a9f6281b-6e1f45a539f7-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-253.29346.142" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="a5f8e4e7-da52-40b9-ac09-6eedaaa69b69" name="Changes" comment="" />
|
||||
<created>1767652287935</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1767652287935</updated>
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="a5f8e4e7-da52-40b9-ac09-6eedaaa69b69" name="Changes" comment="" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="Python Script" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"associatedIndex": 3
|
||||
}</component>
|
||||
<component name="ProjectId" id="37rDRQ5zG8SbPZaUPDbrYGJVv81" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"Python.main.executor": "Debug",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||
"last_opened_file_path": "/home/ciomek/Repositories/colony"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="RecentsManager">
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/colony/core" />
|
||||
<recent name="$PROJECT_DIR$/colony/core/storage" />
|
||||
<recent name="$PROJECT_DIR$/storage" />
|
||||
<recent name="$PROJECT_DIR$/src/domain" />
|
||||
<recent name="$PROJECT_DIR$/src/core" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager">
|
||||
<configuration name="main" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<module name="colony" />
|
||||
<option name="ENV_FILES" value="" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<option name="RUN_TOOL" value="" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/main.py" />
|
||||
<option name="PARAMETERS" value="--role Explorer --listen --discover" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="Python.main" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="bundled-python-sdk-f2b7a9f6281b-6e1f45a539f7-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-253.29346.142" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="a5f8e4e7-da52-40b9-ac09-6eedaaa69b69" name="Changes" comment="" />
|
||||
<created>1767652287935</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1767652287935</updated>
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,31 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
sYiPdVsPJw3i9f/r29R7w5RLUiCJ6Q==
|
||||
-----END PRIVATE KEY-----
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,19 @@
|
||||
from colony.core.network.nodes import LocalNode
|
||||
|
||||
|
||||
class RoleContext:
|
||||
def __init__(self, network, role_manager):
|
||||
self._network = network
|
||||
self._role_manager = role_manager
|
||||
|
||||
def execute(self, command, *args, **kwargs):
|
||||
return self._network.execute(command, *args, **kwargs)
|
||||
|
||||
def peers(self):
|
||||
return self._network.get_all_nodes()
|
||||
|
||||
def self(self) -> LocalNode:
|
||||
return self._network.get_self()
|
||||
|
||||
def set_self_node_roles(self, roles):
|
||||
self._role_manager.set_roles(roles)
|
||||
@@ -0,0 +1,34 @@
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from colony.core.logger.logging import get_logger
|
||||
|
||||
|
||||
class Dispatcher:
|
||||
def __init__(self, network, role_registry):
|
||||
self.logger = get_logger(self.__class__.__name__)
|
||||
|
||||
self.network = network
|
||||
self.role_registry = role_registry
|
||||
|
||||
def dispatch(self, payload):
|
||||
self.logger.debug(f"Dispatching payload for command {payload['command']['role']}.{payload["command"]['name']}")
|
||||
|
||||
c = payload["command"]
|
||||
role_name = c["role"]
|
||||
command_name = c["name"]
|
||||
args = tuple(c.get("args", []))
|
||||
kwargs = dict(c.get("kwargs", {}))
|
||||
|
||||
role_entry = self.role_registry.get_role_class_by_name(role_name)
|
||||
if role_entry is None:
|
||||
raise ValueError(f"Unknown role {role_name}")
|
||||
|
||||
commands_enum = role_entry.COMMANDS
|
||||
command = commands_enum(f"{role_name}.{command_name}")
|
||||
|
||||
node = self.network.get_self()
|
||||
|
||||
self.logger.info(f"Executing {role_name}.{command_name} on local node {node.id}")
|
||||
result = node.execute_local(command, *args, **kwargs)
|
||||
self.logger.debug(f"Passing the results from {role_name}.{command_name}")
|
||||
return result
|
||||
@@ -0,0 +1,5 @@
|
||||
class NetworkException(Exception):
|
||||
pass
|
||||
|
||||
class NoRoleException(Exception):
|
||||
pass
|
||||
Whitespace-only changes.
@@ -0,0 +1,25 @@
|
||||
import logging
|
||||
|
||||
|
||||
class ColorFormatter(logging.Formatter):
|
||||
COLORS = {
|
||||
'DEBUG': '\033[37m',
|
||||
'INFO': '\033[36m',
|
||||
'WARNING': '\033[33m',
|
||||
'ERROR': '\033[31m',
|
||||
'CRITICAL': '\033[41m',
|
||||
}
|
||||
RESET = '\033[0m'
|
||||
BOLD = '\033[1m'
|
||||
|
||||
def format(self, record):
|
||||
color = self.COLORS.get(record.levelname, self.RESET)
|
||||
message = super().format(record)
|
||||
|
||||
if record.levelname in ('WARNING', 'ERROR', 'CRITICAL'):
|
||||
message = f"{self.BOLD}{color}{message}{self.RESET}"
|
||||
else:
|
||||
message = f"{color}{message}{self.RESET}"
|
||||
|
||||
return message
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import logging
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
from colony.core.logger.colors import ColorFormatter
|
||||
|
||||
LOG_DIR = Path("logs")
|
||||
LOG_DIR.mkdir(exist_ok=True)
|
||||
|
||||
_loggers = {}
|
||||
_logger_lock = threading.Lock()
|
||||
FIXED_NAME_WIDTH = 15
|
||||
|
||||
def get_logger(name: str) -> logging.Logger:
|
||||
with _logger_lock:
|
||||
if name in _loggers:
|
||||
return _loggers[name]
|
||||
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
ch = logging.StreamHandler()
|
||||
ch.setLevel(logging.DEBUG)
|
||||
ch_formatter = ColorFormatter(
|
||||
f'[%(asctime)s] %(levelname)-7s %(name)-{FIXED_NAME_WIDTH}s: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
ch.setFormatter(ch_formatter)
|
||||
logger.addHandler(ch)
|
||||
|
||||
fh = logging.FileHandler(LOG_DIR / f"{name}.log", encoding='utf-8')
|
||||
fh.setLevel(logging.DEBUG)
|
||||
fh_formatter = logging.Formatter(
|
||||
f'[%(asctime)s] [%(threadName)s] %(levelname)-8s %(name)-{FIXED_NAME_WIDTH}s: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
fh.setFormatter(fh_formatter)
|
||||
logger.addHandler(fh)
|
||||
|
||||
logger.propagate = False
|
||||
_loggers[name] = logger
|
||||
return logger
|
||||
Whitespace-only changes.
@@ -0,0 +1,95 @@
|
||||
import json
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from colony.core.network.nodes import LocalNode, Node
|
||||
from colony.core.network.requests import CommandRequest
|
||||
from colony.core.logger.logging import get_logger
|
||||
from colony.settings import configuration
|
||||
from colony.utils.network import get_local_ip, get_hostname
|
||||
|
||||
|
||||
class Network:
|
||||
def __init__(self):
|
||||
self.logger = get_logger(self.__class__.__name__)
|
||||
|
||||
self.nodes = []
|
||||
|
||||
self.self_node = LocalNode(
|
||||
hostname=get_hostname(),
|
||||
ip=get_local_ip(),
|
||||
roles=[],
|
||||
port=configuration.listener.port
|
||||
)
|
||||
self.logger.info(f"Self node created: {self.self_node.id}")
|
||||
|
||||
if not self.nodes:
|
||||
self.nodes.append(self.self_node)
|
||||
|
||||
# @staticmethod
|
||||
# def initialize():
|
||||
# network = Network()
|
||||
# network.logger.info("Network.initialize() called")
|
||||
#
|
||||
# network.self_node = LocalNode(
|
||||
# hostname=get_hostname(),
|
||||
# ip=get_local_ip(),
|
||||
# roles=[],
|
||||
# port=configuration.listener.port
|
||||
# )
|
||||
# network.logger.info(f"Self node created: {network.self_node.id}")
|
||||
#
|
||||
# if not network.nodes:
|
||||
# network.nodes.append(network.self_node)
|
||||
#
|
||||
# return network
|
||||
|
||||
def get_self(self):
|
||||
return self.self_node
|
||||
|
||||
def get_all_nodes(self):
|
||||
return self.nodes
|
||||
|
||||
def get_node_of_role(self, role_name: str) -> Optional[Node]:
|
||||
for node in self.nodes:
|
||||
for role in node.roles:
|
||||
if role == role_name:
|
||||
return node
|
||||
|
||||
self.logger.warning(f"No node found with role: {role_name}")
|
||||
return None
|
||||
|
||||
def execute(self, command: Enum, *args, **kwargs):
|
||||
request = CommandRequest.form(command, *args, **kwargs)
|
||||
self.logger.info(f"Executing command {request.id} on role {request.role}")
|
||||
|
||||
node = self.get_node_of_role(request.role)
|
||||
if node is None:
|
||||
raise ValueError(f"No node found with role {request.role}")
|
||||
|
||||
result = node.execute(command, *args, **kwargs)
|
||||
self.logger.debug(f"Command {request.id} executed")
|
||||
return result
|
||||
|
||||
|
||||
def load_network_from_snapshot(self, file_path: str = "storage/colony_snapshot.json"):
|
||||
self.logger.info(f"Loading network snapshot from {file_path}")
|
||||
nodes = []
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
snapshot = json.load(f)
|
||||
except FileNotFoundError:
|
||||
self.logger.warning(f"Snapshot file {file_path} not found. Starting with empty network")
|
||||
snapshot = {"nodes": []}
|
||||
|
||||
for node_data in snapshot.get("nodes", []):
|
||||
node = Node(
|
||||
hostname=node_data["hostname"],
|
||||
ip=node_data["ip"],
|
||||
roles=node_data.get("roles", []),
|
||||
port=node_data["port"]
|
||||
)
|
||||
self.logger.info(f"Loaded node from snapshot: {node.id}")
|
||||
nodes.append(node)
|
||||
|
||||
return nodes
|
||||
@@ -0,0 +1,100 @@
|
||||
import datetime
|
||||
import platform
|
||||
import json
|
||||
|
||||
import GPUtil
|
||||
import psutil
|
||||
|
||||
|
||||
class HardwareStatistics:
|
||||
def __init__(self):
|
||||
self.cpu = None
|
||||
self.mem = None
|
||||
self.gpu = None
|
||||
self.disk = None
|
||||
self.os_info = None
|
||||
self.boot_time = None
|
||||
|
||||
@staticmethod
|
||||
def collect():
|
||||
self = HardwareStatistics()
|
||||
self.cpu = self._get_cpu_stats()
|
||||
self.mem = self._get_memory_stats()
|
||||
self.gpu = self._get_gpu_stats()
|
||||
self.disk = self._get_disk_stats()
|
||||
self.os_info = self._get_os_info()
|
||||
self.boot_time = datetime.datetime.fromtimestamp(
|
||||
psutil.boot_time()
|
||||
).isoformat()
|
||||
return self
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"cpu": self.cpu,
|
||||
"memory": self.mem,
|
||||
"gpu": self.gpu,
|
||||
"disk": self.disk,
|
||||
"os": self.os_info,
|
||||
"boot_time": self.boot_time
|
||||
}
|
||||
|
||||
def to_json(self):
|
||||
return json.dumps(self.to_dict(), indent=2)
|
||||
|
||||
def _get_cpu_stats(self):
|
||||
return {
|
||||
"cores": psutil.cpu_count(logical=False),
|
||||
"threads": psutil.cpu_count(logical=True),
|
||||
"usage_percent": psutil.cpu_percent(interval=1)
|
||||
}
|
||||
|
||||
def _get_memory_stats(self):
|
||||
mem = psutil.virtual_memory()
|
||||
return {
|
||||
"total": mem.total,
|
||||
"available": mem.available,
|
||||
"used": mem.used,
|
||||
"percent": mem.percent
|
||||
}
|
||||
|
||||
def _get_gpu_stats(self):
|
||||
gpus = GPUtil.getGPUs()
|
||||
if not gpus:
|
||||
return []
|
||||
return [
|
||||
{
|
||||
"id": gpu.id,
|
||||
"name": gpu.name,
|
||||
"load": gpu.load * 100,
|
||||
"memory_total": gpu.memoryTotal,
|
||||
"memory_used": gpu.memoryUsed,
|
||||
"memory_free": gpu.memoryFree,
|
||||
"temperature": gpu.temperature
|
||||
}
|
||||
for gpu in gpus
|
||||
]
|
||||
|
||||
def _get_disk_stats(self):
|
||||
disk_info = []
|
||||
for d in psutil.disk_partitions():
|
||||
usage = psutil.disk_usage(d.mountpoint)
|
||||
disk_info.append({
|
||||
"device": d.device,
|
||||
"mountpoint": d.mountpoint,
|
||||
"fstype": d.fstype,
|
||||
"total": usage.total,
|
||||
"used": usage.used,
|
||||
"free": usage.free,
|
||||
"percent": usage.percent
|
||||
})
|
||||
return disk_info
|
||||
|
||||
def _get_os_info(self):
|
||||
return {
|
||||
"system": platform.system(),
|
||||
"node": platform.node(),
|
||||
"release": platform.release(),
|
||||
"version": platform.version(),
|
||||
"machine": platform.machine(),
|
||||
"processor": platform.processor()
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
|
||||
from colony.core.network.hardware import HardwareStatistics
|
||||
from colony.core.network.requests import CommandRequest
|
||||
from colony.transport.whisperer import Whisperer
|
||||
|
||||
|
||||
class Node:
|
||||
def __init__(self, hostname: str, ip: str, roles: List[str], port: int):
|
||||
self.id = f"{hostname}@{ip}"
|
||||
self.hostname = hostname
|
||||
self.ip = ip
|
||||
self.roles = roles
|
||||
self.port = port
|
||||
|
||||
def execute(self, command: Enum, *args, **kwargs):
|
||||
request = CommandRequest.form(command, *args, **kwargs)
|
||||
|
||||
return Whisperer(self.ip, self.port).send({
|
||||
"command": {
|
||||
"role": request.role,
|
||||
"name": request.name,
|
||||
"args": request.args,
|
||||
"kwargs": request.kwargs
|
||||
}
|
||||
})
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"hostname": self.hostname,
|
||||
"ip": self.ip,
|
||||
"port": self.port,
|
||||
"roles": self.roles,
|
||||
"hardware": HardwareStatistics.collect().to_dict()
|
||||
}
|
||||
|
||||
|
||||
class LocalNode(Node):
|
||||
def __init__(self, hostname: str, ip: str, roles: List[str], port: int):
|
||||
super().__init__(hostname, ip, roles, port)
|
||||
|
||||
self.__roles = dict()
|
||||
|
||||
def get_role_instances(self):
|
||||
return self.__roles
|
||||
|
||||
def set_role_instances(self, roles):
|
||||
for role_name, role_instance in roles.items():
|
||||
self.__roles[role_name] = role_instance
|
||||
|
||||
def execute_local(self, command: Enum, *args, **kwargs):
|
||||
request = CommandRequest.form(command, *args, **kwargs)
|
||||
|
||||
role = self.__roles.get(request.role)
|
||||
if role is None:
|
||||
raise ValueError(f"Role {request.role} not bound to local node")
|
||||
|
||||
return role.execute_local(
|
||||
request.id,
|
||||
*request.args,
|
||||
**request.kwargs
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
import dataclasses
|
||||
from enum import Enum
|
||||
from typing import Any, Tuple, Dict, Optional
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class CommandRequest:
|
||||
_id: Enum
|
||||
args: Optional[Tuple] = None
|
||||
kwargs: Optional[Dict[str, Any]] = None
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self._id.value
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
try:
|
||||
return self.id.split('.')[1]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def role(self):
|
||||
try:
|
||||
return self.id.split('.')[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def form(cls, id: Enum, *args: Any, **kwargs: Any):
|
||||
return cls(id, args, kwargs)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import json
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
class StateManager:
|
||||
class Data(Enum):
|
||||
Roles = "roles"
|
||||
|
||||
STATE_FILE = Path("state.json")
|
||||
|
||||
def __init__(self):
|
||||
self._data = {}
|
||||
self._load()
|
||||
|
||||
def _load(self):
|
||||
if self.STATE_FILE.exists():
|
||||
with open(self.STATE_FILE, "r", encoding="utf-8") as f:
|
||||
self._data = json.load(f)
|
||||
else:
|
||||
self._data = {}
|
||||
self._save()
|
||||
|
||||
for key in self.Data:
|
||||
self._data.setdefault(key.value, {})
|
||||
|
||||
def _save(self):
|
||||
with open(self.STATE_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(self._data, f, indent=4)
|
||||
|
||||
def get(self, key: Data) -> Any:
|
||||
return self._data[key.value]
|
||||
|
||||
def set(self, key: Data, value: Any):
|
||||
self._data[key.value] = value
|
||||
self._save()
|
||||
@@ -0,0 +1,60 @@
|
||||
import os
|
||||
import json
|
||||
from enum import Enum
|
||||
|
||||
STORAGE_DIR = "storage"
|
||||
|
||||
class Storage:
|
||||
def __init__(self, storage_dir):
|
||||
self.storage_dir = storage_dir
|
||||
|
||||
os.makedirs(self.storage_dir, exist_ok=True)
|
||||
|
||||
def get_path(self, filename):
|
||||
return os.path.join(self.storage_dir, filename)
|
||||
|
||||
def ls(self, subdir):
|
||||
path = os.path.join(self.storage_dir, subdir)
|
||||
|
||||
if not os.path.exists(path):
|
||||
return []
|
||||
|
||||
return os.listdir(path)
|
||||
|
||||
def save(self, filename, data):
|
||||
path = self.get_path(filename)
|
||||
|
||||
with open(path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
return path
|
||||
|
||||
def open(self, filename):
|
||||
path = self.get_path(filename)
|
||||
|
||||
if not os.path.exists(path):
|
||||
return None
|
||||
|
||||
with open(path, 'rb') as f:
|
||||
return json.loads(f.read())
|
||||
|
||||
def exists(self, filename):
|
||||
return os.path.exists(self.get_path(filename))
|
||||
|
||||
def delete(self, filename):
|
||||
path = self.get_path(filename)
|
||||
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class DefaultStorage(Storage):
|
||||
def __init__(self):
|
||||
super().__init__(STORAGE_DIR)
|
||||
|
||||
|
||||
class DEFAULT_STORAGE_FILES(Enum):
|
||||
COLONY_SNAPSHOT = 'colony_snapshot.json'
|
||||
@@ -0,0 +1,50 @@
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class RolePriority(Enum):
|
||||
GENERAL = 0 # should be on each and every node
|
||||
CRITICAL = 1 # network can't work properly without it
|
||||
HIGH = 2 # network can manager without it but with some difficulty
|
||||
LOW = 3 # it's just nice to have
|
||||
|
||||
|
||||
class HardwareType(Enum):
|
||||
DISK_SPACE = 1
|
||||
CPU = 2
|
||||
GPU = 3
|
||||
RAM = 4
|
||||
LONGEVITY = 5
|
||||
|
||||
|
||||
class HardwarePriority:
|
||||
hardware: HardwareType
|
||||
priority: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class HardwarePreferences:
|
||||
priorities: dict[HardwareType, int]
|
||||
|
||||
def priority_of(self, hardware: HardwareType) -> int:
|
||||
return self.priorities.get(hardware, 0)
|
||||
|
||||
|
||||
class RoleStats:
|
||||
def __init__(self, cls, priority: RolePriority, hardware_preferences: HardwarePreferences = None):
|
||||
self.name = cls.__name__
|
||||
self.cls = cls
|
||||
self.priority = priority
|
||||
self.hardware_preferences = hardware_preferences
|
||||
|
||||
|
||||
class RoleRegistry:
|
||||
def __init__(self, role_list):
|
||||
roles = {}
|
||||
for role_stat in role_list:
|
||||
roles[role_stat.name] = role_stat
|
||||
|
||||
self.roles = roles
|
||||
|
||||
def get_role_class_by_name(self, name):
|
||||
return self.roles.get(name).cls
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,82 @@
|
||||
import datetime
|
||||
from enum import Enum
|
||||
|
||||
from colony.core.storage import DEFAULT_STORAGE_FILES
|
||||
from colony.roles.role import Role
|
||||
from colony.roles.unit.role import Unit
|
||||
from colony.roles.vaultwarden.role import VaultWarden
|
||||
from colony.transport.discover import Discover
|
||||
from colony.transport.whisperer import Whisperer
|
||||
|
||||
|
||||
class Explorer(Role):
|
||||
CONTINUOUS = True
|
||||
LOOP_SECONDS_WAIT = 60
|
||||
|
||||
class COMMANDS(Enum):
|
||||
pass
|
||||
|
||||
def __init__(self, context):
|
||||
commands = {
|
||||
|
||||
}
|
||||
|
||||
super().__init__(commands, context)
|
||||
|
||||
def loop(self):
|
||||
self.logger.info("Getting network snapshot")
|
||||
snapshot = self.get_snapshot()['result']
|
||||
self.logger.debug(f"Loaded snapshot")
|
||||
|
||||
discovered_peers = self.discover_peers()
|
||||
self.logger.info(f"Discovered peers: {list(discovered_peers.keys())}")
|
||||
|
||||
for ip, port in discovered_peers.items():
|
||||
self.logger.info(f"Sending SHOW_STATS command to {ip}:{port}")
|
||||
|
||||
stats = self.fetch_stats_from_peer(ip, port)
|
||||
|
||||
self.logger.debug(f"Received stats from {ip}")
|
||||
snapshot[ip] = stats | {'last_update': datetime.datetime.now().isoformat(), 'last_seen': datetime.datetime.now().isoformat(), 'status': 'reachable'}
|
||||
|
||||
missing_ips = set(snapshot.keys()) - set(discovered_peers.keys())
|
||||
for ip in missing_ips:
|
||||
self.logger.warning(f"Peer {ip} was in snapshot but not reachable. Marking as unreachable")
|
||||
snapshot[ip]['last_update'] = datetime.datetime.now().isoformat()
|
||||
snapshot[ip]['status'] = 'unreachable'
|
||||
|
||||
self.set_snapshot(snapshot)
|
||||
self.logger.info("Snapshot updated successfully")
|
||||
|
||||
|
||||
def get_snapshot(self):
|
||||
return self.context.execute(VaultWarden.COMMANDS.OPEN, DEFAULT_STORAGE_FILES.COLONY_SNAPSHOT.value)
|
||||
|
||||
def set_snapshot(self, snapshot):
|
||||
return self.context.execute(VaultWarden.COMMANDS.OVERWRITE, DEFAULT_STORAGE_FILES.COLONY_SNAPSHOT.value, snapshot)
|
||||
|
||||
def discover_peers(self):
|
||||
peers = {}
|
||||
|
||||
# for ip in get_local_subnet().hosts():
|
||||
for ip in ['192.168.65.101', '192.168.65.102']:
|
||||
try:
|
||||
port = Discover.request(str(ip))
|
||||
peers[str(ip)] = port
|
||||
except (TimeoutError, ConnectionRefusedError):
|
||||
continue
|
||||
return peers
|
||||
|
||||
def fetch_stats_from_peer(self, ip, port):
|
||||
command = Unit.COMMANDS.SHOW_STATS
|
||||
role_name, command_name = command.value.split('.')
|
||||
request = {
|
||||
"command": {
|
||||
"role": role_name,
|
||||
"name": command_name,
|
||||
"args": [],
|
||||
"kwargs": {}
|
||||
}
|
||||
}
|
||||
|
||||
return Whisperer(ip, port).send(request).get('result', {})
|
||||
Whitespace-only changes.
@@ -0,0 +1,21 @@
|
||||
from enum import Enum
|
||||
from colony.roles.role import Role, Command
|
||||
from colony.roles.unit.role import Unit
|
||||
|
||||
|
||||
class Foreman(Role):
|
||||
CONTINUOUS = True
|
||||
LOOP_SECONDS_WAIT = 60
|
||||
|
||||
class COMMANDS(Enum):
|
||||
pass
|
||||
|
||||
def __init__(self, context):
|
||||
commands = {
|
||||
|
||||
}
|
||||
|
||||
super().__init__(commands, context)
|
||||
|
||||
def loop(self):
|
||||
self.context.self().execute(Unit.COMMANDS.SET_ROLES, ['Unit', 'Foreman', 'Explorer', 'VaultWarden'])
|
||||
@@ -0,0 +1,73 @@
|
||||
import threading
|
||||
|
||||
from dependency_injector.wiring import inject, Container, Provide
|
||||
|
||||
from colony.core.context import RoleContext
|
||||
from colony.core.state_manager import StateManager
|
||||
from colony.core.logger.logging import get_logger
|
||||
|
||||
|
||||
class RoleManager:
|
||||
def __init__(self, network, role_registry):
|
||||
self.network = network
|
||||
self.role_registry = role_registry
|
||||
|
||||
self.logger = get_logger(self.__class__.__name__)
|
||||
|
||||
def initialize_roles(self):
|
||||
self.logger.info("Initializing roles...")
|
||||
context = RoleContext(self.network, self)
|
||||
|
||||
roles = {}
|
||||
for role_name in StateManager().get(StateManager.Data.Roles):
|
||||
role_cls = self.role_registry.get_role_class_by_name(role_name)
|
||||
self.logger.debug(f"Creating instance of role {role_name}. ")
|
||||
role_instance = role_cls(context=context)
|
||||
roles[role_name] = role_instance
|
||||
|
||||
self.network.self_node.roles = [role for role in roles.keys()]
|
||||
self.network.self_node.set_role_instances(roles)
|
||||
self.logger.info(f"Roles initialized: {list(roles.keys())}. ")
|
||||
|
||||
def get_role_instances(self):
|
||||
return self.network.self_node.get_role_instances()
|
||||
|
||||
def start_all(self):
|
||||
for role_name, role in self.get_role_instances().items():
|
||||
self.logger.info(f"Starting role {role_name}")
|
||||
t = threading.Thread(target=role.start, daemon=True, name=role_name)
|
||||
t.start()
|
||||
self.logger.debug(f"Thread started for role {role_name}: {t.name}")
|
||||
|
||||
def set_roles(self, new_roles):
|
||||
self.logger.info("Updating roles...")
|
||||
|
||||
current_roles = self.get_role_instances()
|
||||
roles_to_remove = set(current_roles) - set(new_roles)
|
||||
roles_to_add = set(new_roles) - set(current_roles)
|
||||
|
||||
for role_name in roles_to_remove:
|
||||
self.logger.debug(f"Stopping and removing role {role_name}")
|
||||
current_roles[role_name].stop()
|
||||
del current_roles[role_name]
|
||||
|
||||
context = RoleContext(self.network, self)
|
||||
for role_name in roles_to_add:
|
||||
self.logger.debug(f"Creating and adding new role {role_name}")
|
||||
role_cls = available_roles[role_name]
|
||||
current_roles[role_name] = role_cls(context=context)
|
||||
|
||||
StateManager().set(StateManager.Data.Roles, list(current_roles.keys()))
|
||||
|
||||
self.network.self_node.roles = list(current_roles.keys())
|
||||
self.network.self_node.set_role_instances(current_roles)
|
||||
|
||||
for role_name in roles_to_add:
|
||||
role = current_roles[role_name]
|
||||
self.logger.info(f"Starting role {role_name}")
|
||||
t = threading.Thread(target=role.start, daemon=True, name=role_name)
|
||||
t.start()
|
||||
self.logger.debug(f"Thread started for role {role_name}: {t.name}")
|
||||
|
||||
self.logger.info(f"Roles updated to: {list(current_roles.keys())}")
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import threading
|
||||
|
||||
from colony.core.context import RoleContext
|
||||
from colony.core.logger.logging import get_logger
|
||||
|
||||
|
||||
class Command:
|
||||
def __init__(self, callback):
|
||||
self.callback = callback
|
||||
|
||||
class Role:
|
||||
CONTINUOUS = None
|
||||
LOOP_SECONDS_WAIT = None
|
||||
|
||||
def __init__(self, commands, context: RoleContext):
|
||||
self.commands = commands
|
||||
self.context = context
|
||||
|
||||
self._stop_event = threading.Event()
|
||||
|
||||
self._thread = None
|
||||
|
||||
if self.CONTINUOUS and self.LOOP_SECONDS_WAIT is None:
|
||||
raise NotImplementedError("You made this role to run continuously but have not set seconds to wait. Please set constant variable LOOP_SECONDS_WAIT. ")
|
||||
|
||||
self.logger = get_logger(self.__class__.__name__)
|
||||
|
||||
self.logger.info(f'{self.__class__.__name__} initialized and ready')
|
||||
|
||||
def __str__(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
def start(self):
|
||||
if not self.CONTINUOUS:
|
||||
return
|
||||
|
||||
self._thread = threading.Thread(target=self._run_loop, daemon=True, name=str(self))
|
||||
self._thread.start()
|
||||
|
||||
def _run_loop(self):
|
||||
while not self._stop_event.is_set():
|
||||
self.loop()
|
||||
self._stop_event.wait(self.LOOP_SECONDS_WAIT)
|
||||
|
||||
def loop(self):
|
||||
if self.CONTINUOUS:
|
||||
raise NotImplementedError("Continuous roles must implement loop()")
|
||||
|
||||
def stop(self):
|
||||
self._stop_event.set()
|
||||
|
||||
def execute_local(self, name, *args, **kwargs):
|
||||
command = self.commands.get(name)
|
||||
|
||||
if not command:
|
||||
raise ValueError(f"No command {name}")
|
||||
|
||||
return command.callback(*args, **kwargs)
|
||||
Whitespace-only changes.
@@ -0,0 +1,29 @@
|
||||
from enum import Enum
|
||||
from colony.core.network.hardware import HardwareStatistics
|
||||
from colony.roles.role import Role, Command
|
||||
|
||||
|
||||
class Unit(Role):
|
||||
class COMMANDS(Enum):
|
||||
SHOW_STATS = 'Unit.ShowStats'
|
||||
SHOW_HARDWARE = 'Unit.ShowHardware'
|
||||
SET_ROLES = 'Unit.SetRoles'
|
||||
|
||||
def __init__(self, context):
|
||||
commands = {
|
||||
self.COMMANDS.SHOW_STATS.value: Command(self.show_stats),
|
||||
self.COMMANDS.SHOW_HARDWARE.value: Command(self.show_hardware),
|
||||
self.COMMANDS.SET_ROLES.value: Command(self.set_roles),
|
||||
}
|
||||
|
||||
super().__init__(commands, context)
|
||||
|
||||
def show_hardware(self):
|
||||
return HardwareStatistics.collect().to_dict()
|
||||
|
||||
def show_stats(self):
|
||||
self_node = self.context.self()
|
||||
return self_node.to_dict()
|
||||
|
||||
def set_roles(self, roles):
|
||||
self.context.set_self_node_roles(roles)
|
||||
Whitespace-only changes.
@@ -0,0 +1,31 @@
|
||||
from enum import Enum
|
||||
|
||||
from colony.core.storage import DefaultStorage
|
||||
from colony.roles.role import Role, Command
|
||||
|
||||
|
||||
class VaultWarden(Role):
|
||||
class COMMANDS(Enum):
|
||||
LS = 'VaultWarden.ls'
|
||||
OPEN = 'VaultWarden.open'
|
||||
OVERWRITE = 'VaultWarden.overwrite'
|
||||
|
||||
def __init__(self, context):
|
||||
commands = {
|
||||
self.COMMANDS.LS.value: Command(self.ls),
|
||||
self.COMMANDS.OPEN.value: Command(self.open),
|
||||
self.COMMANDS.OVERWRITE.value: Command(self.overwrite)
|
||||
}
|
||||
|
||||
self.storage = DefaultStorage()
|
||||
|
||||
super().__init__(commands, context)
|
||||
|
||||
def ls(self, dir):
|
||||
return self.storage.ls(dir)
|
||||
|
||||
def open(self, file):
|
||||
return self.storage.open(file)
|
||||
|
||||
def overwrite(self, filename, content):
|
||||
return self.storage.save(filename, content)
|
||||
@@ -0,0 +1,7 @@
|
||||
from dynaconf import Dynaconf
|
||||
|
||||
configuration = Dynaconf(
|
||||
envvar_prefix="COLONY",
|
||||
settings_files=['config.json'],
|
||||
load_dotenv=False,
|
||||
)
|
||||
Whitespace-only changes.
@@ -0,0 +1,32 @@
|
||||
import socket
|
||||
import struct
|
||||
|
||||
class Discover:
|
||||
DISCOVER_PORT = 28149
|
||||
MAGIC = b"OK"
|
||||
|
||||
@staticmethod
|
||||
def listen(real_port, host="0.0.0.0"):
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
s.bind((host, Discover.DISCOVER_PORT))
|
||||
s.listen()
|
||||
|
||||
while True:
|
||||
conn, _ = s.accept()
|
||||
try:
|
||||
data = conn.recv(2)
|
||||
if data == Discover.MAGIC:
|
||||
conn.sendall(struct.pack("!H", real_port))
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@staticmethod
|
||||
def request(target_host, timeout=3):
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(timeout)
|
||||
s.connect((target_host, Discover.DISCOVER_PORT))
|
||||
s.sendall(Discover.MAGIC)
|
||||
data = s.recv(2)
|
||||
s.close()
|
||||
return struct.unpack("!H", data)[0]
|
||||
@@ -0,0 +1,64 @@
|
||||
import json
|
||||
import socket
|
||||
import ssl
|
||||
import threading
|
||||
from asyncio import Queue
|
||||
|
||||
from colony.settings import configuration
|
||||
|
||||
|
||||
class Listener:
|
||||
def __init__(self, dispatcher, host=None, port=None, cert_file=None, keyfile=None):
|
||||
self.dispatcher = dispatcher
|
||||
self.host = host or getattr(configuration.listener, "host", "0.0.0.0")
|
||||
self.port = port or getattr(configuration.listener, "port", 9000)
|
||||
self.cert_file = cert_file or getattr(configuration.listener, "cert_file", "certs/server.crt")
|
||||
self.keyfile = keyfile or getattr(configuration.listener, "keyfile", "certs/server.key")
|
||||
self.queue = Queue()
|
||||
self.running = False
|
||||
|
||||
def handle_client(self, conn):
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
context.load_cert_chain(certfile=self.cert_file, keyfile=self.keyfile)
|
||||
|
||||
tls_conn = context.wrap_socket(conn, server_side=True)
|
||||
|
||||
try:
|
||||
data = tls_conn.recv(65536)
|
||||
payload = json.loads(data.decode())
|
||||
|
||||
result = self.dispatcher.dispatch(payload)
|
||||
|
||||
response = json.dumps({
|
||||
"status": "ok",
|
||||
"result": result
|
||||
}).encode()
|
||||
|
||||
tls_conn.sendall(response)
|
||||
except Exception as e:
|
||||
error = json.dumps({
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}).encode()
|
||||
tls_conn.sendall(error)
|
||||
raise
|
||||
finally:
|
||||
tls_conn.close()
|
||||
|
||||
def start(self):
|
||||
self.running = True
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.bind((self.host, self.port))
|
||||
sock.listen(5)
|
||||
|
||||
while self.running:
|
||||
try:
|
||||
conn, addr = sock.accept()
|
||||
t = threading.Thread(target=self.handle_client, args=(conn,), name='HandleClient')
|
||||
t.daemon = True
|
||||
t.start()
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
|
||||
sock.close()
|
||||
@@ -0,0 +1,38 @@
|
||||
import json
|
||||
import socket
|
||||
import ssl
|
||||
|
||||
from colony.settings import configuration
|
||||
|
||||
|
||||
class Whisperer:
|
||||
def __init__(self, host=None, port=None, cert_file=None, keyfile=None):
|
||||
self.host = host or getattr(configuration.whisperer, "host", "127.0.0.1")
|
||||
self.port = port or getattr(configuration.whisperer, "port", 9000)
|
||||
self.cert_file = cert_file or getattr(configuration.whisperer, "cert_file", None)
|
||||
self.keyfile = keyfile or getattr(configuration.whisperer, "keyfile", None)
|
||||
|
||||
def send(self, payload, timeout=500):
|
||||
data = json.dumps(payload).encode()
|
||||
|
||||
context = ssl.create_default_context()
|
||||
context.check_hostname = False
|
||||
context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(timeout)
|
||||
tls_sock = context.wrap_socket(sock)
|
||||
|
||||
try:
|
||||
tls_sock.connect((self.host, self.port))
|
||||
tls_sock.sendall(data)
|
||||
|
||||
response = tls_sock.recv(65536)
|
||||
if not response:
|
||||
raise RuntimeError("Connection closed without response. ")
|
||||
|
||||
return json.loads(response.decode())
|
||||
except socket.timeout:
|
||||
raise TimeoutError("No response before timeout. ")
|
||||
finally:
|
||||
tls_sock.close()
|
||||
Whitespace-only changes.
@@ -0,0 +1,17 @@
|
||||
import ipaddress
|
||||
import socket
|
||||
|
||||
|
||||
def get_local_subnet():
|
||||
hostname = socket.gethostname()
|
||||
local_ip = socket.gethostbyname(hostname)
|
||||
|
||||
subnet = ipaddress.ip_network(f"{local_ip}/24", strict=False)
|
||||
|
||||
return subnet
|
||||
|
||||
def get_local_ip():
|
||||
return socket.gethostbyname(socket.gethostname())
|
||||
|
||||
def get_hostname():
|
||||
return socket.gethostname()
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"whisperer": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 65334
|
||||
},
|
||||
"listener": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 65334,
|
||||
"cert_file": "certs/server.crt",
|
||||
"keyfile": "certs/server.key"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import threading
|
||||
|
||||
from colony.core.network.core import Network
|
||||
from colony.core.dispatcher import Dispatcher
|
||||
from colony.core.state_manager import StateManager
|
||||
from colony.core.storage import DefaultStorage
|
||||
from colony.role_registry import RoleRegistry
|
||||
from colony.roles.manager import RoleManager
|
||||
from colony.transport.discover import Discover
|
||||
from colony.transport.listener import Listener
|
||||
from role_settings import available_roles
|
||||
from colony.settings import configuration
|
||||
|
||||
|
||||
def main():
|
||||
storage = DefaultStorage()
|
||||
state_manager = StateManager()
|
||||
network = Network()
|
||||
role_registry = RoleRegistry(role_list=available_roles)
|
||||
dispatcher = Dispatcher(network=network, role_registry=role_registry)
|
||||
role_manager = RoleManager(network=network, role_registry=role_registry)
|
||||
|
||||
threads = []
|
||||
|
||||
listener = Listener(dispatcher=dispatcher)
|
||||
t = threading.Thread(
|
||||
target=listener.start,
|
||||
daemon=True,
|
||||
name="Listener"
|
||||
)
|
||||
t.start()
|
||||
threads.append(t)
|
||||
|
||||
t = threading.Thread(
|
||||
target=Discover.listen,
|
||||
args=(configuration.listener.port,),
|
||||
daemon=True,
|
||||
name="Discover"
|
||||
)
|
||||
t.start()
|
||||
threads.append(t)
|
||||
|
||||
role_manager.initialize_roles()
|
||||
role_manager.start_all()
|
||||
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,5 @@
|
||||
dependency-injector==4.48.3
|
||||
dynaconf==3.2.12
|
||||
GPUtil==1.4.0
|
||||
psutil==7.2.1
|
||||
setuptools==80.9.0
|
||||
@@ -0,0 +1,30 @@
|
||||
from colony.role_registry import RoleStats, HardwarePreferences, RolePriority, HardwareType
|
||||
from colony.roles.explorer.role import Explorer
|
||||
from colony.roles.foreman.role import Foreman
|
||||
from colony.roles.unit.role import Unit
|
||||
from colony.roles.vaultwarden.role import VaultWarden
|
||||
|
||||
available_roles = [
|
||||
RoleStats(
|
||||
cls=Unit,
|
||||
priority=RolePriority.GENERAL,
|
||||
),
|
||||
RoleStats(
|
||||
cls=Foreman,
|
||||
priority=RolePriority.CRITICAL,
|
||||
),
|
||||
RoleStats(
|
||||
cls=VaultWarden,
|
||||
priority=RolePriority.CRITICAL,
|
||||
hardware_preferences=HardwarePreferences(
|
||||
{
|
||||
HardwareType.DISK_SPACE: 1,
|
||||
HardwareType.LONGEVITY: 3,
|
||||
}
|
||||
)
|
||||
),
|
||||
RoleStats(
|
||||
cls=Explorer,
|
||||
priority=RolePriority.HIGH,
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"roles": [
|
||||
"Unit",
|
||||
"Foreman",
|
||||
"Explorer",
|
||||
"VaultWarden"
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user