1 import email
.Message
, os
, re
, smtplib
3 from autotest_lib
.server
import frontend
7 Base trigger class. You are allowed to derive from it and
8 override functions to suit your needs in the configuration file.
15 # Call each of the actions and pass in the kernel list
16 for action
in self
.__actions
:
20 def add_action(self
, func
):
21 self
.__actions
.append(func
)
24 class base_action(object):
26 Base class for functor actions. Since actions can also be simple functions
27 all action classes need to override __call__ to be callable.
29 def __call__(self
, kernel_list
):
31 Perform the action for that given kernel filenames list.
33 @param kernel_list: a sequence of kernel filenames (strings)
35 raise NotImplemented('__call__ not implemented')
38 class map_action(base_action
):
40 Action that uses a map between machines and their associated control
41 files and kernel configuration files and it schedules them using
45 _encode_sep
= re
.compile('(\D+)')
47 class machine_info(object):
49 Class to organize the machine associated information for this action.
51 def __init__(self
, tests
, kernel_configs
):
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)
63 self
.kernel_configs
= kernel_configs
66 def __init__(self
, tests_map
, jobname_pattern
, job_owner
='autotest',
67 upload_kernel_config
=False):
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
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
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
):
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.
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
99 # dictionary of (test-name,kernel-config)-><list-of-machines>
101 for machine
, info
in self
._tests
_map
.iteritems():
102 config_paths
= info
.kernel_configs
103 kernel_config
= '/boot/config'
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
)
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
:
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
:
141 # make all number sequences to be at least 2 in size (with a leading 0
143 bits
= cls
._encode
_sep
.split(version
)
144 for n
in range(0, len(bits
), 2):
146 bits
[n
] = '0' + bits
[n
]
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
)
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.
173 l
.sort(cmp=cls
._kver
_cmp
)
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
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
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.
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
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
,)
227 self
._dest
_addr
= dest_addr
229 self
._from
_addr
= from_addr
232 def __call__(self
, kernel_list
):
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')
252 server
.sendmail(self
._from
_addr
, self
._dest
_addr
,
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')