Support restricting access to drones by user. Administrators can put lines like
<hostname>_users: showard,scottz
in the global config, where <hostname> is a drone hostname. That drone will then be limited to use by those users (that is, by jobs launched by those users, and tasks launched due to those jobs). This required numerous changes:
* added a requested_by field to SpecialTask (with corresponding migration). For tasks with queue_entries, we can infer this from the job, but for those without, we need this information explicitly declared. Note this can be null if the task was created by the system, not in response to any user action. The only place this occurs now is in scheduler recovery (Dispatcher._recover_hosts_where()), but there may be an upcoming feature to periodically reverify hosts, which would be another (much more common) case.
* modified all SpecialTask creation sites to pass requested_by if necessary.
* modified AgentTask to keep a username attribute, and modified its run() method to pass that to PidfileRunMonitor.run(), which passes it along to DroneManager.execute_command().
* modified Agent to always keep self.task around, there's no reason to throw it away and now that we're looking at it from other classes, it's problematic if it disappears.
* modified Dispatcher throttling code to pass the username when requesting max runnable processes.
* added an allowed_users property to _AbstractDrone, and made DroneManager load it from the global config.
* made DroneManager's max_runnable_processes() and _choose_drone_for_execution() methods accept the username and obey user restrictions.
* added extensive tests for everything. the modiications required to monitor_db_unittest were annoying but not too bad. but parts of that file may need to be removed as they'll be obsoleted by monitor_db_functional_test and they'll become increasingly annoying to maintain.
couple other related changes:
* got rid of CleanupHostsMixin. it was only acutally needed by GatherLogsTasks (since we made the change to have GatherLogsTask always run), so I inlined it there and simplified code accordingly.
* changed a bunch of places in the scheduler that were constructing new instances of Django models for existing rows. they would do something like "models.Host(id=<id of existing host>)". that's correct for scheduler DBModels, but not for Django models. For Django models, you only instantiate new instances when you want to create a new row. for fetching existing rows you always use a manager -- Model.objects.get() or Model.objects.filter() etc. this was an existing bug but wasn't exposed until I made some of the changes involved in this feature.
Signed-off-by: Steve Howard <showard@google.com>
git-svn-id: http://test.kernel.org/svn/autotest/trunk@3961 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/scheduler/drone_manager_unittest.py b/scheduler/drone_manager_unittest.py
index 948564a..a44e729 100755
--- a/scheduler/drone_manager_unittest.py
+++ b/scheduler/drone_manager_unittest.py
@@ -8,11 +8,13 @@
from autotest_lib.scheduler import scheduler_config
class MockDrone(drones._AbstractDrone):
- def __init__(self, name, active_processes=0, max_processes=10):
+ def __init__(self, name, active_processes=0, max_processes=10,
+ allowed_users=None):
super(MockDrone, self).__init__()
self.name = name
self.active_processes = active_processes
self.max_processes = max_processes
+ self.allowed_users = allowed_users
# maps method names list of tuples containing method arguments
self._recorded_calls = {'queue_call': [],
'send_file_to': []}
@@ -61,6 +63,7 @@
_SOURCE_PATH = 'source/path'
_DESTINATION_PATH = 'destination/path'
_WORKING_DIRECTORY = 'working/directory'
+ _USERNAME = 'my_user'
def setUp(self):
self.god = mock.mock_god()
@@ -96,7 +99,8 @@
self.manager._enqueue_drone(MockDrone(index, active_processes,
max_processes))
- return self.manager._choose_drone_for_execution(requested_processes)
+ return self.manager._choose_drone_for_execution(requested_processes,
+ self._USERNAME)
def test_choose_drone_for_execution(self):
@@ -117,6 +121,21 @@
self.assertEquals(drone.name, 1)
+ def test_user_restrictions(self):
+ # this drone is restricted to a different user
+ self.manager._enqueue_drone(MockDrone(1, max_processes=10,
+ allowed_users=['fakeuser']))
+ # this drone is allowed but has lower capacity
+ self.manager._enqueue_drone(MockDrone(2, max_processes=2,
+ allowed_users=[self._USERNAME]))
+
+ self.assertEquals(2,
+ self.manager.max_runnable_processes(self._USERNAME))
+ drone = self.manager._choose_drone_for_execution(
+ 1, username=self._USERNAME)
+ self.assertEquals(drone.name, 2)
+
+
def test_initialize(self):
results_hostname = 'results_repo'
results_install_dir = '/results/install'