Skip to content

Commit a9ebac9

Browse files
authored
Merge pull request #659 from powerapi-ng/feat/openstack-platform
feat: Add OpenStack platform support
2 parents f1dc23c + 09340fb commit a9ebac9

File tree

8 files changed

+377
-1
lines changed

8 files changed

+377
-1
lines changed

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,11 @@ prometheus = ["prometheus-client >= 0.9.0"]
4141

4242
# Plaforms:
4343
kubernetes = ["kubernetes >= 27.0.2"]
44+
openstack = ["openstacksdk >= 4.7.0"]
4445

4546
# Aliases:
4647
all-databases = ["powerapi[mongodb, influxdb, opentsdb, prometheus]"]
47-
all-platforms = ["powerapi[kubernetes]"]
48+
all-platforms = ["powerapi[kubernetes, openstack]"]
4849
everything = ["powerapi[all-databases, all-platforms]"]
4950

5051
[dependency-groups]

src/powerapi/cli/common_cli_parsing_manager.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,11 @@ def __init__(self):
343343
subgroup_parser=subparser_k8s_pre_processor
344344
)
345345

346+
subparser_openstack_pre_processor = SubgroupConfigParsingManager("openstack")
347+
subparser_openstack_pre_processor.add_argument("p", "puller", help_text="Name of the puller to attach the pre-processor to", is_mandatory=True)
348+
subparser_openstack_pre_processor.add_argument("n", "name", help_text="Name of the pre-processor", default_value='preprocessor_openstack')
349+
self.add_subgroup_parser("pre-processor", subparser_openstack_pre_processor)
350+
346351
def parse_argv(self):
347352
"""
348353
Parse command line arguments.

src/powerapi/cli/generator.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
DatabaseNameAlreadyUsed, ProcessorTypeDoesNotExist, ProcessorTypeAlreadyUsed
4242
from powerapi.filter import Filter
4343
from powerapi.processor.pre.k8s import K8sPreProcessorActor
44+
from powerapi.processor.pre.openstack import OpenStackPreProcessorActor
4445
from powerapi.processor.processor_actor import ProcessorActor
4546
from powerapi.puller import PullerActor
4647
from powerapi.pusher import PusherActor
@@ -428,12 +429,25 @@ def _k8s_pre_processor_factory(processor_config: dict) -> K8sPreProcessorActor:
428429
level_logger = logging.DEBUG if processor_config[GENERAL_CONF_VERBOSE_KEY] else logging.INFO
429430
return K8sPreProcessorActor(name, [], target_actors_name, api_mode, api_host, api_key, level_logger)
430431

432+
@staticmethod
433+
def _openstack_pre_processor_factory(processor_config: dict) -> OpenStackPreProcessorActor:
434+
"""
435+
Openstack pre-processor actor factory.
436+
:param processor_config: Pre-Processor configuration
437+
:return: Configured OpenStack pre-processor actor
438+
"""
439+
name = processor_config[ACTOR_NAME_KEY]
440+
target_actors_name = [processor_config[PULLER_NAME_KEY]]
441+
level_logger = logging.DEBUG if processor_config[GENERAL_CONF_VERBOSE_KEY] else logging.INFO
442+
return OpenStackPreProcessorActor(name, [], target_actors_name, level_logger)
443+
431444
def _get_default_processor_factories(self) -> dict[str, Callable[[dict], ProcessorActor]]:
432445
"""
433446
Return the default pre-processors factory.
434447
"""
435448
return {
436449
'k8s': self._k8s_pre_processor_factory,
450+
'openstack': self._openstack_pre_processor_factory
437451
}
438452

439453

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copyright (c) 2025, Inria
2+
# Copyright (c) 2025, University of Lille
3+
# All rights reserved.
4+
#
5+
# Redistribution and use in source and binary forms, with or without
6+
# modification, are permitted provided that the following conditions are met:
7+
#
8+
# * Redistributions of source code must retain the above copyright notice, this
9+
# list of conditions and the following disclaimer.
10+
#
11+
# * Redistributions in binary form must reproduce the above copyright notice,
12+
# this list of conditions and the following disclaimer in the documentation
13+
# and/or other materials provided with the distribution.
14+
#
15+
# * Neither the name of the copyright holder nor the names of its
16+
# contributors may be used to endorse or promote products derived from
17+
# this software without specific prior written permission.
18+
#
19+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
30+
from powerapi.processor.pre.openstack.actor import OpenStackPreProcessorActor
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright (c) 2025, Inria
2+
# Copyright (c) 2025, University of Lille
3+
# All rights reserved.
4+
#
5+
# Redistribution and use in source and binary forms, with or without
6+
# modification, are permitted provided that the following conditions are met:
7+
#
8+
# * Redistributions of source code must retain the above copyright notice, this
9+
# list of conditions and the following disclaimer.
10+
#
11+
# * Redistributions in binary form must reproduce the above copyright notice,
12+
# this list of conditions and the following disclaimer in the documentation
13+
# and/or other materials provided with the distribution.
14+
#
15+
# * Neither the name of the copyright holder nor the names of its
16+
# contributors may be used to endorse or promote products derived from
17+
# this software without specific prior written permission.
18+
#
19+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
30+
import re
31+
32+
LIBVIRT_INSTANCE_NAME_REGEX = re.compile(r"(instance-\d+)")
33+
34+
35+
def get_instance_name_from_libvirt_cgroup(target: str) -> str | None:
36+
"""
37+
Returns the instance name of the target.
38+
:param target: Cgroup path
39+
:return: Instance name (``instance-XXXXXXXX``)
40+
"""
41+
if "\\x" in target:
42+
# Some systems (cgroups v2 managed by systemd) escape special characters in the cgroup path.
43+
# Decoding the path is required in order to reliably extract the instance name.
44+
# For example: /sys/fs/cgroup/machine.slice/machine-qemu\\x2d3\\x2dinstance\\x2d00000003.scope/libvirt/emulator
45+
target = target.encode("utf-8").decode("unicode_escape")
46+
47+
match = LIBVIRT_INSTANCE_NAME_REGEX.match(target)
48+
if match:
49+
return match.group(1)
50+
51+
return None
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Copyright (c) 2023, INRIA
2+
# Copyright (c) 2023, University of Lille
3+
# All rights reserved.
4+
#
5+
# Redistribution and use in source and binary forms, with or without
6+
# modification, are permitted provided that the following conditions are met:
7+
#
8+
# * Redistributions of source code must retain the above copyright notice, this
9+
# list of conditions and the following disclaimer.
10+
#
11+
# * Redistributions in binary form must reproduce the above copyright notice,
12+
# this list of conditions and the following disclaimer in the documentation
13+
# and/or other materials provided with the distribution.
14+
#
15+
# * Neither the name of the copyright holder nor the names of its
16+
# contributors may be used to endorse or promote products derived from
17+
# this software without specific prior written permission.
18+
#
19+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
30+
import logging
31+
32+
from powerapi.actor import Actor
33+
from powerapi.message import StartMessage, PoisonPillMessage
34+
from powerapi.processor.pre.openstack.handlers import StartMessageHandler, PoisonPillMessageHandler, HWPCReportHandler
35+
from powerapi.processor.processor_actor import ProcessorState, ProcessorActor
36+
from powerapi.report import HWPCReport
37+
from .metadata_cache_manager import OpenStackMetadataCacheManager
38+
39+
40+
class OpenStackPreProcessorState(ProcessorState):
41+
"""
42+
State of the OpenStack pre-processor actor.
43+
"""
44+
45+
def __init__(self, actor: Actor, target_actors: list, target_actors_names: list):
46+
"""
47+
:param actor: Actor instance
48+
:param target_actors: list of target actors
49+
:param target_actors_names: list of target actor names
50+
"""
51+
super().__init__(actor, target_actors, target_actors_names)
52+
53+
self.metadata_cache_manager = OpenStackMetadataCacheManager()
54+
55+
56+
class OpenStackPreProcessorActor(ProcessorActor):
57+
"""
58+
Pre-Processor Actor that adds OpenStack related metadata to reports.
59+
"""
60+
61+
def __init__(self, name: str, target_actors: list, target_actors_names: list, level_logger: int = logging.WARNING):
62+
"""
63+
:param name: Name of the actor
64+
:param target_actors: List of target actors
65+
:param target_actors_names: List of target actor names
66+
:param level_logger: Logging level of the actor
67+
"""
68+
super().__init__(name, level_logger, 5000)
69+
70+
self.state = OpenStackPreProcessorState(self, target_actors, target_actors_names)
71+
72+
def setup(self):
73+
"""
74+
Set up the OpenStack pre-processor actor.
75+
"""
76+
self.add_handler(StartMessage, StartMessageHandler(self.state))
77+
self.add_handler(PoisonPillMessage, PoisonPillMessageHandler(self.state))
78+
79+
self.add_handler(HWPCReport, HWPCReportHandler(self.state))
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Copyright (c) 2023, INRIA
2+
# Copyright (c) 2023, University of Lille
3+
# All rights reserved.
4+
#
5+
# Redistribution and use in source and binary forms, with or without
6+
# modification, are permitted provided that the following conditions are met:
7+
#
8+
# * Redistributions of source code must retain the above copyright notice, this
9+
# list of conditions and the following disclaimer.
10+
#
11+
# * Redistributions in binary form must reproduce the above copyright notice,
12+
# this list of conditions and the following disclaimer in the documentation
13+
# and/or other materials provided with the distribution.
14+
#
15+
# * Neither the name of the copyright holder nor the names of its
16+
# contributors may be used to endorse or promote products derived from
17+
# this software without specific prior written permission.
18+
#
19+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
30+
from powerapi.handler import StartHandler, PoisonPillMessageHandler as PoisonPillHandler
31+
from powerapi.processor.handlers import ProcessorReportHandler
32+
from powerapi.report import HWPCReport
33+
from ._utils import get_instance_name_from_libvirt_cgroup
34+
from .metadata_cache_manager import ServerMetadata, MetadataSyncFailed
35+
36+
37+
class StartMessageHandler(StartHandler):
38+
"""
39+
Start message handler for the OpenStack processor actor.
40+
"""
41+
42+
def initialization(self):
43+
"""
44+
Initialize the OpenStack processor.
45+
"""
46+
for actor in self.state.target_actors:
47+
actor.connect_data()
48+
49+
self.state.monitor_agent.start()
50+
51+
52+
class PoisonPillMessageHandler(PoisonPillHandler):
53+
"""
54+
PoisonPill message handler for the OpenStack processor actor.
55+
"""
56+
57+
def teardown(self, soft: bool = False):
58+
"""
59+
Teardown the OpenStack processor.
60+
"""
61+
for actor in self.state.target_actors:
62+
actor.socket_interface.close()
63+
64+
65+
class HWPCReportHandler(ProcessorReportHandler):
66+
"""
67+
Generic report handler for the OpenStack processor actor.
68+
Used to add the server metadata (from the OpenStack API) to the processed report.
69+
"""
70+
71+
def try_get_server_metadata(self, sensor_name: str, instance_name: str) -> ServerMetadata | None:
72+
"""
73+
Try to get the server metadata from the cache.
74+
:param sensor_name: Name of the sensor
75+
:param instance_name: Name of the instance to fetch metadata for
76+
:return: Server metadata entry or None if not found
77+
"""
78+
server_metadata = None
79+
try:
80+
server_metadata = self.state.metadata_cache_manager.get_server_metadata(sensor_name, instance_name)
81+
if server_metadata is None:
82+
# Retry once after syncing the metadata cache.
83+
self.state.metadata_cache_manager.sync_metadata_cache_from_api()
84+
server_metadata = self.state.metadata_cache_manager.get_server_metadata(sensor_name, instance_name)
85+
except MetadataSyncFailed as exn:
86+
self.state.logger.warning(exn)
87+
88+
return server_metadata
89+
90+
def handle(self, msg: HWPCReport):
91+
"""
92+
Process an HWPCReport to add the Kubernetes metadata.
93+
:param msg: The HWPCReport to process
94+
"""
95+
instance_name = get_instance_name_from_libvirt_cgroup(msg.target)
96+
if instance_name is not None:
97+
server_metadata = self.try_get_server_metadata(msg.sensor, instance_name)
98+
if server_metadata is None:
99+
# Drop the report if the server metadata is not present in the cache.
100+
return
101+
102+
msg.target = server_metadata.server_name
103+
msg.metadata['openstack'] = vars(server_metadata)
104+
105+
self._send_report(msg)

0 commit comments

Comments
 (0)