Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # coding: utf-8 | 1 # coding: utf-8 |
| 2 # Copyright 2014 The LUCI Authors. All rights reserved. | 2 # Copyright 2014 The LUCI Authors. All rights reserved. |
| 3 # Use of this source code is governed under the Apache License, Version 2.0 | 3 # Use of this source code is governed under the Apache License, Version 2.0 |
| 4 # that can be found in the LICENSE file. | 4 # that can be found in the LICENSE file. |
| 5 | 5 |
| 6 """Tasks definition. | 6 """Tasks definition. |
| 7 | 7 |
| 8 Each user request creates a new TaskRequest. The TaskRequest instance saves the | 8 Each user request creates a new TaskRequest. The TaskRequest instance saves the |
| 9 metadata of the request, e.g. who requested it, when why, etc. It links to the | 9 metadata of the request, e.g. who requested it, when why, etc. It links to the |
| 10 actual data of the request in a TaskProperties. The TaskProperties represents | 10 actual data of the request in a TaskProperties. The TaskProperties represents |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 39 +---------------------+ | 39 +---------------------+ |
| 40 ^ | 40 ^ |
| 41 | | 41 | |
| 42 <See task_to_run.py and task_result.py> | 42 <See task_to_run.py and task_result.py> |
| 43 | 43 |
| 44 TaskProperties is embedded in TaskRequest. TaskProperties is still declared as a | 44 TaskProperties is embedded in TaskRequest. TaskProperties is still declared as a |
| 45 separate entity to clearly declare the boundary for task request deduplication. | 45 separate entity to clearly declare the boundary for task request deduplication. |
| 46 """ | 46 """ |
| 47 | 47 |
| 48 | 48 |
| 49 import collections | |
| 49 import datetime | 50 import datetime |
| 50 import hashlib | 51 import hashlib |
| 51 import random | 52 import random |
| 52 import re | 53 import re |
| 53 import urlparse | 54 import urlparse |
| 54 | 55 |
| 55 from google.appengine.api import datastore_errors | 56 from google.appengine.api import datastore_errors |
| 56 from google.appengine.ext import ndb | 57 from google.appengine.ext import ndb |
| 57 | 58 |
| 58 from components import auth | 59 from components import auth |
| (...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 252 A part of TaskProperties. | 253 A part of TaskProperties. |
| 253 """ | 254 """ |
| 254 # Package name template. May use cipd.ALL_PARAMS. | 255 # Package name template. May use cipd.ALL_PARAMS. |
| 255 # Most users will specify ${platform} parameter. | 256 # Most users will specify ${platform} parameter. |
| 256 package_name = ndb.StringProperty( | 257 package_name = ndb.StringProperty( |
| 257 indexed=False, validator=_validate_package_name_template) | 258 indexed=False, validator=_validate_package_name_template) |
| 258 # Package version that is valid for all packages matched by package_name. | 259 # Package version that is valid for all packages matched by package_name. |
| 259 # Most users will specify tags. | 260 # Most users will specify tags. |
| 260 version = ndb.StringProperty( | 261 version = ndb.StringProperty( |
| 261 indexed=False, validator=_validate_package_version) | 262 indexed=False, validator=_validate_package_version) |
| 263 # Path to dir, relative to the run dir, where to install the package. | |
| 264 # If empty, the package will be installed in run dir. | |
| 265 path = ndb.StringProperty(indexed=False) | |
| 262 | 266 |
| 263 def __str__(self): | 267 def __str__(self): |
| 264 return '%s:%s' % (self.package_name, self.version) | 268 return '%s:%s' % (self.package_name, self.version) |
| 265 | 269 |
| 266 def _pre_put_hook(self): | 270 def _pre_put_hook(self): |
| 267 super(CipdPackage, self)._pre_put_hook() | 271 super(CipdPackage, self)._pre_put_hook() |
| 268 if not self.package_name: | 272 if not self.package_name: |
| 269 raise datastore_errors.BadValueError('CIPD package name is required') | 273 raise datastore_errors.BadValueError('CIPD package name is required') |
| 270 if not self.version: | 274 if not self.version: |
| 271 raise datastore_errors.BadValueError('CIPD package version is required') | 275 raise datastore_errors.BadValueError('CIPD package version is required') |
| 272 | 276 |
| 277 if self.path: | |
|
M-A Ruel
2016/06/15 17:28:27
Use a validator instead.
nodir
2016/06/15 17:53:42
Done.
| |
| 278 if '\\' in self.path: | |
| 279 raise datastore_errors.BadValueError( | |
| 280 'CIPD package path cannot contain \\. On Windows forward-slashes ' | |
| 281 'will be replaced with back-slashes.') | |
| 282 if '..' in self.path.split('/'): | |
| 283 raise datastore_errors.BadValueError( | |
| 284 'CIPD package path cannot contain "..".') | |
| 285 if self.path.startswith('/'): | |
| 286 raise datastore_errors.BadValueError( | |
| 287 'CIPD package path cannot start with "/".') | |
| 288 | |
| 273 | 289 |
| 274 class CipdInput(ndb.Model): | 290 class CipdInput(ndb.Model): |
| 275 """Specifies which CIPD client and packages to install, from which server. | 291 """Specifies which CIPD client and packages to install, from which server. |
| 276 | 292 |
| 277 A part of TaskProperties. | 293 A part of TaskProperties. |
| 278 """ | 294 """ |
| 279 # URL of the CIPD server. Must start with "https://" or "http://". | 295 # URL of the CIPD server. Must start with "https://" or "http://". |
| 280 server = ndb.StringProperty(indexed=False, validator=_validate_url) | 296 server = ndb.StringProperty(indexed=False, validator=_validate_url) |
| 281 | 297 |
| 282 # CIPD package of CIPD client to use. | 298 # CIPD package of CIPD client to use. |
| 283 # client_package.version is required. | 299 # client_package.version is required. |
| 300 # client_package.path must be None. | |
| 284 client_package = ndb.LocalStructuredProperty(CipdPackage) | 301 client_package = ndb.LocalStructuredProperty(CipdPackage) |
| 285 | 302 |
| 286 # List of packages to install in $CIPD_PATH prior task execution. | 303 # List of packages to install in $CIPD_PATH prior task execution. |
| 287 packages = ndb.LocalStructuredProperty(CipdPackage, repeated=True) | 304 packages = ndb.LocalStructuredProperty(CipdPackage, repeated=True) |
| 288 | 305 |
| 289 def _pre_put_hook(self): | 306 def _pre_put_hook(self): |
| 290 if not self.server: | 307 if not self.server: |
| 291 raise datastore_errors.BadValueError('cipd server is required') | 308 raise datastore_errors.BadValueError('cipd server is required') |
| 292 if not self.client_package: | 309 if not self.client_package: |
| 293 raise datastore_errors.BadValueError('client_package is required') | 310 raise datastore_errors.BadValueError('client_package is required') |
| 311 if self.client_package.path: | |
| 312 raise datastore_errors.BadValueError('client_package.path must be unset') | |
| 294 self.client_package._pre_put_hook() | 313 self.client_package._pre_put_hook() |
| 295 | 314 |
| 296 if not self.packages: | 315 if not self.packages: |
| 297 raise datastore_errors.BadValueError( | 316 raise datastore_errors.BadValueError( |
| 298 'cipd_input cannot have an empty package list') | 317 'cipd_input cannot have an empty package list') |
| 299 | 318 |
| 300 package_names = set() | 319 package_names = set() |
| 301 for p in self.packages: | 320 for p in self.packages: |
| 302 p._pre_put_hook() | 321 p._pre_put_hook() |
| 303 if p.package_name in package_names: | 322 if p.package_name in package_names: |
| 304 raise datastore_errors.BadValueError( | 323 raise datastore_errors.BadValueError( |
| 305 'package %s is specified more than once' % p.package_name) | 324 'package %s is specified more than once' % p.package_name) |
| 306 package_names.add(p.package_name) | 325 package_names.add(p.package_name) |
| 307 self.packages.sort(key=lambda p: p.package_name) | 326 self.packages.sort(key=lambda p: p.package_name) |
| 308 | 327 |
| 328 def packages_grouped_by_path(self): | |
| 329 """Returns sorted [(path), [package]) list. Used by user_task.html.""" | |
| 330 packages = collections.defaultdict(list) | |
| 331 for p in self.packages: | |
| 332 packages[p.path or '.'].append(p) | |
|
M-A Ruel
2016/06/15 17:28:27
I don't like that path=None and path='.' have the
nodir
2016/06/15 17:53:41
Good point, done.
| |
| 333 for pkgs in packages.values(): | |
|
M-A Ruel
2016/06/15 17:28:27
itervalues()
nodir
2016/06/15 17:53:42
Done.
| |
| 334 pkgs.sort() | |
| 335 return sorted(packages.iteritems()) | |
| 309 | 336 |
|
M-A Ruel
2016/06/15 17:28:27
2 lines
nodir
2016/06/15 17:53:42
Done.
| |
| 310 class TaskProperties(ndb.Model): | 337 class TaskProperties(ndb.Model): |
| 311 """Defines all the properties of a task to be run on the Swarming | 338 """Defines all the properties of a task to be run on the Swarming |
| 312 infrastructure. | 339 infrastructure. |
| 313 | 340 |
| 314 This entity is not saved in the DB as a standalone entity, instead it is | 341 This entity is not saved in the DB as a standalone entity, instead it is |
| 315 embedded in a TaskRequest. | 342 embedded in a TaskRequest. |
| 316 | 343 |
| 317 This model is immutable. | 344 This model is immutable. |
| 318 | 345 |
| 319 New-style TaskProperties supports invocation of run_isolated. When this | 346 New-style TaskProperties supports invocation of run_isolated. When this |
| (...skipping 449 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 769 _put_request(request) | 796 _put_request(request) |
| 770 return request | 797 return request |
| 771 | 798 |
| 772 | 799 |
| 773 def validate_priority(priority): | 800 def validate_priority(priority): |
| 774 """Throws ValueError if priority is not a valid value.""" | 801 """Throws ValueError if priority is not a valid value.""" |
| 775 if 0 > priority or MAXIMUM_PRIORITY < priority: | 802 if 0 > priority or MAXIMUM_PRIORITY < priority: |
| 776 raise datastore_errors.BadValueError( | 803 raise datastore_errors.BadValueError( |
| 777 'priority (%d) must be between 0 and %d (inclusive)' % | 804 'priority (%d) must be between 0 and %d (inclusive)' % |
| 778 (priority, MAXIMUM_PRIORITY)) | 805 (priority, MAXIMUM_PRIORITY)) |
| OLD | NEW |