Skip to content

Commit

Permalink
CTX-5430: Removed isConfigured() method since it was just causing tro…
Browse files Browse the repository at this point in the history
…ubles, ive added isConfigured checks in isConfigValid. More improvements, migration fix.
  • Loading branch information
Bogdan Tintor committed Aug 6, 2024
1 parent d7595f0 commit a0594c3
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 72 deletions.
15 changes: 14 additions & 1 deletion coretex/cli/modules/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,20 @@ def initializeUserSession() -> None:
authenticateWithRefreshToken(userConfig)
else:
authenticateUser(userConfig)
except (ConfigurationNotFound, InvalidConfiguration):
except ConfigurationNotFound:
ui.errorEcho("User configuration not found.")
if not ui.clickPrompt("Would you like to configure the user? (Y/n)", type = bool, default = True, show_default = False):
raise

userConfig = configUser()
except InvalidConfiguration as ex:
ui.errorEcho("Invalid user configuration found.")
for error in ex.errors:
ui.errorEcho(f"{error}")

if not ui.clickPrompt("Would you like to reconfigure the user? (Y/n)", type = bool, default = True, show_default = False):
raise

userConfig = configUser()

userConfig.save()
10 changes: 7 additions & 3 deletions coretex/configuration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
def configMigration(configPath: Path) -> None:
with configPath.open("r") as file:
oldConfig = json.load(file)
logging.warning(f"[Coretex] >> WARNING: Old configuration:\n{oldConfig}")

userRaw = {
"username": oldConfig.get("username"),
Expand Down Expand Up @@ -66,7 +67,6 @@ def configMigration(configPath: Path) -> None:


def _syncConfigWithEnv() -> None:
print("i am syncing")
# If configuration doesn't exist default one will be created
# Initialization of User and Node Configuration classes will do
# the necessary sync between config properties and corresponding
Expand All @@ -75,6 +75,10 @@ def _syncConfigWithEnv() -> None:
# old configuration exists, fill out new config files with old configuration
oldConfigPath = CONFIG_DIR / "config.json"
if oldConfigPath.exists():
logging.warning(
f"[Coretex] >> WARNING: Old configuration found at path: {oldConfigPath}. Migrating to new configuration."
f"\nFields with invalid values might be overrided in this process."
)
configMigration(oldConfigPath)

try:
Expand All @@ -84,7 +88,7 @@ def _syncConfigWithEnv() -> None:
except (ConfigurationNotFound, InvalidConfiguration) as ex:
if not isCliRuntime():
logging.error(f">> [Coretex] Error loading configuration. Reason: {ex}")
logging.info("\tIf this message from Coretex Node you can safely ignore it.")
logging.info("\tIf this message came from Coretex Node you can safely ignore it.")

try:
nodeConfig = NodeConfiguration.load()
Expand All @@ -99,4 +103,4 @@ def _syncConfigWithEnv() -> None:
except (ConfigurationNotFound, InvalidConfiguration) as ex:
if not isCliRuntime():
logging.error(f">> [Coretex] Error loading configuration. Reason: {ex}")
logging.info("\tIf this message from Coretex Node you can safely ignore it.")
logging.info("\tIf this message came from Coretex Node you can safely ignore it.")
8 changes: 0 additions & 8 deletions coretex/configuration/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@ def __init__(self, raw: Dict[str, Any]) -> None:
def getConfigPath(cls) -> Path:
pass

@abstractmethod
def _isConfigured(self) -> bool:
pass

@abstractmethod
def _isConfigValid(self) -> Tuple[bool, List[str]]:
pass
Expand All @@ -71,10 +67,6 @@ def load(cls) -> Self:
config = cls(raw)

isValid, errors = config._isConfigValid()

if not config._isConfigured():
raise ConfigurationNotFound("Configuration not found.")

if not isValid:
raise InvalidConfiguration("Invalid configuration found.", errors)

Expand Down
6 changes: 3 additions & 3 deletions coretex/configuration/config_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
DOCKER_CONTAINER_NAME = "coretex_node"
DOCKER_CONTAINER_NETWORK = "coretex_node"
DEFAULT_STORAGE_PATH = str(Path.home() / ".coretex")
DEFAULT_RAM_MEMORY = getAvailableRam()
MINIMUM_RAM_MEMORY = 6
DEFAULT_SWAP_MEMORY = DEFAULT_RAM_MEMORY * 2
DEFAULT_RAM = getAvailableRam()
MINIMUM_RAM = 6
DEFAULT_SWAP_MEMORY = DEFAULT_RAM * 2
DEFAULT_SHARED_MEMORY = 2
DEFAULT_CPU_COUNT = cpuCount if cpuCount is not None else 0
DEFAULT_NODE_MODE = NodeMode.execution
Expand Down
152 changes: 106 additions & 46 deletions coretex/configuration/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ def getEnvVar(key: str, default: str) -> str:
return os.environ[key]


class DockerConfigurationException(Exception):
pass


class NodeConfiguration(BaseConfiguration):

def __init__(self, raw: Dict[str, Any]) -> None:
Expand Down Expand Up @@ -87,7 +91,7 @@ def nodeRam(self) -> int:
nodeRam = self.getOptValue("nodeRam", int)

if nodeRam is None:
nodeRam = config_defaults.DEFAULT_RAM_MEMORY
nodeRam = config_defaults.DEFAULT_RAM

return nodeRam

Expand Down Expand Up @@ -195,78 +199,134 @@ def endpointInvocationPrice(self) -> Optional[float]:
def endpointInvocationPrice(self, value: Optional[float]) -> None:
self._raw["endpointInvocationPrice"] = value

def _isConfigured(self) -> bool:
return (
isinstance(self._raw.get("nodeName"), str) and
isinstance(self._raw.get("image"), str) and
isinstance(self._raw.get("nodeAccessToken"), str) and
isinstance(self._raw.get("cpuCount"), int) and
isinstance(self._raw.get("nodeRam"), int) and
isinstance(self._raw.get("nodeSwap"), int) and
isinstance(self._raw.get("nodeSharedMemory"), int) and
isinstance(self._raw.get("nodeMode"), int)
)

def _isConfigValid(self) -> Tuple[bool, List[str]]:
def validateRamField(self, ramLimit: int) -> Tuple[bool, int, str]:
isValid = True
errorMessages = []
cpuLimit, ramLimit = docker.getResourceLimits()
swapLimit = docker.getDockerSwapLimit()
message = ""

if ramLimit < config_defaults.MINIMUM_RAM:
isValid = False
raise DockerConfigurationException(
f"Minimum Node RAM requirement ({config_defaults.MINIMUM_RAM}GB) "
f"is higher than your current Docker desktop RAM limit ({ramLimit}GB). "
"Please adjust resource limitations in Docker Desktop settings to match Node requirements."
)

defaultRamValue = int(min(max(config_defaults.MINIMUM_RAM, ramLimit), config_defaults.DEFAULT_RAM))

if not isinstance(self._raw.get("nodeRam"), int):
errorMessages.append(
isValid = False
message = (
f"Invalid config \"nodeRam\" field type \"{type(self._raw.get('nodeRam'))}\". Expected: \"int\""
f"Using default value of {defaultRamValue} GB"
)

if self.nodeRam < config_defaults.MINIMUM_RAM:
isValid = False
message = (
f"WARNING: Minimum Node RAM requirement ({config_defaults.MINIMUM_RAM}GB) "
f"is higher than the configured value ({self._raw.get('nodeRam')}GB)"
f"Overriding \"nodeRam\" field to match node RAM requirements."
)

if self.nodeRam > ramLimit:
isValid = False
message = (
f"WARNING: RAM limit in Docker Desktop ({ramLimit}GB) "
f"is lower than the configured value ({self._raw.get('nodeRam')}GB)"
f"Overriding \"nodeRam\" field to limit in Docker Desktop."
)

return isValid, defaultRamValue, message

def validateCPUCount(self, cpuLimit: int) -> Tuple[bool, int, str]:
isValid = True
message = ""
defaultCPUCount = config_defaults.DEFAULT_CPU_COUNT if config_defaults.DEFAULT_CPU_COUNT <= cpuLimit else cpuLimit

if not isinstance(self._raw.get("cpuCount"), int):
errorMessages.append(
isValid = False
message = (
f"Invalid config \"cpuCount\" field type \"{type(self._raw.get('cpuCount'))}\". Expected: \"int\""
f"Using default value of {defaultCPUCount} cores"
)

if self.cpuCount > cpuLimit:
isValid = False
message = (
f"WARNING: CPU limit in Docker Desktop ({cpuLimit}) "
f"is lower than the configured value ({self._raw.get('cpuCount')})"
f"Overriding \"cpuCount\" field to limit in Docker Desktop."
)

return isValid, cpuLimit, message

def validateSWAPMemory(self, swapLimit: int) -> Tuple[bool, int, str]:
isValid = True
message = ""
defaultSWAPMemory = config_defaults.DEFAULT_SWAP_MEMORY if config_defaults.DEFAULT_SWAP_MEMORY <= swapLimit else swapLimit

if not isinstance(self._raw.get("nodeSwap"), int):
errorMessages.append(
isValid = False
message = (
f"Invalid config \"nodeSwap\" field type \"{type(self._raw.get('nodeSwap'))}\". Expected: \"int\""
f"Using default value of {defaultSWAPMemory} GB"
)
isValid = False

if self.cpuCount > cpuLimit:
errorMessages.append(
f"Configuration not valid. CPU limit in Docker Desktop ({cpuLimit}) "
"is lower than the configured value ({self._raw.get('cpuCount')})"
if self.nodeSwap > swapLimit:
isValid = False
message = (
f"WARNING: SWAP limit in Docker Desktop ({swapLimit}GB) "
f"is lower than the configured value ({self.nodeSwap}GB)"
f"Overriding \"nodeSwap\" field to limit in Docker Desktop."
)

return isValid, defaultSWAPMemory, message

def _isConfigValid(self) -> Tuple[bool, List[str]]:
isValid = True
errorMessages = []
cpuLimit, ramLimit = docker.getResourceLimits()
swapLimit = docker.getDockerSwapLimit()

if not isinstance(self._raw.get("nodeName"), str):
isValid = False
errorMessages.append("Required field \"nodeName\" missing")

if ramLimit < config_defaults.MINIMUM_RAM_MEMORY:
errorMessages.append(
f"Minimum Node RAM requirement ({config_defaults.MINIMUM_RAM_MEMORY}GB) "
"is higher than your current Docker desktop RAM limit ({ramLimit}GB). "
"Please adjust resource limitations in Docker Desktop settings to match Node requirements."
)
if not isinstance(self._raw.get("image"), str):
isValid = False
errorMessages.append("Required field \"image\" missing")

if self.nodeRam > ramLimit:
errorMessages.append(
f"Configuration not valid. RAM limit in Docker Desktop ({ramLimit}GB) "
"is lower than the configured value ({self._raw.get('nodeRam')}GB)"
)
if not isinstance(self._raw.get("nodeAccessToken"), str):
isValid = False
errorMessages.append("Required field \"nodeAccessToken\" missing")

if self.nodeRam < config_defaults.MINIMUM_RAM_MEMORY:
errorMessages.append(
f"Configuration not valid. Minimum Node RAM requirement ({config_defaults.MINIMUM_RAM_MEMORY}GB) "
"is higher than the configured value ({self._raw.get('nodeRam')}GB)"
)
if not isinstance(self._raw.get("nodeName"), str):
errorMessages.append(f"Invalid configuration. Missing required field \"nodeName\".")
isValid = False

if self.nodeSwap > swapLimit:
errorMessages.append(
f"Configuration not valid. SWAP limit in Docker Desktop ({swapLimit}GB) "
"is lower than the configured value ({self.nodeSwap}GB)"
)
if not isinstance(self._raw.get("image"), str):
errorMessages.append(f"Invalid configuration. Missing required field \"image\".")
isValid = False

if not isinstance(self._raw.get("nodeAccessToken"), str):
errorMessages.append(f"Invalid configuration. Missing required field \"nodeAccessToken\".")
isValid = False

isRamValid, nodeRam, message = self.validateRamField(ramLimit)
if not isRamValid:
errorMessages.append(message)
self.nodeRam = nodeRam

isCPUCountValid, cpuCount, message = self.validateCPUCount(cpuLimit)
if not isCPUCountValid:
errorMessages.append(message)
self.cpuCount = cpuCount

isSWAPMemoryValid, nodeSwap, message = self.validateSWAPMemory(swapLimit)
if not isSWAPMemoryValid:
errorMessages.append(message)
self.nodeSwap = nodeSwap

return isValid, errorMessages

def getInitScriptPath(self) -> Optional[Path]:
Expand Down
13 changes: 2 additions & 11 deletions coretex/configuration/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,8 @@

from pathlib import Path
from typing import Dict, Any, List, Optional, Tuple
from typing_extensions import Self
from datetime import datetime, timezone

import os

from .base import BaseConfiguration, CONFIG_DIR
from ..utils import decodeDate

Expand All @@ -34,9 +31,6 @@ class UserConfiguration(BaseConfiguration):
def __init__(self, raw: Dict[str, Any]) -> None:
super().__init__(raw)

if not "CTX_API_URL" in os.environ:
os.environ["CTX_API_URL"] = self.serverUrl

@classmethod
def getConfigPath(cls) -> Path:
return CONFIG_DIR / "user_config.json"
Expand Down Expand Up @@ -105,20 +99,17 @@ def projectId(self) -> Optional[int]:
def projectId(self, value: Optional[int]) -> None:
self._raw["projectId"] = value

def _isConfigured(self) -> bool:
return self._raw.get("username") is not None and self._raw.get("password") is not None

def _isConfigValid(self) -> Tuple[bool, List[str]]:
isValid = True
errorMessages = []

if self._raw.get("username") is None or not isinstance(self._raw.get("username"), str):
errorMessages.append("Field \"username\" not found in configuration.")
isValid = False
errorMessages.append("Missing required field \"username\" in user configuration.")

if self._raw.get("password") is None or not isinstance(self._raw.get("password"), str):
errorMessages.append("Field \"password\" not found in configuration.")
isValid = False
errorMessages.append("Missing required field \"password\" in user configuration.")

return isValid, errorMessages

Expand Down

0 comments on commit a0594c3

Please sign in to comment.