Commit 4df0a1e4 authored by Yann Garcia's avatar Yann Garcia
Browse files

Merge latest code from ACME

parent bcce4508
Loading
Loading
Loading
Loading
+24 −23
Original line number Diff line number Diff line
@@ -32,7 +32,7 @@ from ..runtime.Console import Console

from ..services.Dispatcher import Dispatcher
from ..services.RequestManager import RequestManager
from ..services.EventManager import EventManager
from .EventManager import EventManager
from ..services.GroupManager import GroupManager
from ..runtime.Importer import Importer
from ..services.LocationManager import LocationManager
@@ -163,8 +163,8 @@ def startup(args:argparse.Namespace, **kwargs:Dict[str, Any]) -> bool:
			False if the CSE couldn't initialized and started. 
	"""
	global action, announce, coapServer, console, dispatcher, event, groupResource, httpServer, importer, location, mqttClient, mecClient
	global notification, pluginManager, registration, remote, request, script, security, semantic, statistics, storage, textUI, time
	global timeSeries, validator, webSocketServer
	global notification, pluginManager, registration, remote, request, script, security, semantic
	global statistics, storage, textUI, time, timeSeries, validator, webSocketServer

	# Set status
	RC.cseStatus = CSEStatus.STARTING
@@ -207,8 +207,11 @@ def startup(args:argparse.Namespace, **kwargs:Dict[str, Any]) -> bool:
	try:
		textUI = TextUI()						# Start the textUI
		console = Console()						# Start the console

		storage = Storage()						# Initialize the resource storage

		importer = Importer()					# Initialize the importer
		importer.importResourcePolicies()		# Before initializing other components, import the resource policies

		statistics = Statistics()				# Initialize the statistics system
		registration = RegistrationManager()	# Initialize the registration manager
		validator = Validator()					# Initialize the resource validator
@@ -230,6 +233,13 @@ def startup(args:argparse.Namespace, **kwargs:Dict[str, Any]) -> bool:
		script = ScriptManager()				# Initialize the script manager
		action = ActionManager()				# Initialize the action manager

		# Import attribute, flexContainer and enum policies, and configuration documentation
		#
		# After this, the CSE reads the scripts from the default and runtime init directories.
		# It also runs the init script, if there is one. 
		#


		if Configuration.mec_enable:
				L.log('Initializing MEC client')
				if Configuration.mqtt_websocket_enable:
@@ -239,22 +249,8 @@ def startup(args:argparse.Namespace, **kwargs:Dict[str, Any]) -> bool:
				if mecClient is None:
					Configuration.mec_enable = False

		# → Experimental late loading
		#
		# import importlib
		# mod = importlib.import_module('acme.services.ActionManager')
		# action = mod.ActionManager()	

		# mod = importlib.import_module('acme.runtime.ScriptManager')			# Initialize the action manager
		# # script = mod.ScriptManager()				# Initialize the script manager
		# thismodule = sys.modules[__name__]
		# setattr(thismodule, 'script', mod.ScriptManager())

		# Import a default set of resources, e.g. the CSE, first ACP or resource structure
		# Import extra attribute policies for specializations first
		# When this fails, we cannot continue with the CSE startup
		importer = Importer()
		if not importer.doImport():
		if not importer.importPolicies() or not importer.importScripts():
			RC.cseStatus = CSEStatus.STOPPED
			return False
		
@@ -351,6 +347,7 @@ def _shutdown() -> None:
	"""
	if RC.cseStatus not in [CSEStatus.RUNNING, CSEStatus.SHUTTINGDOWNRESTART]:
		return
	L.console('CSE shutting down now', nlb=True)
	
	# The status STOPPINGRESTART is used to indicate that the CSE is shutting down to restart.
	# This is a normal shutdown but in the end the CSE process will return with a special exit code
@@ -364,6 +361,7 @@ def _shutdown() -> None:
	
	# shutdown the services
	pluginManager and pluginManager.shutdown()

	textUI and textUI.shutdown()
	console and console.shutdown()
	time and time.shutdown()
@@ -451,7 +449,10 @@ def resetCSE() -> None:
		# The following event is executed synchronously to give every component
		# a chance to finish
		event.cseReset()	# type: ignore [attr-defined]
		if not importer.doImport():

		# We only import policies, documentation and scripts during restart
		# But we don't import the resource policies again.
		if not importer.importPolicies() or not importer.importScripts():
			textUI and textUI.shutdown()
			L.logErr('Error during import')
			sys.exit()	# what else can we do?
+41 −14
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@ from typing import Any, Dict, Tuple, Optional, cast, Set

import configparser, argparse, os, os.path, pathlib
from copy import deepcopy
import time
from inspect import getmembers
from dotenv import load_dotenv, find_dotenv

@@ -211,7 +212,6 @@ class Configuration(object):
	"""	Replace existing plugins with the same name. """



	cse_registrars:dict[str, CSERegistrar] = {}
	"""	A dictionary of CSE or service provider CSEs registrars. The keys are the CSE IDs, the values are dictionaries with the registrar information. """

@@ -228,6 +228,8 @@ class Configuration(object):
	cse_registration_checkInterval:int
	"""	Time interval to check liveliness of registration(s). """

	cse_registration_unregisterWhenStopping:bool
	"""	Unregister the CSR resource when stopping the CSE. """

	cse_security_secret:str
	"""	The main secret key for the CSE. """
@@ -707,6 +709,27 @@ class Configuration(object):
			Console().print(msg, markup=markup)	# Print error message to console


	@staticmethod
	def _warning(msg:str) -> None:
		"""	Print a warning message to the console.

			Args:
				msg: The warning message to print.
		"""
		Configuration._print(f'[orange3][b u]Configuration Warning[/b u]\n{msg}[/orange3]\n')


	@staticmethod
	def _error(msg:str) -> None:
		"""	Print an error message to the console.

			Args:
				msg: The error message to print.
		"""
		Configuration._print(f'[red][b u]Configuration Error[/b u]\n{msg}[/red]\n')



	@staticmethod
	def initDirectories() -> bool:
		"""	Initialize the directories for the configuration. This method must be called before accessing any configuration value.
@@ -720,7 +743,7 @@ class Configuration(object):
		
		# Test that the config filename is just a filename without a path. If it is then throw an error
		if Configuration._args_configfile and os.path.dirname(Configuration._args_configfile):
			Configuration._print(f'[red]Configuration file must be a filename without a path: {Configuration._args_configfile}')
			Configuration._error(f'Configuration file must be a filename without a path: {Configuration._args_configfile}')
			return False

		# Find out the path to the init directory
@@ -737,7 +760,7 @@ class Configuration(object):
		Configuration._defaultConfigFilePath = Configuration.initDirectory / C.defaultConfigFile
		Configuration._defaultConfigFile = str(Configuration._defaultConfigFilePath)
		if not os.access(Configuration._defaultConfigFile, os.R_OK):
			Configuration._print(f'[red]Default configuration file missing or not readable: {Configuration._defaultConfigFile}')
			Configuration._error(f'Default configuration file missing or not readable: {Configuration._defaultConfigFile}')
			return False

		return True
@@ -869,7 +892,7 @@ class Configuration(object):
			except Exception as e:
				import traceback
				traceback.print_exc()
				Configuration._print(f'[red]Error connecting to Zookeeper server: {e}')
				Configuration._error(f'Error connecting to Zookeeper server: {e}')
				return False
			finally:
				zk.disconnect()
@@ -902,6 +925,11 @@ class Configuration(object):
		# Add environment variables to the defaults
		_defaults.update({ 'DEFAULT': {k: v.replace('$', '$$') for k,v in os.environ.items()} })

		# Check wether none of the environment variables has the same name as any of the default values in "basic.config"
		for k in _defaults['basic.config'].keys():
			if k in os.environ:
				Configuration._warning(fr'The environment variable "{k}" conflicts with an option with the same name in the section \[[i]basic.config[/i]].\nPlease consider renaming the environment variable otherwise it cannot be used for interpolation in that section.')

		# Add (empty) default for supported environment variables to the defaults dictionary for the interpolation during reading the configuration file
		_envVariables = { e: os.getenv(e, '') if e not in _defaults else _defaults[e]
			for e in (
@@ -929,7 +957,7 @@ class Configuration(object):
			# Read the configuration files
			# if len(config.read( [Configuration._defaultConfigFile, Configuration.configfile])) == 0 and Configuration._args_configfile != C.defaultUserConfigFile:		# Allow
			if len(Configuration.configParser.read(configurationFiles)) == 0 and Configuration._args_configfile != C.defaultUserConfigFile:		# Allow
				Configuration._print(f'[red]Configuration file missing or not readable: {Configuration._args_configfile}')
				Configuration._error(f'Configuration file missing or not readable: {Configuration._args_configfile}')
				return False
			
			# Read the extra configuration strings (e.g. from Zookeeper)
@@ -937,16 +965,14 @@ class Configuration(object):
				Configuration.configParser.read_string(cs)

		except configparser.Error as e:
			Configuration._print('[red]Error in configuration file')
			Configuration._print(str(e))
			Configuration._error(f'Error in configuration file\n{str(e)}')
			return False
		
		
		#	Look for deprecated and renamed sections and print an error message
		if _deprecatedSections:
			for o, n in _deprecatedSections:
				if Configuration.configParser.has_section(o):
					Configuration._print(fr'[red]Found old section name in configuration file. Please rename "\[{o}]" to "\[{n}]".')
					Configuration._error(fr'Found old section name in configuration file. Please rename "\[{o}]" to "\[{n}]".')
					return False

		#	Retrieve configuration values
@@ -960,12 +986,11 @@ class Configuration(object):
				m.readConfiguration(Configuration.configParser, Configuration)	# type:ignore [arg-type]
		
		except configparser.InterpolationMissingOptionError as e:
			Configuration._print(f'[red]Error in configuration file: {Configuration.configfile}\n{str(e)}')
			Configuration._print('\n[red]Please provide the option in the section [bold](basic.config)[/bold] in the configuration file or set an environment variable with that name.\n')
			Configuration._error(fr'Error in configuration file: {Configuration.configfile}\n{str(e)}\n\nPlease provide this configuration option in the section \[[i]basic.config[/i]], or set an environment variable with that name.\n')
			return False

		except Exception as e:	# about when findings errors in configuration
			Configuration._print(f'[red]Error in configuration file: {Configuration.configfile}\n{str(e)}')
			Configuration._error(f'Error in configuration file: {Configuration.configfile}\n{str(e)}')
			return False

		# Validate the configuration for each module
@@ -973,7 +998,7 @@ class Configuration(object):
			try:
				m.validateConfiguration(Configuration, True)	# type:ignore [arg-type]
			except ConfigurationError as e:
				Configuration._print(f'[red]{str(e)}')
				Configuration._error(f'{str(e)}')
				return False

		return True
@@ -1008,9 +1033,11 @@ class Configuration(object):
			attr = getattr(Configuration, k)
			match attr:
				case pathlib.Path():
					# Convert pathlib.Path to string
					attr = str(attr)
				case dict():
					# Don't change the original dict, so make a copy
					attr = deepcopy(attr)
				case dict():
					# Convert dict elements to instances dict, if necessary
					for k2,v2 in attr.items():
						if isinstance(v2, CSERegistrar):