| 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 by the Apache v2.0 license that can be | 3 # Use of this source code is governed by the Apache v2.0 license that can be |
| 4 # found in the LICENSE file. | 4 # 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 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 54 import urlparse | 54 import urlparse |
| 55 | 55 |
| 56 from google.appengine.api import datastore_errors | 56 from google.appengine.api import datastore_errors |
| 57 from google.appengine.ext import ndb | 57 from google.appengine.ext import ndb |
| 58 | 58 |
| 59 from components import auth | 59 from components import auth |
| 60 from components import datastore_utils | 60 from components import datastore_utils |
| 61 from components import pubsub | 61 from components import pubsub |
| 62 from components import utils | 62 from components import utils |
| 63 from server import task_pack | 63 from server import task_pack |
| 64 import cipd |
| 64 | 65 |
| 65 | 66 |
| 66 # Maximum acceptable priority value, which is effectively the lowest priority. | 67 # Maximum acceptable priority value, which is effectively the lowest priority. |
| 67 MAXIMUM_PRIORITY = 255 | 68 MAXIMUM_PRIORITY = 255 |
| 68 | 69 |
| 69 | 70 |
| 70 # Enforced on both task request and bots. | 71 # Enforced on both task request and bots. |
| 71 DIMENSION_KEY_RE = ur'^[a-zA-Z\-\_\.]+$' | 72 DIMENSION_KEY_RE = ur'^[a-zA-Z\-\_\.]+$' |
| 72 | 73 |
| 73 | 74 |
| (...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 196 | 197 |
| 197 | 198 |
| 198 def _validate_tags(prop, value): | 199 def _validate_tags(prop, value): |
| 199 """Validates and sorts TaskRequest.tags.""" | 200 """Validates and sorts TaskRequest.tags.""" |
| 200 if not ':' in value: | 201 if not ':' in value: |
| 201 # pylint: disable=W0212 | 202 # pylint: disable=W0212 |
| 202 raise datastore_errors.BadValueError( | 203 raise datastore_errors.BadValueError( |
| 203 '%s must be key:value form, not %s' % (prop._name, value)) | 204 '%s must be key:value form, not %s' % (prop._name, value)) |
| 204 | 205 |
| 205 | 206 |
| 207 def _validate_package_name(prop, value): |
| 208 """Validates a CIPD package name.""" |
| 209 if not cipd.is_valid_package_name(value): |
| 210 raise datastore_errors.BadValueError( |
| 211 '%s must be a valid CIPD package name "%s"' % (prop._name, value)) |
| 212 |
| 213 |
| 214 def _validate_package_version(prop, value): |
| 215 """Validates a CIPD package version.""" |
| 216 if not cipd.is_valid_version(value): |
| 217 raise datastore_errors.BadValueError( |
| 218 '%s must be a valid package version "%s"' % (prop._name, value)) |
| 219 |
| 220 |
| 206 ### Models. | 221 ### Models. |
| 207 | 222 |
| 208 | 223 |
| 209 class FilesRef(ndb.Model): | 224 class FilesRef(ndb.Model): |
| 210 """Defines a data tree reference, normally a reference to a .isolated file.""" | 225 """Defines a data tree reference, normally a reference to a .isolated file.""" |
| 211 # The hash of an isolated archive. | 226 # The hash of an isolated archive. |
| 212 isolated = ndb.StringProperty(validator=_validate_isolated, indexed=False) | 227 isolated = ndb.StringProperty(validator=_validate_isolated, indexed=False) |
| 213 # The hostname of the isolated server to use. | 228 # The hostname of the isolated server to use. |
| 214 isolatedserver = ndb.StringProperty( | 229 isolatedserver = ndb.StringProperty( |
| 215 validator=_validate_hostname, indexed=False) | 230 validator=_validate_hostname, indexed=False) |
| 216 # Namespace on the isolate server. | 231 # Namespace on the isolate server. |
| 217 namespace = ndb.StringProperty(validator=_validate_namespace, indexed=False) | 232 namespace = ndb.StringProperty(validator=_validate_namespace, indexed=False) |
| 218 | 233 |
| 219 def _pre_put_hook(self): | 234 def _pre_put_hook(self): |
| 220 super(FilesRef, self)._pre_put_hook() | 235 super(FilesRef, self)._pre_put_hook() |
| 221 if not self.isolated or not self.isolatedserver or not self.namespace: | 236 if not self.isolated or not self.isolatedserver or not self.namespace: |
| 222 raise datastore_errors.BadValueError( | 237 raise datastore_errors.BadValueError( |
| 223 'isolated requires server and namespace') | 238 'isolated requires server and namespace') |
| 224 | 239 |
| 225 | 240 |
| 241 class CipdPackage(ndb.Model): |
| 242 """A CIPD package to install in $CIPD_PATH and $PATH before task execution. |
| 243 |
| 244 A part of TaskProperties. |
| 245 """ |
| 246 package_name = ndb.StringProperty( |
| 247 indexed=False, validator=_validate_package_name) |
| 248 version = ndb.StringProperty( |
| 249 indexed=False, validator=_validate_package_version) |
| 250 |
| 251 def _pre_put_hook(self): |
| 252 super(CipdPackage, self)._pre_put_hook() |
| 253 if not self.package_name: |
| 254 raise datastore_errors.BadValueError('CIPD package name is required') |
| 255 if not self.version: |
| 256 raise datastore_errors.BadValueError('CIPD package version is required') |
| 257 |
| 258 |
| 226 class TaskProperties(ndb.Model): | 259 class TaskProperties(ndb.Model): |
| 227 """Defines all the properties of a task to be run on the Swarming | 260 """Defines all the properties of a task to be run on the Swarming |
| 228 infrastructure. | 261 infrastructure. |
| 229 | 262 |
| 230 This entity is not saved in the DB as a standalone entity, instead it is | 263 This entity is not saved in the DB as a standalone entity, instead it is |
| 231 embedded in a TaskRequest. | 264 embedded in a TaskRequest. |
| 232 | 265 |
| 233 This model is immutable. | 266 This model is immutable. |
| 234 | 267 |
| 235 New-style TaskProperties supports invocation of run_isolated. When this | 268 New-style TaskProperties supports invocation of run_isolated. When this |
| 236 behavior is desired, the member .inputs_ref must be suppled. .extra_args can | 269 behavior is desired, the member .inputs_ref must be suppled. .extra_args can |
| 237 be supplied to pass extraneous arguments. | 270 be supplied to pass extraneous arguments. |
| 238 """ | 271 """ |
| 239 # Hashing algorithm used to hash TaskProperties to create its key. | 272 # Hashing algorithm used to hash TaskProperties to create its key. |
| 240 HASHING_ALGO = hashlib.sha1 | 273 HASHING_ALGO = hashlib.sha1 |
| 241 | 274 |
| 242 # Commands to run. It is a list of 1 item, the command to run. | 275 # Commands to run. It is a list of 1 item, the command to run. |
| 243 # TODO(maruel): Remove after 2016-06-01. | 276 # TODO(maruel): Remove after 2016-06-01. |
| 244 commands = datastore_utils.DeterministicJsonProperty( | 277 commands = datastore_utils.DeterministicJsonProperty( |
| 245 json_type=list, indexed=False) | 278 json_type=list, indexed=False) |
| 246 # Command to run. This is only relevant when self._inputs_ref is None. This is | 279 # Command to run. This is only relevant when self._inputs_ref is None. This is |
| 247 # what is called 'raw commands', in the sense that no inputs files are | 280 # what is called 'raw commands', in the sense that no inputs files are |
| 248 # declared. | 281 # declared. |
| 249 command = ndb.StringProperty(repeated=True, indexed=False) | 282 command = ndb.StringProperty(repeated=True, indexed=False) |
| 250 | 283 |
| 251 # File inputs of the task. Only inputs_ref or command&data can be specified. | 284 # File inputs of the task. Only inputs_ref or command&data can be specified. |
| 252 inputs_ref = ndb.LocalStructuredProperty(FilesRef) | 285 inputs_ref = ndb.LocalStructuredProperty(FilesRef) |
| 253 | 286 |
| 287 # A list of CIPD packages to install $CIPD_PATH and $PATH before task |
| 288 # execution. |
| 289 packages = ndb.LocalStructuredProperty(CipdPackage, repeated=True) |
| 290 |
| 254 # Filter to use to determine the required properties on the bot to run on. For | 291 # Filter to use to determine the required properties on the bot to run on. For |
| 255 # example, Windows or hostname. Encoded as json. Optional but highly | 292 # example, Windows or hostname. Encoded as json. Optional but highly |
| 256 # recommended. | 293 # recommended. |
| 257 dimensions = datastore_utils.DeterministicJsonProperty( | 294 dimensions = datastore_utils.DeterministicJsonProperty( |
| 258 validator=_validate_dimensions, json_type=dict, indexed=False) | 295 validator=_validate_dimensions, json_type=dict, indexed=False) |
| 259 | 296 |
| 260 # Environment variables. Encoded as json. Optional. | 297 # Environment variables. Encoded as json. Optional. |
| 261 env = datastore_utils.DeterministicJsonProperty( | 298 env = datastore_utils.DeterministicJsonProperty( |
| 262 validator=_validate_dict_of_strings, json_type=dict, indexed=False) | 299 validator=_validate_dict_of_strings, json_type=dict, indexed=False) |
| 263 | 300 |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 326 raise datastore_errors.BadValueError( | 363 raise datastore_errors.BadValueError( |
| 327 'commands is not supported anymore') | 364 'commands is not supported anymore') |
| 328 if not self.is_terminate: | 365 if not self.is_terminate: |
| 329 if bool(self.command) == bool(self.inputs_ref): | 366 if bool(self.command) == bool(self.inputs_ref): |
| 330 raise datastore_errors.BadValueError('use one of command or inputs_ref') | 367 raise datastore_errors.BadValueError('use one of command or inputs_ref') |
| 331 if self.extra_args and not self.inputs_ref: | 368 if self.extra_args and not self.inputs_ref: |
| 332 raise datastore_errors.BadValueError('extra_args require inputs_ref') | 369 raise datastore_errors.BadValueError('extra_args require inputs_ref') |
| 333 if self.inputs_ref: | 370 if self.inputs_ref: |
| 334 self.inputs_ref._pre_put_hook() | 371 self.inputs_ref._pre_put_hook() |
| 335 | 372 |
| 373 package_names = set() |
| 374 for p in self.packages: |
| 375 p._pre_put_hook() |
| 376 if p.package_name in package_names: |
| 377 raise datastore_errors.BadValueError( |
| 378 'package %s is specified more than once' % p.package_name) |
| 379 package_names.add(p.package_name) |
| 380 self.packages.sort(key=lambda p: p.package_name) |
| 381 |
| 382 if self.idempotent: |
| 383 pinned = lambda p: cipd.is_pinned_version(p.version) |
| 384 if self.packages and any(not pinned(p) for p in self.packages): |
| 385 raise datastore_errors.BadValueError( |
| 386 'an idempotent task cannot have unpinned packages; ' |
| 387 'use instance IDs or tags as package versions') |
| 388 |
| 389 |
| 336 | 390 |
| 337 class TaskRequest(ndb.Model): | 391 class TaskRequest(ndb.Model): |
| 338 """Contains a user request. | 392 """Contains a user request. |
| 339 | 393 |
| 340 Key id is a decreasing integer based on time since utils.EPOCH plus some | 394 Key id is a decreasing integer based on time since utils.EPOCH plus some |
| 341 randomness on lower order bits. See _new_request_key() for the complete gory | 395 randomness on lower order bits. See _new_request_key() for the complete gory |
| 342 details. | 396 details. |
| 343 | 397 |
| 344 There is also "old style keys" which inherit from a fake root entity | 398 There is also "old style keys" which inherit from a fake root entity |
| 345 TaskRequestShard. | 399 TaskRequestShard. |
| (...skipping 212 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 558 root_entity_shard_id)) | 612 root_entity_shard_id)) |
| 559 | 613 |
| 560 | 614 |
| 561 def make_request(request, is_bot_or_admin): | 615 def make_request(request, is_bot_or_admin): |
| 562 """Registers the request in the DB. | 616 """Registers the request in the DB. |
| 563 | 617 |
| 564 Fills up some values. | 618 Fills up some values. |
| 565 | 619 |
| 566 If parent_task_id is set, properties for the parent are used: | 620 If parent_task_id is set, properties for the parent are used: |
| 567 - priority: defaults to parent.priority - 1 | 621 - priority: defaults to parent.priority - 1 |
| 568 - user: overriden by parent.user | 622 - user: overridden by parent.user |
| 569 | 623 |
| 570 """ | 624 """ |
| 571 assert request.__class__ is TaskRequest | 625 assert request.__class__ is TaskRequest |
| 572 if request.parent_task_id: | 626 if request.parent_task_id: |
| 573 run_result_key = task_pack.unpack_run_result_key(request.parent_task_id) | 627 run_result_key = task_pack.unpack_run_result_key(request.parent_task_id) |
| 574 result_summary_key = task_pack.run_result_key_to_result_summary_key( | 628 result_summary_key = task_pack.run_result_key_to_result_summary_key( |
| 575 run_result_key) | 629 run_result_key) |
| 576 request_key = task_pack.result_summary_key_to_request_key( | 630 request_key = task_pack.result_summary_key_to_request_key( |
| 577 result_summary_key) | 631 result_summary_key) |
| 578 parent = request_key.get() | 632 parent = request_key.get() |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 648 _put_request(request) | 702 _put_request(request) |
| 649 return request | 703 return request |
| 650 | 704 |
| 651 | 705 |
| 652 def validate_priority(priority): | 706 def validate_priority(priority): |
| 653 """Throws ValueError if priority is not a valid value.""" | 707 """Throws ValueError if priority is not a valid value.""" |
| 654 if 0 > priority or MAXIMUM_PRIORITY < priority: | 708 if 0 > priority or MAXIMUM_PRIORITY < priority: |
| 655 raise datastore_errors.BadValueError( | 709 raise datastore_errors.BadValueError( |
| 656 'priority (%d) must be between 0 and %d (inclusive)' % | 710 'priority (%d) must be between 0 and %d (inclusive)' % |
| 657 (priority, MAXIMUM_PRIORITY)) | 711 (priority, MAXIMUM_PRIORITY)) |
| OLD | NEW |