blob: edca06088fc21e4d3fd37a82dd2f09cba3a36e85 [file] [log] [blame]
Prashanth B340fd1e2014-06-22 12:44:10 -07001#pylint: disable-msg=C0111
2
Prashanth Bcf731e32014-08-10 18:03:57 -07003import cPickle
4import logging
5import os
6import tempfile
7import time
showard170873e2009-01-07 00:22:26 +00008import common
9from autotest_lib.scheduler import drone_utility, email_manager
Prashanth B340fd1e2014-06-22 12:44:10 -070010from autotest_lib.client.bin import local_host
Simran Basiaf9b8e72012-10-12 15:02:36 -070011from autotest_lib.client.common_lib import error, global_config, utils
Prashanth Bf78a6fb2014-06-10 16:09:40 -070012from autotest_lib.client.common_lib.cros.graphite import stats
showard170873e2009-01-07 00:22:26 +000013
14
15AUTOTEST_INSTALL_DIR = global_config.global_config.get_config_value('SCHEDULER',
16 'drone_installation_directory')
17
Eric Li861b2d52011-02-04 14:50:35 -080018class DroneUnreachable(Exception):
19 """The drone is non-sshable."""
20 pass
21
22
Simran Basiaf9b8e72012-10-12 15:02:36 -070023class _BaseAbstractDrone(object):
showard9bb960b2009-11-19 01:02:11 +000024 """
25 Attributes:
26 * allowed_users: set of usernames allowed to use this drone. if None,
27 any user can use this drone.
28 """
Prashanth Bcf731e32014-08-10 18:03:57 -070029 def __init__(self, timestamp_remote_calls=True):
30 """Instantiate an abstract drone.
31
32 @param timestamp_remote_calls: If true, drone_utility is invoked with
33 the --call_time option and the current time. Currently this is only
34 used for testing.
35 """
showard170873e2009-01-07 00:22:26 +000036 self._calls = []
37 self.hostname = None
showardc5afc462009-01-13 00:09:39 +000038 self.enabled = True
showard324bf812009-01-20 23:23:38 +000039 self.max_processes = 0
40 self.active_processes = 0
showard9bb960b2009-11-19 01:02:11 +000041 self.allowed_users = None
Prashanth B340fd1e2014-06-22 12:44:10 -070042 self._autotest_install_dir = AUTOTEST_INSTALL_DIR
43 self._host = None
Prashanth Bcf731e32014-08-10 18:03:57 -070044 self.timestamp_remote_calls = timestamp_remote_calls
showard170873e2009-01-07 00:22:26 +000045
46
47 def shutdown(self):
48 pass
49
50
Prashanth B340fd1e2014-06-22 12:44:10 -070051 @property
52 def _drone_utility_path(self):
53 return os.path.join(self._autotest_install_dir,
54 'scheduler', 'drone_utility.py')
55
56
showard324bf812009-01-20 23:23:38 +000057 def used_capacity(self):
jamesren37b50452010-03-25 20:38:56 +000058 """Gets the capacity used by this drone
59
60 Returns a tuple of (percentage_full, -max_capacity). This is to aid
61 direct comparisons, so that a 0/10 drone is considered less heavily
62 loaded than a 0/2 drone.
63
64 This value should never be used directly. It should only be used in
65 direct comparisons using the basic comparison operators, or using the
66 cmp() function.
67 """
showard324bf812009-01-20 23:23:38 +000068 if self.max_processes == 0:
jamesren37b50452010-03-25 20:38:56 +000069 return (1.0, 0)
70 return (float(self.active_processes) / self.max_processes,
71 -self.max_processes)
showard324bf812009-01-20 23:23:38 +000072
73
showard9bb960b2009-11-19 01:02:11 +000074 def usable_by(self, user):
75 if self.allowed_users is None:
76 return True
77 return user in self.allowed_users
78
79
showard170873e2009-01-07 00:22:26 +000080 def _execute_calls_impl(self, calls):
Prashanth B340fd1e2014-06-22 12:44:10 -070081 if not self._host:
82 raise ValueError('Drone cannot execute calls without a host.')
Prashanth Bcf731e32014-08-10 18:03:57 -070083 drone_utility_cmd = self._drone_utility_path
84 if self.timestamp_remote_calls:
85 drone_utility_cmd = '%s --call_time %s' % (
86 drone_utility_cmd, time.time())
Prashanth B340fd1e2014-06-22 12:44:10 -070087 logging.info("Running drone_utility on %s", self.hostname)
Prashanth Bcf731e32014-08-10 18:03:57 -070088 result = self._host.run('python %s' % drone_utility_cmd,
Prashanth B340fd1e2014-06-22 12:44:10 -070089 stdin=cPickle.dumps(calls), stdout_tee=None,
90 connect_timeout=300)
91 try:
92 return cPickle.loads(result.stdout)
93 except Exception: # cPickle.loads can throw all kinds of exceptions
94 logging.critical('Invalid response:\n---\n%s\n---', result.stdout)
95 raise
showard170873e2009-01-07 00:22:26 +000096
97
98 def _execute_calls(self, calls):
Prashanth Bf78a6fb2014-06-10 16:09:40 -070099 stats.Gauge('drone_execute_call_count').send(
100 self.hostname.replace('.', '_'), len(calls))
showard170873e2009-01-07 00:22:26 +0000101 return_message = self._execute_calls_impl(calls)
102 for warning in return_message['warnings']:
103 subject = 'Warning from drone %s' % self.hostname
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700104 logging.warning(subject + '\n' + warning)
showard170873e2009-01-07 00:22:26 +0000105 email_manager.manager.enqueue_notify_email(subject, warning)
106 return return_message['results']
107
108
Prashanth B340fd1e2014-06-22 12:44:10 -0700109 def get_calls(self):
110 """Returns the calls queued against this drone.
111
112 @return: A list of calls queued against the drone.
113 """
114 return self._calls
115
116
showard170873e2009-01-07 00:22:26 +0000117 def call(self, method, *args, **kwargs):
118 return self._execute_calls(
119 [drone_utility.call(method, *args, **kwargs)])
120
121
122 def queue_call(self, method, *args, **kwargs):
123 self._calls.append(drone_utility.call(method, *args, **kwargs))
124
125 def clear_call_queue(self):
126 self._calls = []
127
128
129 def execute_queued_calls(self):
130 if not self._calls:
131 return
Prashanth B340fd1e2014-06-22 12:44:10 -0700132 results = self._execute_calls(self._calls)
showard170873e2009-01-07 00:22:26 +0000133 self.clear_call_queue()
Prashanth B340fd1e2014-06-22 12:44:10 -0700134 return results
showard170873e2009-01-07 00:22:26 +0000135
136
showardac5b0002009-10-19 18:34:00 +0000137 def set_autotest_install_dir(self, path):
138 pass
139
140
Simran Basiaf9b8e72012-10-12 15:02:36 -0700141SiteDrone = utils.import_site_class(
142 __file__, 'autotest_lib.scheduler.site_drones',
143 '_SiteAbstractDrone', _BaseAbstractDrone)
144
145
146class _AbstractDrone(SiteDrone):
147 pass
148
149
showard170873e2009-01-07 00:22:26 +0000150class _LocalDrone(_AbstractDrone):
Prashanth Bcf731e32014-08-10 18:03:57 -0700151 def __init__(self, timestamp_remote_calls=True):
152 super(_LocalDrone, self).__init__(
153 timestamp_remote_calls=timestamp_remote_calls)
showard170873e2009-01-07 00:22:26 +0000154 self.hostname = 'localhost'
Prashanth B340fd1e2014-06-22 12:44:10 -0700155 self._host = local_host.LocalHost()
showard170873e2009-01-07 00:22:26 +0000156 self._drone_utility = drone_utility.DroneUtility()
157
158
showard170873e2009-01-07 00:22:26 +0000159 def send_file_to(self, drone, source_path, destination_path,
160 can_fail=False):
161 if drone.hostname == self.hostname:
showardde634ee2009-01-30 01:44:24 +0000162 self.queue_call('copy_file_or_directory', source_path,
163 destination_path)
showard170873e2009-01-07 00:22:26 +0000164 else:
165 self.queue_call('send_file_to', drone.hostname, source_path,
166 destination_path, can_fail)
167
168
169class _RemoteDrone(_AbstractDrone):
Prashanth Bcf731e32014-08-10 18:03:57 -0700170 def __init__(self, hostname, timestamp_remote_calls=True):
171 super(_RemoteDrone, self).__init__(
172 timestamp_remote_calls=timestamp_remote_calls)
showard170873e2009-01-07 00:22:26 +0000173 self.hostname = hostname
174 self._host = drone_utility.create_host(hostname)
Eric Li861b2d52011-02-04 14:50:35 -0800175 if not self._host.is_up():
176 logging.error('Drone %s is unpingable, kicking out', hostname)
177 raise DroneUnreachable
Eric Lid656d562011-04-20 11:48:29 -0700178
179
showard202343e2009-10-14 16:20:24 +0000180 def set_autotest_install_dir(self, path):
181 self._autotest_install_dir = path
182
183
showard170873e2009-01-07 00:22:26 +0000184 def shutdown(self):
185 super(_RemoteDrone, self).shutdown()
186 self._host.close()
187
188
showard170873e2009-01-07 00:22:26 +0000189 def send_file_to(self, drone, source_path, destination_path,
190 can_fail=False):
191 if drone.hostname == self.hostname:
showardde634ee2009-01-30 01:44:24 +0000192 self.queue_call('copy_file_or_directory', source_path,
193 destination_path)
showard170873e2009-01-07 00:22:26 +0000194 elif isinstance(drone, _LocalDrone):
195 drone.queue_call('get_file_from', self.hostname, source_path,
196 destination_path)
197 else:
198 self.queue_call('send_file_to', drone.hostname, source_path,
199 destination_path, can_fail)
200
201
showard170873e2009-01-07 00:22:26 +0000202def get_drone(hostname):
203 """
204 Use this factory method to get drone objects.
205 """
206 if hostname == 'localhost':
207 return _LocalDrone()
Eric Li861b2d52011-02-04 14:50:35 -0800208 try:
209 return _RemoteDrone(hostname)
210 except DroneUnreachable:
211 return None