| 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 154 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 213 prop._name, value)) | 214 prop._name, value)) |
| 214 | 215 |
| 215 | 216 |
| 216 def _validate_package_version(prop, value): | 217 def _validate_package_version(prop, value): |
| 217 """Validates a CIPD package version.""" | 218 """Validates a CIPD package version.""" |
| 218 if not cipd.is_valid_version(value): | 219 if not cipd.is_valid_version(value): |
| 219 raise datastore_errors.BadValueError( | 220 raise datastore_errors.BadValueError( |
| 220 '%s must be a valid package version "%s"' % (prop._name, value)) | 221 '%s must be a valid package version "%s"' % (prop._name, value)) |
| 221 | 222 |
| 222 | 223 |
| 224 def _validate_package_path(_prop, path): |
| 225 """Validates a CIPD installation path.""" |
| 226 if not path: |
| 227 raise datastore_errors.BadValueError( |
| 228 'CIPD package path is required. Use "." to install to run dir.') |
| 229 if '\\' in path: |
| 230 raise datastore_errors.BadValueError( |
| 231 'CIPD package path cannot contain \\. On Windows forward-slashes ' |
| 232 'will be replaced with back-slashes.') |
| 233 if '..' in path.split('/'): |
| 234 raise datastore_errors.BadValueError( |
| 235 'CIPD package path cannot contain "..".') |
| 236 if path.startswith('/'): |
| 237 raise datastore_errors.BadValueError( |
| 238 'CIPD package path cannot start with "/".') |
| 239 |
| 240 |
| 223 ### Models. | 241 ### Models. |
| 224 | 242 |
| 225 | 243 |
| 226 class FilesRef(ndb.Model): | 244 class FilesRef(ndb.Model): |
| 227 """Defines a data tree reference, normally a reference to a .isolated file.""" | 245 """Defines a data tree reference, normally a reference to a .isolated file.""" |
| 228 | 246 |
| 229 # TODO(maruel): make this class have one responsibility. Currently it is used | 247 # TODO(maruel): make this class have one responsibility. Currently it is used |
| 230 # in two modes: | 248 # in two modes: |
| 231 # - a reference to a tree, as class docstring says. | 249 # - a reference to a tree, as class docstring says. |
| 232 # - input/output settings in TaskProperties. | 250 # - input/output settings in TaskProperties. |
| (...skipping 19 matching lines...) Expand all Loading... |
| 252 A part of TaskProperties. | 270 A part of TaskProperties. |
| 253 """ | 271 """ |
| 254 # Package name template. May use cipd.ALL_PARAMS. | 272 # Package name template. May use cipd.ALL_PARAMS. |
| 255 # Most users will specify ${platform} parameter. | 273 # Most users will specify ${platform} parameter. |
| 256 package_name = ndb.StringProperty( | 274 package_name = ndb.StringProperty( |
| 257 indexed=False, validator=_validate_package_name_template) | 275 indexed=False, validator=_validate_package_name_template) |
| 258 # Package version that is valid for all packages matched by package_name. | 276 # Package version that is valid for all packages matched by package_name. |
| 259 # Most users will specify tags. | 277 # Most users will specify tags. |
| 260 version = ndb.StringProperty( | 278 version = ndb.StringProperty( |
| 261 indexed=False, validator=_validate_package_version) | 279 indexed=False, validator=_validate_package_version) |
| 280 # Path to dir, relative to the run dir, where to install the package. |
| 281 # If empty, the package will be installed in run dir. |
| 282 path = ndb.StringProperty(indexed=False, validator=_validate_package_path) |
| 262 | 283 |
| 263 def __str__(self): | 284 def __str__(self): |
| 264 return '%s:%s' % (self.package_name, self.version) | 285 return '%s:%s' % (self.package_name, self.version) |
| 265 | 286 |
| 266 def _pre_put_hook(self): | 287 def _pre_put_hook(self): |
| 267 super(CipdPackage, self)._pre_put_hook() | 288 super(CipdPackage, self)._pre_put_hook() |
| 268 if not self.package_name: | 289 if not self.package_name: |
| 269 raise datastore_errors.BadValueError('CIPD package name is required') | 290 raise datastore_errors.BadValueError('CIPD package name is required') |
| 270 if not self.version: | 291 if not self.version: |
| 271 raise datastore_errors.BadValueError('CIPD package version is required') | 292 raise datastore_errors.BadValueError('CIPD package version is required') |
| 272 | 293 |
| 273 | 294 |
| 274 class CipdInput(ndb.Model): | 295 class CipdInput(ndb.Model): |
| 275 """Specifies which CIPD client and packages to install, from which server. | 296 """Specifies which CIPD client and packages to install, from which server. |
| 276 | 297 |
| 277 A part of TaskProperties. | 298 A part of TaskProperties. |
| 278 """ | 299 """ |
| 279 # URL of the CIPD server. Must start with "https://" or "http://". | 300 # URL of the CIPD server. Must start with "https://" or "http://". |
| 280 server = ndb.StringProperty(indexed=False, validator=_validate_url) | 301 server = ndb.StringProperty(indexed=False, validator=_validate_url) |
| 281 | 302 |
| 282 # CIPD package of CIPD client to use. | 303 # CIPD package of CIPD client to use. |
| 283 # client_package.version is required. | 304 # client_package.version is required. |
| 305 # client_package.path must be None. |
| 284 client_package = ndb.LocalStructuredProperty(CipdPackage) | 306 client_package = ndb.LocalStructuredProperty(CipdPackage) |
| 285 | 307 |
| 286 # List of packages to install in $CIPD_PATH prior task execution. | 308 # List of packages to install in $CIPD_PATH prior task execution. |
| 287 packages = ndb.LocalStructuredProperty(CipdPackage, repeated=True) | 309 packages = ndb.LocalStructuredProperty(CipdPackage, repeated=True) |
| 288 | 310 |
| 289 def _pre_put_hook(self): | 311 def _pre_put_hook(self): |
| 290 if not self.server: | 312 if not self.server: |
| 291 raise datastore_errors.BadValueError('cipd server is required') | 313 raise datastore_errors.BadValueError('cipd server is required') |
| 292 if not self.client_package: | 314 if not self.client_package: |
| 293 raise datastore_errors.BadValueError('client_package is required') | 315 raise datastore_errors.BadValueError('client_package is required') |
| 316 if self.client_package.path: |
| 317 raise datastore_errors.BadValueError('client_package.path must be unset') |
| 294 self.client_package._pre_put_hook() | 318 self.client_package._pre_put_hook() |
| 295 | 319 |
| 296 if not self.packages: | 320 if not self.packages: |
| 297 raise datastore_errors.BadValueError( | 321 raise datastore_errors.BadValueError( |
| 298 'cipd_input cannot have an empty package list') | 322 'cipd_input cannot have an empty package list') |
| 299 | 323 |
| 300 package_names = set() | 324 package_names = set() |
| 301 for p in self.packages: | 325 for p in self.packages: |
| 302 p._pre_put_hook() | 326 p._pre_put_hook() |
| 303 if p.package_name in package_names: | 327 if p.package_name in package_names: |
| 304 raise datastore_errors.BadValueError( | 328 raise datastore_errors.BadValueError( |
| 305 'package %s is specified more than once' % p.package_name) | 329 'package %s is specified more than once' % p.package_name) |
| 306 package_names.add(p.package_name) | 330 package_names.add(p.package_name) |
| 307 self.packages.sort(key=lambda p: p.package_name) | 331 self.packages.sort(key=lambda p: p.package_name) |
| 308 | 332 |
| 333 def packages_grouped_by_path(self): |
| 334 """Returns sorted [(path), [package]) list. Used by user_task.html.""" |
| 335 packages = collections.defaultdict(list) |
| 336 for p in self.packages: |
| 337 packages[p.path].append(p) |
| 338 for pkgs in packages.itervalues(): |
| 339 pkgs.sort() |
| 340 return sorted(packages.iteritems()) |
| 341 |
| 309 | 342 |
| 310 class TaskProperties(ndb.Model): | 343 class TaskProperties(ndb.Model): |
| 311 """Defines all the properties of a task to be run on the Swarming | 344 """Defines all the properties of a task to be run on the Swarming |
| 312 infrastructure. | 345 infrastructure. |
| 313 | 346 |
| 314 This entity is not saved in the DB as a standalone entity, instead it is | 347 This entity is not saved in the DB as a standalone entity, instead it is |
| 315 embedded in a TaskRequest. | 348 embedded in a TaskRequest. |
| 316 | 349 |
| 317 This model is immutable. | 350 This model is immutable. |
| 318 | 351 |
| (...skipping 450 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 769 _put_request(request) | 802 _put_request(request) |
| 770 return request | 803 return request |
| 771 | 804 |
| 772 | 805 |
| 773 def validate_priority(priority): | 806 def validate_priority(priority): |
| 774 """Throws ValueError if priority is not a valid value.""" | 807 """Throws ValueError if priority is not a valid value.""" |
| 775 if 0 > priority or MAXIMUM_PRIORITY < priority: | 808 if 0 > priority or MAXIMUM_PRIORITY < priority: |
| 776 raise datastore_errors.BadValueError( | 809 raise datastore_errors.BadValueError( |
| 777 'priority (%d) must be between 0 and %d (inclusive)' % | 810 'priority (%d) must be between 0 and %d (inclusive)' % |
| 778 (priority, MAXIMUM_PRIORITY)) | 811 (priority, MAXIMUM_PRIORITY)) |
| OLD | NEW |