Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(435)

Side by Side Diff: appengine/swarming/server/task_request.py

Issue 1910713002: swarming: add support for cipd on the server side (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-py@master
Patch Set: nits Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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
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))
OLDNEW
« no previous file with comments | « appengine/swarming/message_conversion.py ('k') | appengine/swarming/server/task_request_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698