コード例 #1
0
ファイル: api.py プロジェクト: xinghun61/infra
def get_build_async(req, res, _ctx, mask):
    """Retrieves a build by id or number."""
    validation.validate_get_build_request(req)

    if req.id:
        logging.info('Build id: %s', req.id)
        build = yield service.get_async(req.id)
    else:
        logging.info(
            'Build id: %s/%d',
            config.builder_id_string(req.builder),
            req.build_number,
        )
        tag = buildtags.build_address_tag(req.builder, req.build_number)
        q = search.Query(
            bucket_ids=[bucket_id_string(req.builder)],
            tags=[tag],
            include_experimental=True,
        )
        found, _ = yield search.search_async(q)
        build = found[0] if found else None

    if not build:
        raise not_found()
    yield build_to_proto_async(build, res, mask)
コード例 #2
0
ファイル: creation.py プロジェクト: xinghun61/infra
 def _ensure_builder_cache(self, build_proto):
     """Ensures that build_proto has a "builder" cache."""
     caches = build_proto.infra.swarming.caches
     if not any(c.path == 'builder' for c in caches):
         h = hashlib.sha256(config.builder_id_string(build_proto.builder))
         builder_cache = caches.add(
             path='builder',
             name='builder_%s_v2' % h.hexdigest(),
         )
         builder_cache.wait_for_warm_cache.FromTimedelta(
             _DEFAULT_BUILDER_CACHE_EXPIRATION)
コード例 #3
0
ファイル: swarming.py プロジェクト: xinghun61/infra
def _builder_matches(builder_id, predicate):
    bs = config.builder_id_string(builder_id)

    def matches(regex_list):
        for r in regex_list:
            try:
                if re.match('^%s$' % r, bs):
                    return True
            except re.error:  # pragma: no cover
                logging.exception('Regex %r failed on %r', r, bs)
        return False

    if matches(predicate.regex_exclude):
        return False
    return not predicate.regex or matches(predicate.regex)
コード例 #4
0
ファイル: model.py プロジェクト: xinghun61/infra
class Build(ndb.Model):
    """Describes a build.

  Build key:
    Build keys are autogenerated, monotonically decreasing integers.
    That is, when sorted by key, new builds are first.
    Build has no parent.

    Build id is a 64 bits integer represented as a string to the user.
    - 1 highest order bit is set to 0 to keep value positive.
    - 43 bits are 43 lower bits of bitwise-inverted time since
      BEGINING_OF_THE_WORLD at 1ms resolution.
      It is good for 2**43 / 365.3 / 24 / 60 / 60 / 1000 = 278 years
      or 2010 + 278 = year 2288.
    - 16 bits are set to a random value. Assuming an instance is internally
      consistent with itself, it can ensure to not reuse the same 16 bits in two
      consecutive requests and/or throttle itself to one request per
      millisecond. Using random value reduces to 2**-15 the probability of
      collision on exact same timestamp at 1ms resolution, so a maximum
      theoretical rate of 65536000 requests/sec but an effective rate in the
      range of ~64k qps without much transaction conflicts. We should be fine.
    - 4 bits are 0. This is to represent the 'version' of the entity
      schema.

    The idea is taken from Swarming TaskRequest entity:
    https://code.google.com/p/swarming/source/browse/appengine/swarming/server/task_request.py#329
  """

    # ndb library sometimes silently ignores memcache errors
    # => memcache is not synchronized with datastore
    # => a build never finishes from the app code perspective
    # => builder is stuck for days.
    # We workaround this problem by setting a timeout.
    _memcache_timeout = 600  # 10m

    # Stores the build proto. The primary property of this entity.
    # Majority of the other properties are either derivatives of this field or
    # legacy.
    #
    # Does not include:
    #   output.properties: see BuildOutputProperties
    #   steps: see BuildSteps.
    #   tags: stored in tags attribute, because we have to index them anyway.
    #   input.properties: see BuildInputProperties.
    #     CAVEAT: field input.properties does exist during build creation, and
    #     moved into BuildInputProperties right before initial datastore.put.
    #   infra: see BuildInfra.
    #     CAVEAT: field infra does exist during build creation, and moved into
    #     BuildInfra right before initial datastore.put.
    #
    # Transition period: proto is either None or complete, i.e. created by
    # creation.py or fix_builds.py.
    proto = datastore_utils.ProtobufProperty(build_pb2.Build)

    # A randomly generated key associated with the created swarming task.
    # Embedded in a build token provided to a swarming task in secret bytes.
    # Needed in case Buildbucket unintentionally creates multiple swarming tasks
    # associated with the build.
    # Populated by swarming.py on swarming task creation.
    # A part of the message in build token (tokens.py) required for UpdateBuild
    # api.
    swarming_task_key = ndb.StringProperty(indexed=False)

    # == proto-derived properties ================================================
    #
    # These properties are derived from "proto" properties.
    # They are used to index builds.

    status = ndb.ComputedProperty(lambda self: self.proto.status,
                                  name='status_v2')

    @property
    def is_ended(self):  # pragma: no cover
        return is_terminal_status(self.proto.status)

    incomplete = ndb.ComputedProperty(lambda self: not self.is_ended)

    # ID of the LUCI project to which this build belongs.
    project = ndb.ComputedProperty(lambda self: self.proto.builder.project)

    # Indexed string "<project_id>/<bucket_name>".
    # Example: "chromium/try".
    # Prefix "luci.<project_id>." is stripped from bucket name.
    bucket_id = ndb.ComputedProperty(lambda self: config.format_bucket_id(
        self.proto.builder.project, self.proto.builder.bucket))

    # Indexed string "<project_id>/<bucket_name>/<builder_name>".
    # Example: "chromium/try/linux-rel".
    # Prefix "luci.<project_id>." is stripped from bucket name.
    builder_id = ndb.ComputedProperty(
        lambda self: config.builder_id_string(self.proto.builder))

    canary = ndb.ComputedProperty(lambda self: self.proto.canary)

    # Value of proto.create_time.
    # Making this property computed is not-entirely trivial because
    # ComputedProperty saves it as int, as opposed to datetime.datetime.
    # TODO(nodir): remove usages of create_time indices, rely on build id ordering
    # instead.
    create_time = ndb.DateTimeProperty()

    # A list of colon-separated key-value pairs. Indexed.
    # Used to populate tags in builds_to_protos_async, if requested.
    tags = ndb.StringProperty(repeated=True)

    # If True, the build won't affect monitoring and won't be surfaced in
    # search results unless explicitly requested.
    experimental = ndb.ComputedProperty(
        lambda self: self.proto.input.experimental)

    # Value of proto.created_by.
    # Making this property computed is not-entirely trivial because
    # ComputedProperty saves it as string, but IdentityProperty stores it
    # as a blob property.
    created_by = auth.IdentityProperty()

    is_luci = ndb.BooleanProperty()

    @property
    def is_ended(self):  # pragma: no cover
        return self.proto.status not in (common_pb2.STATUS_UNSPECIFIED,
                                         common_pb2.SCHEDULED,
                                         common_pb2.STARTED)

    # == Legacy properties =======================================================

    status_legacy = msgprop.EnumProperty(BuildStatus,
                                         default=BuildStatus.SCHEDULED,
                                         name='status')

    status_changed_time = ndb.DateTimeProperty(auto_now_add=True)

    # immutable arbitrary build parameters.
    parameters = datastore_utils.DeterministicJsonProperty(json_type=dict)

    # PubSub message parameters for build status change notifications.
    # TODO(nodir): replace with notification_pb2.NotificationConfig.
    pubsub_callback = ndb.StructuredProperty(PubSubCallback, indexed=False)

    # id of the original build that this build was derived from.
    retry_of = ndb.IntegerProperty()

    # a URL to a build-system-specific build, viewable by a human.
    url = ndb.StringProperty(indexed=False)

    # V1 status properties. Computed by _pre_put_hook.
    result = msgprop.EnumProperty(BuildResult)
    result_details = datastore_utils.DeterministicJsonProperty(json_type=dict)
    cancelation_reason = msgprop.EnumProperty(CancelationReason)
    failure_reason = msgprop.EnumProperty(FailureReason)

    # Lease-time properties.

    # TODO(nodir): move lease to a separate entity under Build.
    # It would be more efficient.
    # current lease expiration date.
    # The moment the build is leased, |lease_expiration_date| is set to
    # (utcnow + lease_duration).
    lease_expiration_date = ndb.DateTimeProperty()
    # None if build is not leased, otherwise a random value.
    # Changes every time a build is leased. Can be used to verify that a client
    # is the leaseholder.
    lease_key = ndb.IntegerProperty(indexed=False)
    # True if the build is currently leased. Otherwise False
    is_leased = ndb.ComputedProperty(lambda self: self.lease_key is not None)
    leasee = auth.IdentityProperty()
    never_leased = ndb.BooleanProperty()

    # ============================================================================

    def _pre_put_hook(self):
        """Checks Build invariants before putting."""
        super(Build, self)._pre_put_hook()

        config.validate_project_id(self.proto.builder.project)
        config.validate_bucket_name(self.proto.builder.bucket)

        self.update_v1_status_fields()
        self.proto.update_time.FromDatetime(utils.utcnow())

        is_started = self.proto.status == common_pb2.STARTED
        is_ended = self.is_ended
        is_leased = self.lease_key is not None
        assert not (is_ended and is_leased)
        assert (self.lease_expiration_date is not None) == is_leased
        assert (self.leasee is not None) == is_leased

        tag_delm = buildtags.DELIMITER
        assert not self.tags or all(tag_delm in t for t in self.tags)

        assert self.proto.HasField('create_time')
        assert self.proto.HasField('end_time') == is_ended
        assert not is_started or self.proto.HasField('start_time')

        def _ts_less(ts1, ts2):
            return ts1.seconds and ts2.seconds and ts1.ToDatetime(
            ) < ts2.ToDatetime()

        assert not _ts_less(self.proto.start_time, self.proto.create_time)
        assert not _ts_less(self.proto.end_time, self.proto.create_time)
        assert not _ts_less(self.proto.end_time, self.proto.start_time)
        self.tags = sorted(set(self.tags))

    def update_v1_status_fields(self):
        """Updates V1 status fields."""
        self.status_legacy = None
        self.result = None
        self.failure_reason = None
        self.cancelation_reason = None

        status = self.proto.status
        if status == common_pb2.SCHEDULED:
            self.status_legacy = BuildStatus.SCHEDULED
        elif status == common_pb2.STARTED:
            self.status_legacy = BuildStatus.STARTED
        elif status == common_pb2.SUCCESS:
            self.status_legacy = BuildStatus.COMPLETED
            self.result = BuildResult.SUCCESS
        elif status == common_pb2.FAILURE:
            self.status_legacy = BuildStatus.COMPLETED
            self.result = BuildResult.FAILURE
            self.failure_reason = FailureReason.BUILD_FAILURE
        elif status == common_pb2.INFRA_FAILURE:
            self.status_legacy = BuildStatus.COMPLETED
            if self.proto.status_details.HasField('timeout'):
                self.result = BuildResult.CANCELED
                self.cancelation_reason = CancelationReason.TIMEOUT
            else:
                self.result = BuildResult.FAILURE
                self.failure_reason = FailureReason.INFRA_FAILURE
        elif status == common_pb2.CANCELED:
            self.status_legacy = BuildStatus.COMPLETED
            self.result = BuildResult.CANCELED
            self.cancelation_reason = CancelationReason.CANCELED_EXPLICITLY
        else:  # pragma: no cover
            assert False, status

    def regenerate_lease_key(self):
        """Changes lease key to a different random int."""
        while True:
            new_key = random.randint(0, 1 << 31)
            if new_key != self.lease_key:  # pragma: no branch
                self.lease_key = new_key
                break

    def clear_lease(self):  # pragma: no cover
        """Clears build's lease attributes."""
        self.lease_key = None
        self.lease_expiration_date = None
        self.leasee = None

    def tags_to_protos(self, dest):
        """Adds non-hidden self.tags to a repeated StringPair container."""
        for t in self.tags:
            k, v = buildtags.parse(t)
            if k not in buildtags.HIDDEN_TAG_KEYS:
                dest.add(key=k, value=v)