Skip to content

Commit a5f20f1

Browse files
authored
Merge pull request #797 from powerapi-ng/feat/processor-lazy-import
feat: Improve handling of pre-processors with missing dependencies
2 parents d169865 + 2df2f7c commit a5f20f1

18 files changed

+303
-461
lines changed

src/powerapi/cli/generator.py

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@
3535
from powerapi.exception import PowerAPIException, ModelNameAlreadyUsed, DatabaseNameDoesNotExist, ModelNameDoesNotExist, \
3636
DatabaseNameAlreadyUsed, ProcessorTypeDoesNotExist, ProcessorTypeAlreadyUsed
3737
from powerapi.filter import ReportFilter
38-
from powerapi.processor.pre.k8s import K8sPreProcessorActor, K8sProcessorConfig
39-
from powerapi.processor.pre.openstack import OpenStackPreProcessorActor
4038
from powerapi.processor.processor_actor import ProcessorActor
4139
from powerapi.puller import PullerActor
4240
from powerapi.pusher import PusherActor
@@ -375,14 +373,13 @@ class ProcessorGenerator(Generator):
375373
Generator that initializes the processor actor(s) from the configuration.
376374
"""
377375

378-
def __init__(self, component_group_name: str, processor_factory: dict[str, Callable[[dict], ProcessorActor]]):
376+
def __init__(self, component_group_name: str):
379377
"""
380378
:param component_group_name: Name of the component group
381-
:param processor_factory: Dictionary mapping processor type to actor factory
382379
"""
383380
super().__init__(component_group_name)
384381

385-
self.processor_factory = processor_factory
382+
self.processor_factory: dict[str, Callable[[dict], ProcessorActor]] = {}
386383

387384
def remove_processor_factory(self, processor_type: str) -> None:
388385
"""
@@ -405,6 +402,14 @@ def add_processor_factory(self, processor_type: str, processor_factory_function:
405402

406403
self.processor_factory[processor_type] = processor_factory_function
407404

405+
def _generate_processor(self, processor_name: str, component_config: dict) -> ProcessorActor:
406+
try:
407+
return self.processor_factory[processor_name](component_config)
408+
except KeyError as exn:
409+
raise PowerAPIException('Configuration error: Invalid processor type: %s', processor_name) from exn
410+
except ImportError as exn:
411+
raise PowerAPIException('Dependencies for %s processor are not installed', processor_name) from exn
412+
408413
def _gen_actor(self, component_config: dict, main_config: dict, component_name: str) -> ProcessorActor:
409414
"""
410415
Helper method to generate a processor actor from the given configuration.
@@ -414,12 +419,9 @@ def _gen_actor(self, component_config: dict, main_config: dict, component_name:
414419
:return: Processor actor
415420
"""
416421
processor_actor_type = component_config[COMPONENT_TYPE_KEY]
417-
if processor_actor_type not in self.processor_factory:
418-
raise PowerAPIException(f'Configuration error: Unknown processor actor type: {processor_actor_type}')
419-
420422
component_config[ACTOR_NAME_KEY] = component_name
421423
component_config[GENERAL_CONF_VERBOSE_KEY] = main_config[GENERAL_CONF_VERBOSE_KEY]
422-
return self.processor_factory[processor_actor_type](component_config)
424+
return self._generate_processor(processor_actor_type, component_config)
423425

424426

425427
class PreProcessorGenerator(ProcessorGenerator):
@@ -428,39 +430,35 @@ class PreProcessorGenerator(ProcessorGenerator):
428430
"""
429431

430432
def __init__(self):
431-
super().__init__('pre-processor', self._get_default_processor_factories())
433+
super().__init__('pre-processor')
434+
435+
self.add_processor_factory('k8s', self._k8s_pre_processor_factory)
436+
self.add_processor_factory('openstack', self._openstack_pre_processor_factory)
432437

433438
@staticmethod
434-
def _k8s_pre_processor_factory(processor_config: dict) -> K8sPreProcessorActor:
439+
def _k8s_pre_processor_factory(processor_config: dict) -> ProcessorActor:
435440
"""
436441
Kubernetes pre-processor actor factory.
437442
:param processor_config: Pre-Processor configuration
438443
:return: Configured Kubernetes pre-processor actor
439444
"""
445+
from powerapi.processor.pre.k8s.actor import K8sPreProcessorActor, K8sProcessorConfig
440446
name = processor_config[ACTOR_NAME_KEY]
441-
api_mode = processor_config.get(K8S_API_MODE_KEY, 'manual') # use manual mode by default
447+
api_mode = processor_config[K8S_API_MODE_KEY]
442448
api_host = processor_config.get(K8S_API_HOST_KEY, None)
443449
api_key = processor_config.get(K8S_API_KEY_KEY, None)
444450
level_logger = logging.DEBUG if processor_config[GENERAL_CONF_VERBOSE_KEY] else logging.INFO
445451
config = K8sProcessorConfig(api_mode, api_host, api_key)
446452
return K8sPreProcessorActor(name, config, level_logger)
447453

448454
@staticmethod
449-
def _openstack_pre_processor_factory(processor_config: dict) -> OpenStackPreProcessorActor:
455+
def _openstack_pre_processor_factory(processor_config: dict) -> ProcessorActor:
450456
"""
451457
Openstack pre-processor actor factory.
452458
:param processor_config: Pre-Processor configuration
453459
:return: Configured OpenStack pre-processor actor
454460
"""
461+
from powerapi.processor.pre.openstack.actor import OpenStackPreProcessorActor
455462
name = processor_config[ACTOR_NAME_KEY]
456463
level_logger = logging.DEBUG if processor_config[GENERAL_CONF_VERBOSE_KEY] else logging.INFO
457464
return OpenStackPreProcessorActor(name, level_logger)
458-
459-
def _get_default_processor_factories(self) -> dict[str, Callable[[dict], ProcessorActor]]:
460-
"""
461-
Return the default pre-processors factory.
462-
"""
463-
return {
464-
'k8s': self._k8s_pre_processor_factory,
465-
'openstack': self._openstack_pre_processor_factory
466-
}

src/powerapi/processor/pre/k8s/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,3 @@
2626
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
2727
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2828
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29-
30-
from .actor import K8sPreProcessorActor, K8sProcessorConfig

src/powerapi/processor/pre/openstack/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,3 @@
2727
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2828
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2929

30-
from powerapi.processor.pre.openstack.actor import OpenStackPreProcessorActor

tests/unit/cli/conftest.py

Lines changed: 1 addition & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -80,23 +80,6 @@ def several_inputs_outputs_stream_socket_without_some_arguments_config(several_i
8080
return several_inputs_outputs_stream_config
8181

8282

83-
@pytest.fixture
84-
def several_k8s_pre_processors_config():
85-
"""
86-
Configuration with several k8s processors
87-
"""
88-
return load_configuration_from_json_file(file_name='several_k8s_pre_processors_configuration.json')
89-
90-
91-
@pytest.fixture
92-
def several_k8s_pre_processors_without_some_arguments_config():
93-
"""
94-
Configuration with several k8s processors
95-
"""
96-
return load_configuration_from_json_file(
97-
file_name='several_k8s_pre_processors_without_some_arguments_configuration.json')
98-
99-
10083
@pytest.fixture
10184
def csv_io_postmortem_config(invalid_csv_io_stream_config):
10285
"""
@@ -146,14 +129,6 @@ def config_without_output(csv_io_postmortem_config):
146129
return csv_io_postmortem_config
147130

148131

149-
@pytest.fixture
150-
def k8s_pre_processor_config():
151-
"""
152-
Configuration with k8s as pre-processor
153-
"""
154-
return load_configuration_from_json_file(file_name='k8s_pre_processor_configuration.json')
155-
156-
157132
@pytest.fixture
158133
def subgroup_parser():
159134
"""
@@ -427,124 +402,12 @@ def empty_pre_processor_config(pre_processor_complete_configuration):
427402
return pre_processor_complete_configuration
428403

429404

430-
@pytest.fixture(params=['k8s_pre_processor_wrong_binding_configuration.json'])
431-
def pre_processor_wrong_binding_configuration(request):
432-
"""
433-
Return a dictionary containing wrong bindings with a pre-processor
434-
"""
435-
return load_configuration_from_json_file(file_name=request.param)
436-
437-
438405
@pytest.fixture(params=['k8s_pre_processor_with_non_existing_puller_configuration.json'])
439406
def pre_processor_with_unexisting_puller_configuration(request):
440407
"""
441408
Return a dictionary containing a pre-processor with a puller that doesn't exist
442409
"""
443-
return load_configuration_from_json_file(
444-
file_name=request.param)
445-
446-
447-
@pytest.fixture(params=['k8s_pre_processor_with_reused_puller_in_bindings_configuration.json'])
448-
def pre_processor_with_reused_puller_in_bindings_configuration(request):
449-
"""
450-
Return a dictionary containing a pre-processor with a puller that doesn't exist
451-
"""
452-
return load_configuration_from_json_file(
453-
file_name=request.param)
454-
455-
456-
@pytest.fixture
457-
def pre_processor_pullers_and_processors_dictionaries(pre_processor_complete_configuration):
458-
"""
459-
Return a dictionary which contains puller actors, a dictionary of processors as well as the pushers
460-
"""
461-
return get_pre_processor_pullers_and_processors_dictionaries_from_configuration(
462-
configuration=pre_processor_complete_configuration)
463-
464-
465-
def get_pre_processor_pullers_and_processors_dictionaries_from_configuration(configuration: dict) -> (dict, dict, dict):
466-
"""
467-
Return a tuple of dictionaries (pullers, processors) created from the given configuration.
468-
:param dict configuration : Dictionary containing the configuration
469-
"""
470-
report_filter = BroadcastReportFilter()
471-
puller_generator = PullerGenerator(report_filter=report_filter)
472-
pullers = puller_generator.generate(main_config=configuration)
473-
474-
pusher_generator = PusherGenerator()
475-
pushers = pusher_generator.generate(main_config=configuration)
476-
477-
route_table = RouteTable()
478-
479-
dispatcher = DispatcherActor('dispatcher', None, pushers, route_table)
480-
481-
report_filter.register(lambda msg: True, dispatcher.get_proxy())
482-
483-
processor_generator = PreProcessorGenerator()
484-
processors = processor_generator.generate(main_config=configuration)
485-
486-
return pullers, processors, pushers
487-
488-
489-
@pytest.fixture
490-
def pre_processor_binding_manager(pre_processor_complete_configuration, pre_processor_pullers_and_processors_dictionaries):
491-
"""
492-
Return a ProcessorBindingManager with a Processor
493-
"""
494-
pullers = pre_processor_pullers_and_processors_dictionaries[0]
495-
processors = pre_processor_pullers_and_processors_dictionaries[1]
496-
497-
return PreProcessorBindingManager(
498-
config=pre_processor_complete_configuration,
499-
pullers=pullers,
500-
processors=processors
501-
)
502-
503-
504-
@pytest.fixture
505-
def pre_processor_binding_manager_with_wrong_binding_types(pre_processor_wrong_binding_configuration):
506-
"""
507-
Return a PreProcessorBindingManager with wrong target for the pre-processor (a pusher instead of a puller)
508-
"""
509-
_, processors, pushers = get_pre_processor_pullers_and_processors_dictionaries_from_configuration(
510-
configuration=pre_processor_wrong_binding_configuration)
511-
512-
return PreProcessorBindingManager(
513-
config=pre_processor_wrong_binding_configuration,
514-
pullers=pushers,
515-
processors=processors
516-
)
517-
518-
519-
@pytest.fixture
520-
def pre_processor_binding_manager_with_unexisting_puller(pre_processor_with_unexisting_puller_configuration):
521-
"""
522-
Return a PreProcessorBindingManager with an unexisting target for the pre-processor (a puller that doesn't exist)
523-
"""
524-
pullers, processors, _ = get_pre_processor_pullers_and_processors_dictionaries_from_configuration(
525-
configuration=pre_processor_with_unexisting_puller_configuration)
526-
527-
return PreProcessorBindingManager(
528-
config=pre_processor_with_unexisting_puller_configuration,
529-
pullers=pullers,
530-
processors=processors
531-
)
532-
533-
534-
@pytest.fixture
535-
def pre_processor_binding_manager_with_reused_puller_in_bindings(
536-
pre_processor_with_reused_puller_in_bindings_configuration):
537-
"""
538-
Return a PreProcessorBindingManager with a puller used by two different pre-processors
539-
"""
540-
pullers, processors, _ = get_pre_processor_pullers_and_processors_dictionaries_from_configuration(
541-
configuration=pre_processor_with_reused_puller_in_bindings_configuration)
542-
543-
return PreProcessorBindingManager(
544-
config=pre_processor_with_reused_puller_in_bindings_configuration,
545-
pullers=pullers,
546-
processors=processors
547-
)
410+
return load_configuration_from_json_file(request.param)
548411

549412

550413
def get_config_with_longest_argument_names(config: dict, arguments: dict):

0 commit comments

Comments
 (0)