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 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 142 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 216 # Namespace on the isolate server. | 217 # Namespace on the isolate server. |
| 217 namespace = ndb.StringProperty(validator=_validate_namespace, indexed=False) | 218 namespace = ndb.StringProperty(validator=_validate_namespace, indexed=False) |
| 218 | 219 |
| 219 def _pre_put_hook(self): | 220 def _pre_put_hook(self): |
| 220 super(FilesRef, self)._pre_put_hook() | 221 super(FilesRef, self)._pre_put_hook() |
| 221 if not self.isolated or not self.isolatedserver or not self.namespace: | 222 if not self.isolated or not self.isolatedserver or not self.namespace: |
| 222 raise datastore_errors.BadValueError( | 223 raise datastore_errors.BadValueError( |
| 223 'isolated requires server and namespace') | 224 'isolated requires server and namespace') |
| 224 | 225 |
| 225 | 226 |
| 227 class CipdPackage(ndb.Model): | |
| 228 """A CIPD package to install in $CIPD_PATH and $PATH before task execution. | |
| 229 | |
| 230 A part of TaskProperties. | |
| 231 """ | |
| 232 package_name = ndb.StringProperty(indexed=False) | |
| 233 version = ndb.StringProperty(indexed=False) | |
| 234 | |
| 235 def _pre_put_hook(self): | |
| 236 super(CipdPackage, self)._pre_put_hook() | |
| 237 if not self.package_name: | |
| 238 raise datastore_errors.BadValueError('CIPD package name is required') | |
| 239 if not cipd.is_valid_package_name(self.package_name): | |
|
M-A Ruel
2016/04/25 19:09:45
Could you use validator=wrapper_function instead?
nodir
2016/04/25 20:27:16
Done.
| |
| 240 raise datastore_errors.BadValueError( | |
| 241 'malformed package name "%s"' % self.package_name) | |
| 242 if not self.version: | |
| 243 raise datastore_errors.BadValueError('CIPD package version is required') | |
| 244 if not cipd.is_valid_version(self.version): | |
|
M-A Ruel
2016/04/25 19:09:45
Same
nodir
2016/04/25 20:27:15
Done.
| |
| 245 raise datastore_errors.BadValueError( | |
| 246 'malformed package version "%s"' % self.version) | |
| 247 | |
| 248 | |
| 226 class TaskProperties(ndb.Model): | 249 class TaskProperties(ndb.Model): |
| 227 """Defines all the properties of a task to be run on the Swarming | 250 """Defines all the properties of a task to be run on the Swarming |
| 228 infrastructure. | 251 infrastructure. |
| 229 | 252 |
| 230 This entity is not saved in the DB as a standalone entity, instead it is | 253 This entity is not saved in the DB as a standalone entity, instead it is |
| 231 embedded in a TaskRequest. | 254 embedded in a TaskRequest. |
| 232 | 255 |
| 233 This model is immutable. | 256 This model is immutable. |
| 234 | 257 |
| 235 New-style TaskProperties supports invocation of run_isolated. When this | 258 New-style TaskProperties supports invocation of run_isolated. When this |
| 236 behavior is desired, the member .inputs_ref must be suppled. .extra_args can | 259 behavior is desired, the member .inputs_ref must be suppled. .extra_args can |
| 237 be supplied to pass extraneous arguments. | 260 be supplied to pass extraneous arguments. |
| 238 """ | 261 """ |
| 239 # Hashing algorithm used to hash TaskProperties to create its key. | 262 # Hashing algorithm used to hash TaskProperties to create its key. |
| 240 HASHING_ALGO = hashlib.sha1 | 263 HASHING_ALGO = hashlib.sha1 |
| 241 | 264 |
| 242 # Commands to run. It is a list of 1 item, the command to run. | 265 # Commands to run. It is a list of 1 item, the command to run. |
| 243 # TODO(maruel): Remove after 2016-06-01. | 266 # TODO(maruel): Remove after 2016-06-01. |
| 244 commands = datastore_utils.DeterministicJsonProperty( | 267 commands = datastore_utils.DeterministicJsonProperty( |
| 245 json_type=list, indexed=False) | 268 json_type=list, indexed=False) |
| 246 # Command to run. This is only relevant when self._inputs_ref is None. This is | 269 # 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 | 270 # what is called 'raw commands', in the sense that no inputs files are |
| 248 # declared. | 271 # declared. |
| 249 command = ndb.StringProperty(repeated=True, indexed=False) | 272 command = ndb.StringProperty(repeated=True, indexed=False) |
| 250 | 273 |
| 251 # File inputs of the task. Only inputs_ref or command&data can be specified. | 274 # File inputs of the task. Only inputs_ref or command&data can be specified. |
| 252 inputs_ref = ndb.LocalStructuredProperty(FilesRef) | 275 inputs_ref = ndb.LocalStructuredProperty(FilesRef) |
| 253 | 276 |
| 277 # A list of CIPD packages to install $CIPD_PATH and $PATH before task | |
| 278 # execution. | |
| 279 packages = ndb.LocalStructuredProperty(CipdPackage, repeated=True) | |
| 280 | |
| 254 # Filter to use to determine the required properties on the bot to run on. For | 281 # 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 | 282 # example, Windows or hostname. Encoded as json. Optional but highly |
| 256 # recommended. | 283 # recommended. |
| 257 dimensions = datastore_utils.DeterministicJsonProperty( | 284 dimensions = datastore_utils.DeterministicJsonProperty( |
| 258 validator=_validate_dimensions, json_type=dict, indexed=False) | 285 validator=_validate_dimensions, json_type=dict, indexed=False) |
| 259 | 286 |
| 260 # Environment variables. Encoded as json. Optional. | 287 # Environment variables. Encoded as json. Optional. |
| 261 env = datastore_utils.DeterministicJsonProperty( | 288 env = datastore_utils.DeterministicJsonProperty( |
| 262 validator=_validate_dict_of_strings, json_type=dict, indexed=False) | 289 validator=_validate_dict_of_strings, json_type=dict, indexed=False) |
| 263 | 290 |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 325 if self.commands: | 352 if self.commands: |
| 326 raise datastore_errors.BadValueError( | 353 raise datastore_errors.BadValueError( |
| 327 'commands is not supported anymore') | 354 'commands is not supported anymore') |
| 328 if not self.is_terminate: | 355 if not self.is_terminate: |
| 329 if bool(self.command) == bool(self.inputs_ref): | 356 if bool(self.command) == bool(self.inputs_ref): |
| 330 raise datastore_errors.BadValueError('use one of command or inputs_ref') | 357 raise datastore_errors.BadValueError('use one of command or inputs_ref') |
| 331 if self.extra_args and not self.inputs_ref: | 358 if self.extra_args and not self.inputs_ref: |
| 332 raise datastore_errors.BadValueError('extra_args require inputs_ref') | 359 raise datastore_errors.BadValueError('extra_args require inputs_ref') |
| 333 if self.inputs_ref: | 360 if self.inputs_ref: |
| 334 self.inputs_ref._pre_put_hook() | 361 self.inputs_ref._pre_put_hook() |
| 362 for p in self.packages: | |
|
M-A Ruel
2016/04/25 19:09:45
Can you also sort it? It's important that listing
nodir
2016/04/25 20:27:16
good point, done
also add a check that only one ve
| |
| 363 p._pre_put_hook() | |
| 364 if self.idempotent: | |
| 365 pinned = lambda p: cipd.is_pinned_version(p.version) | |
| 366 if self.packages and any(not pinned(p) for p in self.packages): | |
| 367 raise datastore_errors.BadValueError( | |
| 368 'an idempotent task cannot have unpinned packages; ' | |
| 369 'use instance IDs or tags as package versions') | |
| 370 | |
| 335 | 371 |
| 336 | 372 |
| 337 class TaskRequest(ndb.Model): | 373 class TaskRequest(ndb.Model): |
| 338 """Contains a user request. | 374 """Contains a user request. |
| 339 | 375 |
| 340 Key id is a decreasing integer based on time since utils.EPOCH plus some | 376 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 | 377 randomness on lower order bits. See _new_request_key() for the complete gory |
| 342 details. | 378 details. |
| 343 | 379 |
| 344 There is also "old style keys" which inherit from a fake root entity | 380 There is also "old style keys" which inherit from a fake root entity |
| (...skipping 213 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 558 root_entity_shard_id)) | 594 root_entity_shard_id)) |
| 559 | 595 |
| 560 | 596 |
| 561 def make_request(request, is_bot_or_admin): | 597 def make_request(request, is_bot_or_admin): |
| 562 """Registers the request in the DB. | 598 """Registers the request in the DB. |
| 563 | 599 |
| 564 Fills up some values. | 600 Fills up some values. |
| 565 | 601 |
| 566 If parent_task_id is set, properties for the parent are used: | 602 If parent_task_id is set, properties for the parent are used: |
| 567 - priority: defaults to parent.priority - 1 | 603 - priority: defaults to parent.priority - 1 |
| 568 - user: overriden by parent.user | 604 - user: overridden by parent.user |
| 569 | 605 |
| 570 """ | 606 """ |
| 571 assert request.__class__ is TaskRequest | 607 assert request.__class__ is TaskRequest |
| 572 if request.parent_task_id: | 608 if request.parent_task_id: |
| 573 run_result_key = task_pack.unpack_run_result_key(request.parent_task_id) | 609 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( | 610 result_summary_key = task_pack.run_result_key_to_result_summary_key( |
| 575 run_result_key) | 611 run_result_key) |
| 576 request_key = task_pack.result_summary_key_to_request_key( | 612 request_key = task_pack.result_summary_key_to_request_key( |
| 577 result_summary_key) | 613 result_summary_key) |
| 578 parent = request_key.get() | 614 parent = request_key.get() |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 648 _put_request(request) | 684 _put_request(request) |
| 649 return request | 685 return request |
| 650 | 686 |
| 651 | 687 |
| 652 def validate_priority(priority): | 688 def validate_priority(priority): |
| 653 """Throws ValueError if priority is not a valid value.""" | 689 """Throws ValueError if priority is not a valid value.""" |
| 654 if 0 > priority or MAXIMUM_PRIORITY < priority: | 690 if 0 > priority or MAXIMUM_PRIORITY < priority: |
| 655 raise datastore_errors.BadValueError( | 691 raise datastore_errors.BadValueError( |
| 656 'priority (%d) must be between 0 and %d (inclusive)' % | 692 'priority (%d) must be between 0 and %d (inclusive)' % |
| 657 (priority, MAXIMUM_PRIORITY)) | 693 (priority, MAXIMUM_PRIORITY)) |
| OLD | NEW |