Skip to content

Commit

Permalink
Merge pull request #596 from adriaan42/adriaan/dynamic-instance-creation
Browse files Browse the repository at this point in the history
add dbus commands for dynamic creation of instances
  • Loading branch information
yarda authored May 21, 2024
2 parents 98d7dea + cddcd23 commit f75d795
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 16 deletions.
20 changes: 20 additions & 0 deletions com.redhat.tuned.policy
Original file line number Diff line number Diff line change
Expand Up @@ -247,4 +247,24 @@
</defaults>
</action>

<action id="com.redhat.tuned.instance_create">
<description>Create new plugin instance</description>
<message>Authentication is required to create a new plugin instance</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>yes</allow_active>
</defaults>
</action>

<action id="com.redhat.tuned.instance_destroy">
<description>Delete a plugin instance</description>
<message>Authentication is required to destroy the instance</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>yes</allow_active>
</defaults>
</action>

</policyconfig>
14 changes: 7 additions & 7 deletions tests/unit/plugins/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ def test_option_bool(self):

def test_create_instance(self):
instance = self._plugin.create_instance(\
'first_instance','test','test','test','test',\
'first_instance',0,'test','test','test','test',\
{'default_option1':'default_value2'})
self.assertIsNotNone(instance)

def test_destroy_instance(self):
instance = self._plugin.create_instance(\
'first_instance','test','test','test','test',\
'first_instance',0,'test','test','test','test',\
{'default_option1':'default_value2'})
instance.plugin.init_devices()

Expand All @@ -66,7 +66,7 @@ def test_destroy_instance(self):
def test_get_matching_devices(self):
""" without udev regex """
instance = self._plugin.create_instance(\
'first_instance','right_device*',None,'test','test',\
'first_instance',0,'right_device*',None,'test','test',\
{'default_option1':'default_value2'})

self.assertEqual(self._plugin._get_matching_devices(\
Expand All @@ -75,7 +75,7 @@ def test_get_matching_devices(self):

""" with udev regex """
instance = self._plugin.create_instance(\
'second_instance','right_device*','device[1-2]','test','test',\
'second_instance',0,'right_device*','device[1-2]','test','test',\
{'default_option1':'default_value2'})

device1 = DummyDevice('device1',{'name':'device1'})
Expand Down Expand Up @@ -104,15 +104,15 @@ def test_check_commands(self):
plugin_instance_factory,None,None)

def test_execute_all_non_device_commands(self):
instance = self._commands_plugin.create_instance('test_instance','',\
instance = self._commands_plugin.create_instance('test_instance',0,'',\
'','','',{'size':'XXL'})

self._commands_plugin._execute_all_non_device_commands(instance)

self.assertEqual(self._commands_plugin._size,'XXL')

def test_execute_all_device_commands(self):
instance = self._commands_plugin.create_instance('test_instance','',\
instance = self._commands_plugin.create_instance('test_instance',0,'',\
'','','',{'device_setting':'010'})

device1 = DummyDevice('device1',{})
Expand All @@ -133,7 +133,7 @@ def test_process_assignment_modifiers(self):
'<100','200'),'100')

def test_get_current_value(self):
instance = self._commands_plugin.create_instance('test_instance','',\
instance = self._commands_plugin.create_instance('test_instance',0,'',\
'','','',{})

command = [com for com in self._commands_plugin._commands.values()\
Expand Down
102 changes: 102 additions & 0 deletions tuned/daemon/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import threading
import tuned.consts as consts
from tuned.utils.commands import commands
from tuned.plugins import hotplug

__all__ = ["Controller"]

Expand Down Expand Up @@ -352,6 +353,10 @@ def instance_acquire_devices(self, devices, instance_name, caller = None):
rets = "Instance '%s' not found" % instance_name
log.error(rets)
return (False, rets)
if not isinstance(instance_target.plugin, hotplug.Plugin):
rets = "Plugin '%s' does not support hotplugging or dynamic instances." % instance_target.plugin.name
log.error(rets)
return (False, rets)
devs = set(self._cmd.devstr2devs(devices))
log.debug("Instance '%s' trying to acquire devices '%s'." % (instance_target.name, str(devs)))
for instance in self._daemon._unit_manager.instances:
Expand Down Expand Up @@ -417,3 +422,100 @@ def instance_get_devices(self, instance_name, caller = None):
rets = "Instance '%s' not found" % instance_name
log.error(rets)
return (False, rets, [])

@exports.export("ssa{ss}", "(bs)")
def instance_create(self, plugin_name, instance_name, options, caller = None):
"""Dynamically create a plugin instance
Parameters:
plugin_name -- name of the plugin
instance_name -- name of the new instance
dict of string-string -- options for the new instance
Return:
bool -- True on success
string -- error message or "OK"
"""
if caller == "":
return (False, "Unauthorized")
plugins = {p.name: p for p in self._daemon._unit_manager.plugins}
if not plugin_name in plugins.keys():
rets = "Plugin '%s' not found" % plugin_name
log.error(rets)
return (False, rets)
plugin = plugins[plugin_name]
if not isinstance(plugin, hotplug.Plugin):
rets = "Plugin '%s' does not support hotplugging or dynamic instances." % plugin.name
log.error(rets)
return (False, rets)
devices = options.pop("devices", None)
devices_udev_regex = options.pop("devices_udev_regex", None)
script_pre = options.pop("script_pre", None)
script_post = options.pop("script_post", None)
priority = int(options.pop("priority", self._daemon._unit_manager._def_instance_priority))
try:
instance = plugin.create_instance(instance_name, priority, devices, devices_udev_regex, script_pre, script_post, options)
plugin.initialize_instance(instance)
self._daemon._unit_manager.instances.append(instance)
except Exception as e:
rets = "Error creating instance '%s': %s" % (instance_name, str(e))
log.error(rets)
return (False, rets)
log.info("Created dynamic instance '%s' of plugin '%s'" % (instance_name, plugin_name))

plugin.assign_free_devices(instance)
plugin.instance_apply_tuning(instance)
# transfer matching devices from other instances, if the priority of the new
# instance is equal or higher (equal or lower priority value)
for other_instance in self._daemon._unit_manager.instances:
if (other_instance == instance or
other_instance.plugin != plugin or
instance.priority > other_instance.priority):
continue
devs_moving = plugin._get_matching_devices(instance, other_instance.processed_devices)
if len(devs_moving):
log.info("Moving devices '%s' from instance '%s' to instance '%s'." % (str(devs_moving),
other_instance.name, instance.name))
plugin._remove_devices_nocheck(other_instance, devs_moving)
plugin._add_devices_nocheck(instance, devs_moving)
return (True, "OK")

@exports.export("s", "(bs)")
def instance_destroy(self, instance_name, caller = None):
"""Destroy a dynamically created plugin instance
Parameters:
instance_name -- name of the new instance
Return:
bool -- True on success
string -- error message or "OK"
"""
if caller == "":
return (False, "Unauthorized")
try:
instance = [i for i in self._daemon._unit_manager.instances if i.name == instance_name][0]
except IndexError:
rets = "Instance '%s' not found" % instance_name
log.error(rets)
return (False, rets)
plugin = instance.plugin
if not isinstance(plugin, hotplug.Plugin):
rets = "Plugin '%s' does not support hotplugging or dynamic instances." % plugin.name
log.error(rets)
return (False, rets)
devices = instance.processed_devices.copy()
try:
plugin._remove_devices_nocheck(instance, devices)
self._daemon._unit_manager.instances.remove(instance)
plugin.instance_unapply_tuning(instance)
plugin.destroy_instance(instance)
except Exception as e:
rets = "Error deleting instance '%s': %s" % (instance_name, str(e))
log.error(rets)
return (False, rets)
log.info("Deleted instance '%s'" % instance_name)
for device in devices:
# _add_device() will find a suitable plugin instance
plugin._add_device(device)
return (True, "OK")
5 changes: 3 additions & 2 deletions tuned/plugins/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,16 @@ def _option_bool(self, value):
# Interface for manipulation with instances of the plugin.
#

def create_instance(self, name, devices_expression, devices_udev_regex, script_pre, script_post, options):
def create_instance(self, name, priority, devices_expression, devices_udev_regex, script_pre, script_post, options):
"""Create new instance of the plugin and seize the devices."""
if name in self._instances:
raise Exception("Plugin instance with name '%s' already exists." % name)

effective_options = self._get_effective_options(options)
instance = self._instance_factory.create(self, name, devices_expression, devices_udev_regex, \
instance = self._instance_factory.create(self, name, priority, devices_expression, devices_udev_regex, \
script_pre, script_post, effective_options)
self._instances[name] = instance
self._instances = collections.OrderedDict(sorted(self._instances.items(), key=lambda x: x[1].priority))

return instance

Expand Down
7 changes: 6 additions & 1 deletion tuned/plugins/instance/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class Instance(object):
"""
"""

def __init__(self, plugin, name, devices_expression, devices_udev_regex, script_pre, script_post, options):
def __init__(self, plugin, name, priority, devices_expression, devices_udev_regex, script_pre, script_post, options):
self._plugin = plugin
self._name = name
self._devices_expression = devices_expression
Expand All @@ -14,6 +14,7 @@ def __init__(self, plugin, name, devices_expression, devices_udev_regex, script_
self._options = options

self._active = True
self._priority = priority
self._has_static_tuning = False
self._has_dynamic_tuning = False
self._assigned_devices = set()
Expand All @@ -38,6 +39,10 @@ def active(self):
def active(self, value):
self._active = value

@property
def priority(self):
return self._priority

@property
def devices_expression(self):
return self._devices_expression
Expand Down
11 changes: 10 additions & 1 deletion tuned/profiles/unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ class Unit(object):
Unit description.
"""

__slots__ = [ "_name", "_type", "_enabled", "_replace", "_drop", "_devices", "_devices_udev_regex", \
__slots__ = [ "_name", "_priority", "_type", "_enabled", "_replace", "_drop", "_devices", "_devices_udev_regex", \
"_cpuinfo_regex", "_uname_regex", "_script_pre", "_script_post", "_options" ]

def __init__(self, name, config):
self._name = name
self._priority = config.pop("priority", None)
self._type = config.pop("type", self._name)
self._enabled = config.pop("enabled", True) in [True, "True", "true", 1, "1"]
self._replace = config.pop("replace", False) in [True, "True", "true", 1, "1"]
Expand All @@ -29,6 +30,14 @@ def __init__(self, name, config):
def name(self):
return self._name

@property
def priority(self):
return self._priority

@priority.setter
def priority(self, value):
self._priority = value

@property
def type(self):
return self._type
Expand Down
12 changes: 7 additions & 5 deletions tuned/units/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,15 @@ def create(self, instances_config):
log.debug("skipping instance '%s', uname does not match" % instance_name)
continue

instance_info.options.setdefault("priority", self._def_instance_priority)
instance_info.options["priority"] = int(instance_info.options["priority"])
if instance_info.priority is None:
instance_info.priority = int(self._def_instance_priority)
else:
instance_info.priority = int(instance_info.priority)
instance_info_list.append(instance_info)

instance_info_list.sort(key=lambda x: x.options["priority"])
instance_info_list.sort(key=lambda x: x.priority)
plugins_by_name = collections.OrderedDict()
for instance_info in instance_info_list:
instance_info.options.pop("priority")
plugins_by_name[instance_info.type] = None

for plugin_name, none in list(plugins_by_name.items()):
Expand All @@ -102,7 +103,8 @@ def create(self, instances_config):
if plugin is None:
continue
log.debug("creating '%s' (%s)" % (instance_info.name, instance_info.type))
new_instance = plugin.create_instance(instance_info.name, instance_info.devices, instance_info.devices_udev_regex, \
new_instance = plugin.create_instance(instance_info.name, instance_info.priority, \
instance_info.devices, instance_info.devices_udev_regex, \
instance_info.script_pre, instance_info.script_post, instance_info.options)
instances.append(new_instance)
for instance in instances:
Expand Down

0 comments on commit f75d795

Please sign in to comment.