OLD | NEW |
1 # Copyright 2015 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """BuildBucketIntegrator integrates Buildbot and Buildbucket.""" | 5 """BuildBucketIntegrator integrates Buildbot and Buildbucket.""" |
6 | 6 |
7 import datetime | 7 import datetime |
8 import itertools | |
9 import json | 8 import json |
10 import logging | 9 import logging |
11 import traceback | |
12 | 10 |
13 from buildbot.util import deferredLocked | 11 from buildbot.util import deferredLocked |
14 from master.buildbucket import common, changestore | 12 from master.buildbucket import changestore |
15 from master.buildbucket.common import log | |
16 from twisted.internet import defer, reactor | 13 from twisted.internet import defer, reactor |
17 from twisted.internet.defer import inlineCallbacks, returnValue | 14 from twisted.internet.defer import inlineCallbacks, returnValue |
18 | 15 |
19 from . import common | 16 from . import common |
20 | 17 |
21 # buildbucket API-related constants. | 18 # buildbucket API-related constants. |
22 # time enough to schedule and start a build. | 19 # time enough to schedule and start a build. |
23 LEASE_DURATION = datetime.timedelta(hours=1) | 20 LEASE_DURATION = datetime.timedelta(hours=1) |
24 DEFAULT_HEARTBEAT_INTERVAL = datetime.timedelta(minutes=5) | 21 DEFAULT_HEARTBEAT_INTERVAL = datetime.timedelta(minutes=5) |
25 # Maximum integer that max_builds buildbucket parameter can take. | 22 # Maximum integer that max_builds buildbucket parameter can take. |
(...skipping 21 matching lines...) Expand all Loading... |
47 # It is a dict build_id -> lease, where lease is a dict with keys: | 44 # It is a dict build_id -> lease, where lease is a dict with keys: |
48 # - key: lease key | 45 # - key: lease key |
49 # - build_request: BuildRequestGateway | 46 # - build_request: BuildRequestGateway |
50 # - build: if build_started with same build_id was called, | 47 # - build: if build_started with same build_id was called, |
51 # the associated build. | 48 # the associated build. |
52 # _lease is None on creation, but it gets loaded from the database in | 49 # _lease is None on creation, but it gets loaded from the database in |
53 # _ensure_leases_loaded(). | 50 # _ensure_leases_loaded(). |
54 | 51 |
55 def __init__( | 52 def __init__( |
56 self, buckets, build_params_hook=None, max_lease_count=None, | 53 self, buckets, build_params_hook=None, max_lease_count=None, |
57 heartbeat_interval=None): | 54 heartbeat_interval=None, change_store_factory=None): |
58 """Creates a BuildBucketIntegrator. | 55 """Creates a BuildBucketIntegrator. |
59 | 56 |
60 Args: | 57 Args: |
61 buckets (list of str): poll only builds in any of |buckets|. | 58 buckets (list of str): poll only builds in any of |buckets|. |
62 build_params_hook (func): If not None, a callable with arguments | 59 build_params_hook (func): If not None, a callable with arguments |
63 (params, build) that can modify the supplied parameters during | 60 (params, build) that can modify the supplied parameters during |
64 validation. | 61 validation. |
65 | 62 |
66 If a ValueError is raised, the build will be marked as an | 63 If a ValueError is raised, the build will be marked as an |
67 INVALID_BUILD_DEFINITION failure and the error message will be | 64 INVALID_BUILD_DEFINITION failure and the error message will be |
68 propagated to the BuildBucket status. | 65 propagated to the BuildBucket status. |
69 max_lease_count (int): maximum number of builds that can be leased at a | 66 max_lease_count (int): maximum number of builds that can be leased at a |
70 time. Defaults to the number of connected slaves. | 67 time. Defaults to the number of connected slaves. |
71 heartbeat_interval (datetime.timedelta): frequency of build heartbeats. | 68 heartbeat_interval (datetime.timedelta): frequency of build heartbeats. |
72 Defaults to 1 minute. | 69 Defaults to 1 minute. |
| 70 change_store_factory: function (BuildbotGateway) => ChangeStore. |
73 """ | 71 """ |
74 assert buckets, 'Buckets not specified' | 72 assert buckets, 'Buckets not specified' |
75 assert max_lease_count is None or isinstance(max_lease_count, int) | 73 assert max_lease_count is None or isinstance(max_lease_count, int) |
76 if max_lease_count is not None: | 74 if max_lease_count is not None: |
77 assert max_lease_count >= 1 | 75 assert max_lease_count >= 1 |
78 self.buckets = buckets[:] | 76 self.buckets = buckets[:] |
79 self.build_params_hook = build_params_hook | 77 self.build_params_hook = build_params_hook |
80 self.buildbot = None | 78 self.buildbot = None |
81 self.buildbucket_service = None | 79 self.buildbucket_service = None |
82 self._find_change_cache = None | 80 self._find_change_cache = None |
83 self.started = False | 81 self.started = False |
84 self.changes = None | 82 self.changes = None |
85 self.poll_lock = defer.DeferredLock() | 83 self.poll_lock = defer.DeferredLock() |
86 self._max_lease_count = max_lease_count | 84 self._max_lease_count = max_lease_count |
87 self._leases = None | 85 self._leases = None |
88 self.heartbeat_interval = heartbeat_interval or DEFAULT_HEARTBEAT_INTERVAL | 86 self.heartbeat_interval = heartbeat_interval or DEFAULT_HEARTBEAT_INTERVAL |
| 87 self.change_store_factory = change_store_factory or changestore.ChangeStore |
89 | 88 |
90 def get_max_lease_count(self): | 89 def get_max_lease_count(self): |
91 if self._max_lease_count: | 90 if self._max_lease_count: |
92 return self._max_lease_count | 91 return self._max_lease_count |
93 if not self.buildbot: | 92 if not self.buildbot: |
94 return 0 | 93 return 0 |
95 return len(self.buildbot.get_connected_slaves()) | 94 return len(self.buildbot.get_connected_slaves()) |
96 | 95 |
97 def log(self, message, level=None): | 96 def log(self, message, level=None): |
98 common.log(message, level) | 97 common.log(message, level) |
99 | 98 |
100 def start(self, buildbot, buildbucket_service, change_store_factory=None): | 99 def start(self, buildbot, buildbucket_service): |
101 assert not self.started, 'BuildBucketIntegrator is already started' | 100 assert not self.started, 'BuildBucketIntegrator is already started' |
102 assert buildbot | 101 assert buildbot |
103 assert buildbucket_service | 102 assert buildbucket_service |
104 change_store_factory = change_store_factory or changestore.ChangeStore | |
105 self.buildbot = buildbot | 103 self.buildbot = buildbot |
106 self.buildbucket_service = buildbucket_service | 104 self.buildbucket_service = buildbucket_service |
107 self.buildbucket_service.start() | 105 self.buildbucket_service.start() |
108 self.changes = change_store_factory(buildbot) | 106 self.changes = self.change_store_factory(buildbot) |
109 self.started = True | 107 self.started = True |
110 self.log('integrator started') | 108 self.log('integrator started') |
111 self._do_until_stopped(self.heartbeat_interval, self.send_heartbeats) | 109 self._do_until_stopped(self.heartbeat_interval, self.send_heartbeats) |
112 self._do_until_stopped( | 110 self._do_until_stopped( |
113 LEASE_CLEANUP_INTERVAL, self.clean_completed_build_requests) | 111 LEASE_CLEANUP_INTERVAL, self.clean_completed_build_requests) |
114 | 112 |
115 @inlineCallbacks | 113 @inlineCallbacks |
116 def _get_leases_from_db(self): | 114 def _get_leases_from_db(self): |
117 """Returns currently held leases from the database. | 115 """Returns currently held leases from the database. |
118 | 116 |
(...skipping 524 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
643 'properties': properties_to_send, | 641 'properties': properties_to_send, |
644 }, sort_keys=True), | 642 }, sort_keys=True), |
645 } | 643 } |
646 if status == 'SUCCESS': | 644 if status == 'SUCCESS': |
647 yield self._leased_build_call('succeed', build, body) | 645 yield self._leased_build_call('succeed', build, body) |
648 else: | 646 else: |
649 body['failure_reason'] = ( | 647 body['failure_reason'] = ( |
650 'BUILD_FAILURE' if status == 'FAILURE' | 648 'BUILD_FAILURE' if status == 'FAILURE' |
651 else 'INFRA_FAILURE') | 649 else 'INFRA_FAILURE') |
652 yield self._leased_build_call('fail', build, body) | 650 yield self._leased_build_call('fail', build, body) |
OLD | NEW |