virt.virt_test_utils: run_autotest - 'tar' needs relative paths to strip the leading '/'
[autotest-zwu.git] / mirror / trigger.py
blob87e5e6df8749a29c9b2b4ecc5cc8c1cf1c8a6c2a
1 import email.Message, os, re, smtplib
3 from autotest_lib.server import frontend
5 class trigger(object):
6 """
7 Base trigger class. You are allowed to derive from it and
8 override functions to suit your needs in the configuration file.
9 """
10 def __init__(self):
11 self.__actions = []
14 def run(self, files):
15 # Call each of the actions and pass in the kernel list
16 for action in self.__actions:
17 action(files)
20 def add_action(self, func):
21 self.__actions.append(func)
24 class base_action(object):
25 """
26 Base class for functor actions. Since actions can also be simple functions
27 all action classes need to override __call__ to be callable.
28 """
29 def __call__(self, kernel_list):
30 """
31 Perform the action for that given kernel filenames list.
33 @param kernel_list: a sequence of kernel filenames (strings)
34 """
35 raise NotImplemented('__call__ not implemented')
38 class map_action(base_action):
39 """
40 Action that uses a map between machines and their associated control
41 files and kernel configuration files and it schedules them using
42 the AFE.
43 """
45 _encode_sep = re.compile('(\D+)')
47 class machine_info(object):
48 """
49 Class to organize the machine associated information for this action.
50 """
51 def __init__(self, tests, kernel_configs):
52 """
53 Instantiate a machine_info object.
55 @param tests: a sequence of test names (as named in the frontend
56 database) to run for this host
57 @param kernel_configs: a dictionary of
58 kernel_version -> config_filename associating kernel
59 versions with corresponding kernel configuration files
60 ("~" inside the filename will be expanded)
61 """
62 self.tests = tests
63 self.kernel_configs = kernel_configs
66 def __init__(self, tests_map, jobname_pattern, job_owner='autotest',
67 upload_kernel_config=False):
68 """
69 Instantiate a map_action.
71 @param tests_map: a dictionary of hostname -> machine_info
72 @param jobname_pattern: a string pattern used to make the job name
73 containing a single "%s" that will be replaced with the kernel
74 version
75 @param job_owner: the user used to talk with the RPC server
76 @param upload_kernel_config: specify if the generate control file
77 should contain code that downloads and sends to the client the
78 kernel config file (in case it is an URL); this requires that
79 the tests_map refers only to server side tests
80 """
81 self._tests_map = tests_map
82 self._jobname_pattern = jobname_pattern
83 self._afe = frontend.AFE(user=job_owner)
84 self._upload_kernel_config = upload_kernel_config
87 def __call__(self, kernel_list):
88 """
89 Schedule jobs to run on the given list of kernel versions using
90 the configured machines -> machine_info mapping for test name
91 selection and kernel config file selection.
92 """
93 for kernel in kernel_list:
94 # Get a list of all the machines available for testing
95 # and the tests that each one is to execute and group them by
96 # test/kernel-config so we can run a single job for the same
97 # group
99 # dictionary of (test-name,kernel-config)-><list-of-machines>
100 jobs = {}
101 for machine, info in self._tests_map.iteritems():
102 config_paths = info.kernel_configs
103 kernel_config = '/boot/config'
105 if config_paths:
106 kvers = config_paths.keys()
107 close = self._closest_kver_leq(kvers, kernel)
108 kernel_config = config_paths[close]
110 for test in info.tests:
111 jobs.setdefault((test, kernel_config), [])
112 jobs[(test, kernel_config)].append(machine)
114 for (test, kernel_config), hosts in jobs.iteritems():
115 c = self._generate_control(test, kernel, kernel_config)
116 self._schedule_job(self._jobname_pattern % kernel, c, hosts)
119 @classmethod
120 def _kver_encode(cls, version):
122 Encode the various kernel version strings (ex 2.6.20, 2.6.21-rc1,
123 2.7.30-rc2-git3, etc) in a way that makes them easily comparable using
124 lexicographic ordering.
126 @param version: kernel version string to encode
128 @return processed kernel version string that can be compared using
129 lexicographic comparison
131 # if it's not a "rc" release, add a -rc99 so it orders at the end of
132 # all other rc releases for the same base kernel version
133 if 'rc' not in version:
134 version += '-rc99'
136 # if it's not a git snapshot add a -git99 so it orders at the end of
137 # all other git snapshots for the same base kernel version
138 if 'git' not in version:
139 version += '-git99'
141 # make all number sequences to be at least 2 in size (with a leading 0
142 # if necessary)
143 bits = cls._encode_sep.split(version)
144 for n in range(0, len(bits), 2):
145 if len(bits[n]) < 2:
146 bits[n] = '0' + bits[n]
147 return ''.join(bits)
150 @classmethod
151 def _kver_cmp(cls, a, b):
153 Compare 2 kernel versions.
155 @param a, b: kernel version strings to compare
157 @return True if 'a' is less than 'b' or False otherwise
159 a, b = cls._kver_encode(a), cls._kver_encode(b)
160 return cmp(a, b)
163 @classmethod
164 def _closest_kver_leq(cls, klist, kver):
166 Return the closest kernel ver in the list that is <= kver unless
167 kver is the lowest, in which case return the lowest in klist.
169 if kver in klist:
170 return kver
171 l = list(klist)
172 l.append(kver)
173 l.sort(cmp=cls._kver_cmp)
174 i = l.index(kver)
175 if i == 0:
176 return l[1]
177 return l[i - 1]
180 def _generate_control(self, test, kernel, kernel_config):
182 Uses generate_control_file RPC to generate a control file given
183 a test name and kernel information.
185 @param test: The test name string as it's named in the frontend
186 database.
187 @param kernel: A str of the kernel version (i.e. x.x.xx)
188 @param kernel_config: A str filename to the kernel config on the
189 client
191 @returns a dict representing a control file as described by
192 frontend.afe.rpc_interface.generate_control_file
194 kernel_info = dict(version=kernel,
195 config_file=os.path.expanduser(kernel_config))
196 return self._afe.generate_control_file(
197 tests=[test], kernel=[kernel_info],
198 upload_kernel_config=self._upload_kernel_config)
201 def _schedule_job(self, jobname, control, hosts):
202 control_type = ('Client', 'Server')[control.is_server]
204 self._afe.create_job(control.control_file, jobname,
205 control_type=control_type, hosts=hosts)
208 class email_action(base_action):
210 An action object to send emails about found new kernel versions.
212 _MAIL = 'sendmail'
214 def __init__(self, dest_addr, from_addr='autotest-server@localhost'):
216 Create an email_action instance.
218 @param dest_addr: a string or a list of strings with the destination
219 email address(es)
220 @param from_addr: optional source email address for the sent mails
221 (default 'autotest-server@localhost')
223 # if passed a string for the dest_addr convert it to a tuple
224 if type(dest_addr) is str:
225 self._dest_addr = (dest_addr,)
226 else:
227 self._dest_addr = dest_addr
229 self._from_addr = from_addr
232 def __call__(self, kernel_list):
233 if not kernel_list:
234 return
236 message = '\n'.join(kernel_list)
237 message = 'Testing new kernel releases:\n%s' % message
239 self._mail('autotest new kernel notification', message)
242 def _mail(self, subject, message_text):
243 message = email.Message.Message()
244 message['To'] = ', '.join(self._dest_addr)
245 message['From'] = self._from_addr
246 message['Subject'] = subject
247 message.set_payload(message_text)
249 if self._sendmail(message.as_string()):
250 server = smtplib.SMTP('localhost')
251 try:
252 server.sendmail(self._from_addr, self._dest_addr,
253 message.as_string())
254 finally:
255 server.quit()
258 @classmethod
259 def _sendmail(cls, message):
261 Send an email using the sendmail command.
263 # open a pipe to the mail program and
264 # write the data to the pipe
265 p = os.popen('%s -t' % cls._MAIL, 'w')
266 p.write(message)
267 return p.close()