buildgrid 0.3.4__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- buildgrid/server/app/cli.py +4 -4
- buildgrid/server/app/settings/parser.py +46 -17
- buildgrid/server/app/settings/schema.yml +16 -2
- buildgrid/server/bots/service.py +15 -12
- buildgrid/server/cas/storage/index/index_abc.py +0 -7
- buildgrid/server/cas/storage/index/sql.py +91 -215
- buildgrid/server/cas/storage/redis_fmb_cache.py +220 -0
- buildgrid/server/enums.py +5 -0
- buildgrid/server/metrics_names.py +0 -2
- buildgrid/server/scheduler/impl.py +328 -108
- buildgrid/server/sql/alembic/versions/3737630fc9cf_remove_deleted_column_from_sql_cas_index.py +43 -0
- buildgrid/server/sql/alembic/versions/ff09fbc30c3e_add_bots_version.py +47 -0
- buildgrid/server/sql/models.py +1 -2
- buildgrid/server/sql/provider.py +6 -1
- buildgrid/server/sql/utils.py +3 -3
- buildgrid/server/utils/bots.py +1 -1
- buildgrid/server/version.py +1 -1
- {buildgrid-0.3.4.dist-info → buildgrid-0.4.0.dist-info}/METADATA +2 -2
- {buildgrid-0.3.4.dist-info → buildgrid-0.4.0.dist-info}/RECORD +23 -20
- {buildgrid-0.3.4.dist-info → buildgrid-0.4.0.dist-info}/WHEEL +1 -1
- {buildgrid-0.3.4.dist-info → buildgrid-0.4.0.dist-info}/entry_points.txt +0 -0
- {buildgrid-0.3.4.dist-info → buildgrid-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {buildgrid-0.3.4.dist-info → buildgrid-0.4.0.dist-info}/top_level.txt +0 -0
buildgrid/server/app/cli.py
CHANGED
|
@@ -26,10 +26,10 @@ import logging
|
|
|
26
26
|
import os
|
|
27
27
|
import sys
|
|
28
28
|
from typing import Any, cast
|
|
29
|
+
from importlib.resources import files
|
|
29
30
|
|
|
30
31
|
import click
|
|
31
32
|
import grpc
|
|
32
|
-
from importlib_resources import files
|
|
33
33
|
|
|
34
34
|
from buildgrid.server.logging import JSONFormatter
|
|
35
35
|
from buildgrid.server.metadata import ctx_grpc_request_id
|
|
@@ -64,9 +64,9 @@ class App(click.Group):
|
|
|
64
64
|
def list_commands(self, ctx: Any) -> list[str]:
|
|
65
65
|
"""Lists available command names."""
|
|
66
66
|
commands = []
|
|
67
|
-
for
|
|
68
|
-
if
|
|
69
|
-
command_name =
|
|
67
|
+
for path in cmd_folder.iterdir():
|
|
68
|
+
if path.name.endswith(".py") and path.name.startswith("cmd_"):
|
|
69
|
+
command_name = path.name[4:-3].replace("_", "-")
|
|
70
70
|
commands.append(command_name)
|
|
71
71
|
commands.sort()
|
|
72
72
|
|
|
@@ -18,6 +18,7 @@ import sys
|
|
|
18
18
|
from collections import defaultdict
|
|
19
19
|
from typing import TYPE_CHECKING, Any, Callable, Hashable, Iterable, Sequence, TypedDict, TypeVar
|
|
20
20
|
from urllib.parse import urlparse
|
|
21
|
+
from importlib.resources import files
|
|
21
22
|
|
|
22
23
|
import buildgrid_metering.client as metering
|
|
23
24
|
import click
|
|
@@ -26,7 +27,6 @@ import jsonschema
|
|
|
26
27
|
import requests
|
|
27
28
|
import yaml
|
|
28
29
|
from buildgrid_metering.client.exceptions import MeteringServiceClientError, MeteringServiceError
|
|
29
|
-
from importlib_resources import files
|
|
30
30
|
|
|
31
31
|
from buildgrid.server.actioncache.caches.action_cache_abc import ActionCacheABC
|
|
32
32
|
from buildgrid.server.actioncache.caches.lru_cache import LruActionCache
|
|
@@ -99,6 +99,7 @@ if TYPE_CHECKING:
|
|
|
99
99
|
from buildgrid.server.actioncache.caches.redis_cache import RedisActionCache
|
|
100
100
|
from buildgrid.server.cas.storage.index.redis import RedisIndex
|
|
101
101
|
from buildgrid.server.cas.storage.redis import RedisStorage
|
|
102
|
+
from buildgrid.server.cas.storage.redis_fmb_cache import RedisFMBCache
|
|
102
103
|
from buildgrid.server.redis.provider import RedisProvider
|
|
103
104
|
|
|
104
105
|
|
|
@@ -459,6 +460,38 @@ def load_redis_index(storage: StorageABC, redis: "RedisProvider", prefix: str |
|
|
|
459
460
|
return RedisIndex(redis=redis, storage=storage, prefix=prefix)
|
|
460
461
|
|
|
461
462
|
|
|
463
|
+
@object_tag("!redis-fmb-cache")
|
|
464
|
+
def load_redis_fmb_cache(
|
|
465
|
+
storage: StorageABC, redis: "RedisProvider", ttl_secs: int, prefix: str | None = None
|
|
466
|
+
) -> "RedisFMBCache":
|
|
467
|
+
"""Generates :class:`buildgrid.server.cas.storage.redis_fmb_cache.RedisFMBCache`
|
|
468
|
+
using the tag ``!redis-fmb-cache``.
|
|
469
|
+
|
|
470
|
+
Usage
|
|
471
|
+
.. code:: yaml
|
|
472
|
+
|
|
473
|
+
- !redis-fmb-cache
|
|
474
|
+
storage: *cas-storage
|
|
475
|
+
redis: *redis
|
|
476
|
+
ttl-secs: 3600 # 1 hour
|
|
477
|
+
prefix: "D"
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
storage(:class:`buildgrid.server.cas.storage.storage_abc.StorageABC`):
|
|
481
|
+
Instance of storage to use. This must be a storage object constructed using
|
|
482
|
+
a YAML tag ending in ``-storage``, for example ``!disk-storage``.
|
|
483
|
+
redis (:class:`buildgrid.server.redis.provider.RedisProvider`): A configured Redis
|
|
484
|
+
connection manager. This must be an object with an ``!redis-connection`` YAML tag.
|
|
485
|
+
prefix (str): An optional prefix to use to prefix keys written by this index. If not
|
|
486
|
+
specified a prefix of "C" is used.
|
|
487
|
+
"""
|
|
488
|
+
|
|
489
|
+
# Import here so there is no global buildgrid dependency on redis
|
|
490
|
+
from buildgrid.server.cas.storage.redis_fmb_cache import RedisFMBCache
|
|
491
|
+
|
|
492
|
+
return RedisFMBCache(redis, storage, ttl_secs, prefix)
|
|
493
|
+
|
|
494
|
+
|
|
462
495
|
@object_tag("!replicated-storage")
|
|
463
496
|
def load_replicated_storage(
|
|
464
497
|
storages: list[StorageABC],
|
|
@@ -1358,9 +1391,9 @@ def load_static_property_set(
|
|
|
1358
1391
|
def load_sql_index(
|
|
1359
1392
|
storage: StorageABC,
|
|
1360
1393
|
sql: SqlProvider,
|
|
1394
|
+
sql_ro: SqlProvider | None = None,
|
|
1361
1395
|
window_size: int = 1000,
|
|
1362
1396
|
inclause_limit: int = -1,
|
|
1363
|
-
fallback_on_get: bool = False,
|
|
1364
1397
|
max_inline_blob_size: int = 0,
|
|
1365
1398
|
refresh_accesstime_older_than: int = 0,
|
|
1366
1399
|
) -> SQLIndex:
|
|
@@ -1371,13 +1404,12 @@ def load_sql_index(
|
|
|
1371
1404
|
.. code:: yaml
|
|
1372
1405
|
|
|
1373
1406
|
- !sql-index
|
|
1374
|
-
#
|
|
1375
|
-
# with a `&cas-storage` anchor
|
|
1407
|
+
# Assuming the YAML anchors are defined elsewhere in the config file
|
|
1376
1408
|
storage: *cas-storage
|
|
1377
1409
|
sql: *sql
|
|
1410
|
+
sql-ro: *readonly-sql
|
|
1378
1411
|
window-size: 1000
|
|
1379
1412
|
inclause-limit: -1
|
|
1380
|
-
fallback-on-get: no
|
|
1381
1413
|
max-inline-blob-size: 256
|
|
1382
1414
|
refresh-accesstime-older-than: 0
|
|
1383
1415
|
|
|
@@ -1385,16 +1417,18 @@ def load_sql_index(
|
|
|
1385
1417
|
storage(:class:`buildgrid.server.cas.storage.storage_abc.StorageABC`):
|
|
1386
1418
|
Instance of storage to use. This must be a storage object constructed using
|
|
1387
1419
|
a YAML tag ending in ``-storage``, for example ``!disk-storage``.
|
|
1420
|
+
sql (:class:`buildgrid.server.sql.provider.SqlProvider`): A configured SQL
|
|
1421
|
+
connection manager. This must be an object with an ``!sql-connection`` YAML tag.
|
|
1422
|
+
sql_ro (:class:`buildgrid.server.sql.provider.SqlProvider`): Similar to `sql`,
|
|
1423
|
+
but used for readonly backend transactions.
|
|
1424
|
+
If set, it should be configured with a replica of main DB using an optional but
|
|
1425
|
+
encouraged readonly role. Permission check is not executed by BuildGrid.
|
|
1426
|
+
If not set, readonly transactions are executed by `sql` object.
|
|
1388
1427
|
window_size (uint): Maximum number of blobs to fetch in one SQL operation
|
|
1389
1428
|
(larger resultsets will be automatically split into multiple queries)
|
|
1390
1429
|
inclause_limit (int): If nonnegative, overrides the default number of variables
|
|
1391
1430
|
permitted per "in" clause. See the buildgrid.server.cas.storage.index.sql.SQLIndex
|
|
1392
1431
|
comments for more details.
|
|
1393
|
-
fallback_on_get (bool): By default, the SQL Index only fetches blobs from the
|
|
1394
|
-
underlying storage if they're present in the index on ``get_blob``/``bulk_read_blobs``
|
|
1395
|
-
requests to minimize interactions with the storage. If this is set, the index
|
|
1396
|
-
instead checks the underlying storage directly on ``get_blob``/``bulk_read_blobs``
|
|
1397
|
-
requests, then loads all blobs found into the index.
|
|
1398
1432
|
max_inline_blob_size (int): Blobs of this size or smaller are stored directly in the index
|
|
1399
1433
|
and not in the backing storage (must be nonnegative).
|
|
1400
1434
|
refresh-accesstime-older-than (int): When reading a blob, its access timestamp will not be
|
|
@@ -1403,19 +1437,14 @@ def load_sql_index(
|
|
|
1403
1437
|
"""
|
|
1404
1438
|
|
|
1405
1439
|
storage_type = type(storage).__name__
|
|
1406
|
-
click.echo(
|
|
1407
|
-
f"SQLIndex: storage={storage_type}, "
|
|
1408
|
-
f"window_size={window_size}, "
|
|
1409
|
-
f"inclause_limit={inclause_limit}, "
|
|
1410
|
-
f"fallback_on_get={fallback_on_get}"
|
|
1411
|
-
)
|
|
1440
|
+
click.echo(f"SQLIndex: storage={storage_type}, window_size={window_size}, inclause_limit={inclause_limit}, ")
|
|
1412
1441
|
click.echo(click.style(f"Creating an SQL CAS Index for {storage_type}\n", fg="green", bold=True))
|
|
1413
1442
|
return SQLIndex(
|
|
1414
1443
|
sql,
|
|
1415
1444
|
storage,
|
|
1445
|
+
sql_ro=sql_ro,
|
|
1416
1446
|
window_size=window_size,
|
|
1417
1447
|
inclause_limit=inclause_limit,
|
|
1418
|
-
fallback_on_get=fallback_on_get,
|
|
1419
1448
|
max_inline_blob_size=max_inline_blob_size,
|
|
1420
1449
|
refresh_accesstime_older_than=refresh_accesstime_older_than,
|
|
1421
1450
|
)
|
|
@@ -24,7 +24,9 @@ properties:
|
|
|
24
24
|
sentry: { "$ref": "#/definitions/sentry" }
|
|
25
25
|
limiter: { "$ref": "#/definitions/limiter" }
|
|
26
26
|
anyOf:
|
|
27
|
-
-
|
|
27
|
+
- anyOf:
|
|
28
|
+
- required: [server, instances]
|
|
29
|
+
- required: [server, services]
|
|
28
30
|
- required: [cleanup]
|
|
29
31
|
|
|
30
32
|
definitions:
|
|
@@ -172,6 +174,7 @@ definitions:
|
|
|
172
174
|
- "$ref": "#/definitions/size-differentiated-storage"
|
|
173
175
|
- "$ref": "#/definitions/sql-storage"
|
|
174
176
|
- "$ref": "#/definitions/with-cache-storage"
|
|
177
|
+
- "$ref": "#/definitions/redis-fmb-cache"
|
|
175
178
|
- "$ref": "#/definitions/index"
|
|
176
179
|
|
|
177
180
|
index:
|
|
@@ -413,6 +416,17 @@ definitions:
|
|
|
413
416
|
action-cache: { "$ref": "#/definitions/cache" }
|
|
414
417
|
required: [kind, action-cache]
|
|
415
418
|
|
|
419
|
+
redis-fmb-cache:
|
|
420
|
+
type: object
|
|
421
|
+
propertyNames: { pattern: "^[A-Za-z0-9-]*$" }
|
|
422
|
+
properties:
|
|
423
|
+
kind: { type: string, enum: ["!redis-fmb-cache"] }
|
|
424
|
+
storage: { "$ref": "#/definitions/storage" }
|
|
425
|
+
redis: { "$ref": "#/definitions/redis-connection" }
|
|
426
|
+
ttl-secs: { type: integer, minimum: 0 }
|
|
427
|
+
prefix: { type: string }
|
|
428
|
+
required: [kind, storage, redis, ttl-secs]
|
|
429
|
+
|
|
416
430
|
redis-index:
|
|
417
431
|
type: object
|
|
418
432
|
propertyNames: { pattern: "^[A-Za-z0-9-]*$" }
|
|
@@ -429,10 +443,10 @@ definitions:
|
|
|
429
443
|
properties:
|
|
430
444
|
kind: { type: string, enum: ["!sql-index"] }
|
|
431
445
|
sql: { "$ref": "#/definitions/sql-connection" }
|
|
446
|
+
sql-ro: { "$ref": "#/definitions/sql-connection" }
|
|
432
447
|
storage: { "$ref": "#/definitions/storage" }
|
|
433
448
|
window-size: { type: integer, minimum: 1 }
|
|
434
449
|
inclause-limit: { type: integer, minimum: 1 }
|
|
435
|
-
fallback-on-get: { type: boolean }
|
|
436
450
|
max-inline-blob-size: { type: integer, minimum: 0, maximum: 1000000000 }
|
|
437
451
|
refresh-accesstime-older-than: { type: integer, minimum: 0 }
|
|
438
452
|
required: [kind, sql, storage]
|
buildgrid/server/bots/service.py
CHANGED
|
@@ -117,33 +117,29 @@ class BaseBotsService:
|
|
|
117
117
|
|
|
118
118
|
LOGGER.debug("Beginning initial lease synchronization.", tags=bot_log_tags(request.bot_session))
|
|
119
119
|
|
|
120
|
-
orig_leases_count = len(request.bot_session.leases)
|
|
121
120
|
with scheduler.bot_notifier.subscription(request.bot_session.name) as event:
|
|
122
121
|
capacity = get_bot_capacity(request.bot_session)
|
|
123
|
-
|
|
122
|
+
old_version = int(request.bot_session.version) if request.bot_session.version else 0
|
|
123
|
+
leases, new_version = scheduler.synchronize_bot_leases(
|
|
124
124
|
request.bot_session.name,
|
|
125
125
|
request.bot_session.bot_id,
|
|
126
126
|
request.bot_session.status,
|
|
127
|
+
old_version,
|
|
127
128
|
request.bot_session.leases,
|
|
128
129
|
lease_id_to_partial_execution_metadata,
|
|
129
130
|
max_capacity=capacity,
|
|
130
131
|
)
|
|
131
132
|
del request.bot_session.leases[:]
|
|
132
133
|
request.bot_session.leases.extend(leases)
|
|
134
|
+
request.bot_session.version = str(new_version)
|
|
133
135
|
|
|
134
136
|
LOGGER.debug("Completed initial lease synchronization.", tags=bot_log_tags(request.bot_session))
|
|
135
137
|
|
|
136
138
|
capabilities = list(map(hash_from_dict, scheduler.property_set.worker_properties(request.bot_session)))
|
|
137
139
|
scheduler.maybe_update_bot_platforms(request.bot_session.name, capabilities)
|
|
138
140
|
|
|
139
|
-
#
|
|
140
|
-
|
|
141
|
-
# completed. Any extra assignments can be picked up when we next synchronize.
|
|
142
|
-
#
|
|
143
|
-
# This should also be skipped if we've removed a lease from the session, to mitigate
|
|
144
|
-
# situations where the scheduler is updated with the new state of a lease, but a fault
|
|
145
|
-
# thereafter causes the worker to retry the old UpdateBotSession call.
|
|
146
|
-
if not leases and not orig_leases_count:
|
|
141
|
+
# Block on lease assignment if we have space and an unchanged set of leases.
|
|
142
|
+
if len(leases) < capacity and old_version == new_version:
|
|
147
143
|
self._request_leases(
|
|
148
144
|
request.bot_session,
|
|
149
145
|
CancellationContext(context),
|
|
@@ -231,11 +227,18 @@ class BaseBotsService:
|
|
|
231
227
|
|
|
232
228
|
# Synchronize the lease again to pick up db changes.
|
|
233
229
|
LOGGER.debug("Synchronizing leases after job assignment wait.", tags=bot_log_tags(bot_session))
|
|
234
|
-
if
|
|
235
|
-
bot_session.name,
|
|
230
|
+
if leases_version := scheduler.synchronize_bot_leases(
|
|
231
|
+
bot_session.name,
|
|
232
|
+
bot_session.bot_id,
|
|
233
|
+
bot_session.status,
|
|
234
|
+
int(bot_session.version) if bot_session.version else 0,
|
|
235
|
+
bot_session.leases,
|
|
236
|
+
max_capacity=capacity,
|
|
236
237
|
):
|
|
238
|
+
leases, new_version = leases_version
|
|
237
239
|
del bot_session.leases[:]
|
|
238
240
|
bot_session.leases.extend(leases)
|
|
241
|
+
bot_session.version = str(new_version)
|
|
239
242
|
|
|
240
243
|
|
|
241
244
|
class BotsService(BotsServicer, BaseBotsService, InstancedServicer[BotsInterface]):
|
|
@@ -34,13 +34,6 @@ from ..storage_abc import StorageABC
|
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
class IndexABC(StorageABC):
|
|
37
|
-
@abc.abstractmethod
|
|
38
|
-
def __init__(self, *, fallback_on_get: bool = False) -> None:
|
|
39
|
-
# If fallback is enabled, the index is required to fetch blobs from
|
|
40
|
-
# storage on each get_blob and bulk_read_blobs request and update
|
|
41
|
-
# itself accordingly.
|
|
42
|
-
self._fallback_on_get = fallback_on_get
|
|
43
|
-
|
|
44
37
|
@abc.abstractmethod
|
|
45
38
|
def least_recent_digests(self) -> Iterator[Digest]:
|
|
46
39
|
"""Generator to iterate through the digests in LRU order"""
|