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 205 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 216 if not cipd.is_valid_version(value): | 216 if not cipd.is_valid_version(value): |
| 217 raise datastore_errors.BadValueError( | 217 raise datastore_errors.BadValueError( |
| 218 '%s must be a valid package version "%s"' % (prop._name, value)) | 218 '%s must be a valid package version "%s"' % (prop._name, value)) |
| 219 | 219 |
| 220 | 220 |
| 221 ### Models. | 221 ### Models. |
| 222 | 222 |
| 223 | 223 |
| 224 class FilesRef(ndb.Model): | 224 class FilesRef(ndb.Model): |
| 225 """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.""" |
| 226 # The hash of an isolated archive. | 226 |
| 227 # Specifies FilesRef mode. | |
|
M-A Ruel
2016/05/11 14:41:41
This is not needed?
nodir
2016/05/11 16:04:29
Done.
| |
| 228 # if True, this instance is a reference to a tree, as class docstring says. | |
| 229 # if False, this instance is isolate input/output settings in TaskProperties. | |
| 230 # TODO(maruel): refactor this class, get rid of this. | |
| 231 is_ref = True | |
| 232 | |
| 233 # if is_ref, the hash of an isolated archive. | |
| 234 # otherwise, the hash of the input isolated archive. | |
| 227 isolated = ndb.StringProperty(validator=_validate_isolated, indexed=False) | 235 isolated = ndb.StringProperty(validator=_validate_isolated, indexed=False) |
| 228 # The hostname of the isolated server to use. | 236 # The hostname of the isolated server to use. |
| 229 isolatedserver = ndb.StringProperty( | 237 isolatedserver = ndb.StringProperty( |
| 230 validator=_validate_hostname, indexed=False) | 238 validator=_validate_hostname, indexed=False) |
| 231 # Namespace on the isolate server. | 239 # Namespace on the isolate server. |
| 232 namespace = ndb.StringProperty(validator=_validate_namespace, indexed=False) | 240 namespace = ndb.StringProperty(validator=_validate_namespace, indexed=False) |
| 233 | 241 |
| 234 def _pre_put_hook(self): | 242 def _pre_put_hook(self): |
| 235 super(FilesRef, self)._pre_put_hook() | 243 super(FilesRef, self)._pre_put_hook() |
| 236 if not self.isolated or not self.isolatedserver or not self.namespace: | 244 if self.is_ref: |
|
M-A Ruel
2016/05/11 14:41:41
Remove this part, self.isolated can be None.
nodir
2016/05/11 16:04:29
Done. Now is_ref is not used, so I've removed it.
| |
| 237 raise datastore_errors.BadValueError( | 245 if not self.isolated or not self.isolatedserver or not self.namespace: |
| 238 'isolated requires server and namespace') | 246 raise datastore_errors.BadValueError( |
| 247 'isolated requires server and namespace') | |
| 248 else: | |
| 249 if not self.isolatedserver or not self.namespace: | |
| 250 raise datastore_errors.BadValueError( | |
| 251 'isolate server and namespace are required') | |
| 239 | 252 |
| 240 | 253 |
| 241 class CipdPackage(ndb.Model): | 254 class CipdPackage(ndb.Model): |
| 242 """A CIPD package to install in $CIPD_PATH and $PATH before task execution. | 255 """A CIPD package to install in $CIPD_PATH and $PATH before task execution. |
| 243 | 256 |
| 244 A part of TaskProperties. | 257 A part of TaskProperties. |
| 245 """ | 258 """ |
| 246 package_name = ndb.StringProperty( | 259 package_name = ndb.StringProperty( |
| 247 indexed=False, validator=_validate_package_name) | 260 indexed=False, validator=_validate_package_name) |
| 248 version = ndb.StringProperty( | 261 version = ndb.StringProperty( |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 259 class TaskProperties(ndb.Model): | 272 class TaskProperties(ndb.Model): |
| 260 """Defines all the properties of a task to be run on the Swarming | 273 """Defines all the properties of a task to be run on the Swarming |
| 261 infrastructure. | 274 infrastructure. |
| 262 | 275 |
| 263 This entity is not saved in the DB as a standalone entity, instead it is | 276 This entity is not saved in the DB as a standalone entity, instead it is |
| 264 embedded in a TaskRequest. | 277 embedded in a TaskRequest. |
| 265 | 278 |
| 266 This model is immutable. | 279 This model is immutable. |
| 267 | 280 |
| 268 New-style TaskProperties supports invocation of run_isolated. When this | 281 New-style TaskProperties supports invocation of run_isolated. When this |
| 269 behavior is desired, the member .inputs_ref must be suppled. .extra_args can | 282 behavior is desired, the member .inputs_ref.isolated must be supplied. |
|
M-A Ruel
2016/05/11 14:41:41
Strictly speaking, it could still be missing when
nodir
2016/05/11 16:04:29
Done.
| |
| 270 be supplied to pass extraneous arguments. | 283 .extra_args can be supplied to pass extraneous arguments. |
| 271 """ | 284 """ |
| 285 | |
| 286 # TODO(maruel): convert inputs_ref and _TaskResultCommon.outputs_ref as: | |
| 287 # - input = String which is the isolated input, if any | |
| 288 # - isolated_server = <server, metadata e.g. namespace> which is a | |
| 289 # simplified version of FilesRef | |
| 290 # - _TaskResultCommon.output = String which is isolated output, if any. | |
| 291 | |
| 272 # Hashing algorithm used to hash TaskProperties to create its key. | 292 # Hashing algorithm used to hash TaskProperties to create its key. |
| 273 HASHING_ALGO = hashlib.sha1 | 293 HASHING_ALGO = hashlib.sha1 |
| 274 | 294 |
| 275 # Commands to run. It is a list of 1 item, the command to run. | 295 # Commands to run. It is a list of 1 item, the command to run. |
| 276 # TODO(maruel): Remove after 2016-06-01. | 296 # TODO(maruel): Remove after 2016-06-01. |
| 277 commands = datastore_utils.DeterministicJsonProperty( | 297 commands = datastore_utils.DeterministicJsonProperty( |
| 278 json_type=list, indexed=False) | 298 json_type=list, indexed=False) |
| 279 # Command to run. This is only relevant when self._inputs_ref is None. This is | 299 # Command to run. This is only relevant when self.inputs_ref.isolated is None. |
| 280 # what is called 'raw commands', in the sense that no inputs files are | 300 # This is what is called 'raw commands', in the sense that no inputs files are |
| 281 # declared. | 301 # declared. |
| 282 command = ndb.StringProperty(repeated=True, indexed=False) | 302 command = ndb.StringProperty(repeated=True, indexed=False) |
| 283 | 303 |
| 284 # File inputs of the task. Only inputs_ref or command&data can be specified. | 304 # Isolate server, namespace and input isolate hash. |
| 305 # | |
| 306 # Despite its name, contains isolate server URL and namespace for isolated | |
| 307 # output too. See TODO at the top of this class. | |
| 308 # May be non-None even if task input is not isolated. | |
| 309 # | |
| 310 # Only inputs_ref.isolated or command&data can be specified. | |
|
M-A Ruel
2016/05/11 14:41:41
data doesn't exist anymore, it's a left over so re
nodir
2016/05/11 16:04:29
Done.
| |
| 285 inputs_ref = ndb.LocalStructuredProperty(FilesRef) | 311 inputs_ref = ndb.LocalStructuredProperty(FilesRef) |
| 286 | 312 |
| 287 # A list of CIPD packages to install $CIPD_PATH and $PATH before task | 313 # A list of CIPD packages to install $CIPD_PATH and $PATH before task |
| 288 # execution. | 314 # execution. |
| 289 packages = ndb.LocalStructuredProperty(CipdPackage, repeated=True) | 315 packages = ndb.LocalStructuredProperty(CipdPackage, repeated=True) |
| 290 | 316 |
| 291 # Filter to use to determine the required properties on the bot to run on. For | 317 # Filter to use to determine the required properties on the bot to run on. For |
| 292 # example, Windows or hostname. Encoded as json. Optional but highly | 318 # example, Windows or hostname. Encoded as json. Optional but highly |
| 293 # recommended. | 319 # recommended. |
| 294 dimensions = datastore_utils.DeterministicJsonProperty( | 320 dimensions = datastore_utils.DeterministicJsonProperty( |
| 295 validator=_validate_dimensions, json_type=dict, indexed=False) | 321 validator=_validate_dimensions, json_type=dict, indexed=False) |
| 296 | 322 |
| 297 # Environment variables. Encoded as json. Optional. | 323 # Environment variables. Encoded as json. Optional. |
| 298 env = datastore_utils.DeterministicJsonProperty( | 324 env = datastore_utils.DeterministicJsonProperty( |
| 299 validator=_validate_dict_of_strings, json_type=dict, indexed=False) | 325 validator=_validate_dict_of_strings, json_type=dict, indexed=False) |
| 300 | 326 |
| 301 # Maximum duration the bot can take to run this task. It's named hard_timeout | 327 # Maximum duration the bot can take to run this task. It's named hard_timeout |
| 302 # in the bot. | 328 # in the bot. |
| 303 execution_timeout_secs = ndb.IntegerProperty( | 329 execution_timeout_secs = ndb.IntegerProperty( |
| 304 validator=_validate_timeout, required=True, indexed=False) | 330 validator=_validate_timeout, required=True, indexed=False) |
| 305 | 331 |
| 306 # Extra arguments to supply to the command `python run_isolated ...`. Can only | 332 # Extra arguments to supply to the command `python run_isolated ...`. Can only |
| 307 # be set if inputs_ref is set. | 333 # be set if inputs_ref.isolated is set. |
| 308 extra_args = ndb.StringProperty(repeated=True, indexed=False) | 334 extra_args = ndb.StringProperty(repeated=True, indexed=False) |
| 309 | 335 |
| 310 # Grace period is the time between signaling the task it timed out and killing | 336 # Grace period is the time between signaling the task it timed out and killing |
| 311 # the process. During this time the process should clean up itself as quickly | 337 # the process. During this time the process should clean up itself as quickly |
| 312 # as possible, potentially uploading partial results back. | 338 # as possible, potentially uploading partial results back. |
| 313 grace_period_secs = ndb.IntegerProperty( | 339 grace_period_secs = ndb.IntegerProperty( |
| 314 validator=_validate_grace, default=30, indexed=False) | 340 validator=_validate_grace, default=30, indexed=False) |
| 315 | 341 |
| 316 # Bot controlled timeout for new bytes from the subprocess. If a subprocess | 342 # Bot controlled timeout for new bytes from the subprocess. If a subprocess |
| 317 # doesn't output new data to stdout for .io_timeout_secs, consider the command | 343 # doesn't output new data to stdout for .io_timeout_secs, consider the command |
| 318 # timed out. Optional. | 344 # timed out. Optional. |
| 319 io_timeout_secs = ndb.IntegerProperty( | 345 io_timeout_secs = ndb.IntegerProperty( |
| 320 validator=_validate_timeout, indexed=False) | 346 validator=_validate_timeout, indexed=False) |
| 321 | 347 |
| 322 # If True, the task can safely be served results from a previously succeeded | 348 # If True, the task can safely be served results from a previously succeeded |
| 323 # task. | 349 # task. |
| 324 idempotent = ndb.BooleanProperty(default=False, indexed=False) | 350 idempotent = ndb.BooleanProperty(default=False, indexed=False) |
| 325 | 351 |
| 326 @property | 352 @property |
| 327 def is_terminate(self): | 353 def is_terminate(self): |
| 328 """If True, it is a terminate request.""" | 354 """If True, it is a terminate request.""" |
| 329 return ( | 355 return ( |
| 330 not self.commands and | 356 not self.commands and |
| 331 not self.command and | 357 not self.command and |
| 332 self.dimensions.keys() == [u'id'] and | 358 self.dimensions.keys() == [u'id'] and |
| 333 not self.inputs_ref and | 359 not (self.inputs_ref and self.inputs_ref.isolated) and |
| 334 not self.env and | 360 not self.env and |
| 335 not self.execution_timeout_secs and | 361 not self.execution_timeout_secs and |
| 336 not self.extra_args and | 362 not self.extra_args and |
| 337 not self.grace_period_secs and | 363 not self.grace_period_secs and |
| 338 not self.io_timeout_secs and | 364 not self.io_timeout_secs and |
| 339 not self.idempotent) | 365 not self.idempotent) |
| 340 | 366 |
| 341 @property | 367 @property |
| 342 def properties_hash(self): | 368 def properties_hash(self): |
| 343 """Calculates the hash for this entity IFF the task is idempotent. | 369 """Calculates the hash for this entity IFF the task is idempotent. |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 356 out = super(TaskProperties, self).to_dict(exclude=['commands']) | 382 out = super(TaskProperties, self).to_dict(exclude=['commands']) |
| 357 out['command'] = self.commands[0] if self.commands else self.command | 383 out['command'] = self.commands[0] if self.commands else self.command |
| 358 return out | 384 return out |
| 359 | 385 |
| 360 def _pre_put_hook(self): | 386 def _pre_put_hook(self): |
| 361 super(TaskProperties, self)._pre_put_hook() | 387 super(TaskProperties, self)._pre_put_hook() |
| 362 if self.commands: | 388 if self.commands: |
| 363 raise datastore_errors.BadValueError( | 389 raise datastore_errors.BadValueError( |
| 364 'commands is not supported anymore') | 390 'commands is not supported anymore') |
| 365 if not self.is_terminate: | 391 if not self.is_terminate: |
| 366 if bool(self.command) == bool(self.inputs_ref): | 392 isolated_input = self.inputs_ref and self.inputs_ref.isolated |
| 367 raise datastore_errors.BadValueError('use one of command or inputs_ref') | 393 if bool(self.command) == bool(isolated_input): |
| 368 if self.extra_args and not self.inputs_ref: | 394 raise datastore_errors.BadValueError( |
| 369 raise datastore_errors.BadValueError('extra_args require inputs_ref') | 395 'use one of command or inputs_ref.isolated') |
| 396 if self.extra_args and not isolated_input: | |
| 397 raise datastore_errors.BadValueError( | |
| 398 'extra_args require inputs_ref.isolated') | |
| 370 if self.inputs_ref: | 399 if self.inputs_ref: |
| 400 self.inputs_ref.is_ref = False | |
| 371 self.inputs_ref._pre_put_hook() | 401 self.inputs_ref._pre_put_hook() |
| 372 | 402 |
| 373 package_names = set() | 403 package_names = set() |
| 374 for p in self.packages: | 404 for p in self.packages: |
| 375 p._pre_put_hook() | 405 p._pre_put_hook() |
| 376 if p.package_name in package_names: | 406 if p.package_name in package_names: |
| 377 raise datastore_errors.BadValueError( | 407 raise datastore_errors.BadValueError( |
| 378 'package %s is specified more than once' % p.package_name) | 408 'package %s is specified more than once' % p.package_name) |
| 379 package_names.add(p.package_name) | 409 package_names.add(p.package_name) |
| 380 self.packages.sort(key=lambda p: p.package_name) | 410 self.packages.sort(key=lambda p: p.package_name) |
| 381 | 411 |
| 382 if self.idempotent: | 412 if self.idempotent: |
| 383 pinned = lambda p: cipd.is_pinned_version(p.version) | 413 pinned = lambda p: cipd.is_pinned_version(p.version) |
| 384 if self.packages and any(not pinned(p) for p in self.packages): | 414 if self.packages and any(not pinned(p) for p in self.packages): |
| 385 raise datastore_errors.BadValueError( | 415 raise datastore_errors.BadValueError( |
| 386 'an idempotent task cannot have unpinned packages; ' | 416 'an idempotent task cannot have unpinned packages; ' |
| 387 'use instance IDs or tags as package versions') | 417 'use instance IDs or tags as package versions') |
| 388 | 418 |
| 389 | 419 |
| 390 | |
| 391 class TaskRequest(ndb.Model): | 420 class TaskRequest(ndb.Model): |
| 392 """Contains a user request. | 421 """Contains a user request. |
| 393 | 422 |
| 394 Key id is a decreasing integer based on time since utils.EPOCH plus some | 423 Key id is a decreasing integer based on time since utils.EPOCH plus some |
| 395 randomness on lower order bits. See _new_request_key() for the complete gory | 424 randomness on lower order bits. See _new_request_key() for the complete gory |
| 396 details. | 425 details. |
| 397 | 426 |
| 398 There is also "old style keys" which inherit from a fake root entity | 427 There is also "old style keys" which inherit from a fake root entity |
| 399 TaskRequestShard. | 428 TaskRequestShard. |
| 400 TODO(maruel): Remove support 2015-10-01 once entities are deleted. | 429 TODO(maruel): Remove support 2015-10-01 once entities are deleted. |
| (...skipping 301 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 702 _put_request(request) | 731 _put_request(request) |
| 703 return request | 732 return request |
| 704 | 733 |
| 705 | 734 |
| 706 def validate_priority(priority): | 735 def validate_priority(priority): |
| 707 """Throws ValueError if priority is not a valid value.""" | 736 """Throws ValueError if priority is not a valid value.""" |
| 708 if 0 > priority or MAXIMUM_PRIORITY < priority: | 737 if 0 > priority or MAXIMUM_PRIORITY < priority: |
| 709 raise datastore_errors.BadValueError( | 738 raise datastore_errors.BadValueError( |
| 710 'priority (%d) must be between 0 and %d (inclusive)' % | 739 'priority (%d) must be between 0 and %d (inclusive)' % |
| 711 (priority, MAXIMUM_PRIORITY)) | 740 (priority, MAXIMUM_PRIORITY)) |
| OLD | NEW |