Index: appengine/swarming/server/task_result.py |
diff --git a/appengine/swarming/server/task_result.py b/appengine/swarming/server/task_result.py |
index b905db0cb14a90cdc7ef7c361d44232f105af5cd..07d64ccb0967001a610111a6f4a2b54be6911120 100644 |
--- a/appengine/swarming/server/task_result.py |
+++ b/appengine/swarming/server/task_result.py |
@@ -58,9 +58,11 @@ Graph of schema: |
+---------------+ +---------------+ |
""" |
+import collections |
import datetime |
import logging |
import random |
+import re |
from google.appengine.api import datastore_errors |
from google.appengine.datastore import datastore_query |
@@ -72,6 +74,8 @@ from server import large |
from server import task_pack |
from server import task_request |
+import cipd |
+ |
# Amount of time after which a bot is considered dead. In short, if a bot has |
# not ping in the last 5 minutes while running a task, it is considered dead. |
BOT_PING_TOLERANCE = datetime.timedelta(seconds=5*60) |
@@ -306,6 +310,21 @@ class PerformanceStats(ndb.Model): |
'PerformanceStats.bot_overhead is required') |
+class CipdPins(ndb.Model): |
+ """Specifies which CIPD client and packages were actually installed. |
+ |
+ A part of _TaskResultCommon. |
+ """ |
+ # CIPD package of CIPD client to use. |
+ # client_package.package_name and version are provided. |
+ # client_package.path will be None. |
+ client_package = ndb.LocalStructuredProperty(task_request.CipdPackage) |
+ |
+ # List of packages to install in $CIPD_PATH prior task execution. |
+ packages = ndb.LocalStructuredProperty(task_request.CipdPackage, |
+ repeated=True) |
+ |
+ |
class _TaskResultCommon(ndb.Model): |
"""Contains properties that is common to both TaskRunResult and |
TaskResultSummary. |
@@ -373,6 +392,9 @@ class _TaskResultCommon(ndb.Model): |
# set. The isolateserver and namespace should match. |
outputs_ref = ndb.LocalStructuredProperty(task_request.FilesRef) |
+ # The pinned versions of all the CIPD packages used in the task. |
+ cipd_pins = ndb.LocalStructuredProperty(CipdPins) |
+ |
@property |
def can_be_canceled(self): |
"""Returns True if the task is in a state that can be canceled.""" |
@@ -564,6 +586,24 @@ class _TaskResultCommon(ndb.Model): |
out = yield TaskOutput.get_output_async(output_key, self.stdout_chunks) |
raise ndb.Return(out) |
+ def validate(self, request): |
+ """Validation that requires the task_request. |
+ |
+ Full validation includes calling this method, and the checks in |
+ _pre_put_hook. |
+ |
+ Raises ValueError if this is invalid, otherwise returns None. |
+ """ |
+ props = request.properties |
+ |
+ if props.cipd_input and self.cipd_pins: |
+ with cipd.pin_check_fn(None, None) as check: |
+ check(props.cipd_input.client_package, self.cipd_pins.client_package) |
+ if len(props.cipd_input.packages) != len(self.cipd_pins.packages): |
+ raise ValueError('Mismatched package lengths') |
+ for a, b in zip(props.cipd_input.packages, self.cipd_pins.packages): |
+ check(a, b) |
+ |
def _pre_put_hook(self): |
"""Use extra validation that cannot be validated throught 'validator'.""" |
super(_TaskResultCommon, self)._pre_put_hook() |
@@ -782,6 +822,7 @@ class TaskResultSummary(_TaskResultCommon): |
def reset_to_pending(self): |
"""Resets this entity to pending state.""" |
+ self.cipd_pins = None |
self.duration = None |
self.exit_code = None |
self.internal_failure = False |