Merge changes I3bbae9f6,I23cc7581,I90ab6bb3,I4164766a into main
* changes:
Start gradual rollout of disabling bazel mode
Validate feature param values
Cache the controlled feature enablement result
Send metric for partially rolled out features
diff --git a/atest/arg_parser.py b/atest/arg_parser.py
index 3250525..05d7720 100644
--- a/atest/arg_parser.py
+++ b/atest/arg_parser.py
@@ -20,6 +20,7 @@
from atest import bazel_mode
from atest import constants
+from atest import rollout_control
from atest.atest_utils import BuildOutputMode
@@ -119,7 +120,7 @@
)
parser.add_argument(
'--bazel-mode',
- default=True,
+ default=not rollout_control.disable_bazel_mode_by_default.is_enabled(),
action='store_true',
help='Run tests using Bazel (default: True).',
)
diff --git a/atest/atest_enum.py b/atest/atest_enum.py
index 33b9861..d44fdf2 100644
--- a/atest/atest_enum.py
+++ b/atest/atest_enum.py
@@ -117,6 +117,10 @@
IS_PLOCATEDB_LOCKED = 63
# Device update duration
DEVICE_UPDATE_MS = 64
+ # The ID of the feature that is controlled by rollout control. Positive value
+ # means the feature is enabled, negative value means disabled.
+ ROLLOUT_CONTROLLED_FEATURE_ID = 65
+ ROLLOUT_CONTROLLED_FEATURE_ID_OVERRIDE = 66
@unique
diff --git a/atest/rollout_control.py b/atest/rollout_control.py
index be5c1da..df146f5 100644
--- a/atest/rollout_control.py
+++ b/atest/rollout_control.py
@@ -15,17 +15,24 @@
"""Rollout control for Atest features."""
+import functools
import getpass
import hashlib
import logging
import os
+from atest import atest_enum
+from atest.metrics import metrics
class RolloutControlledFeature:
"""Base class for Atest features under rollout control."""
def __init__(
- self, name: str, rollout_percentage: float, env_control_flag: str
+ self,
+ name: str,
+ rollout_percentage: float,
+ env_control_flag: str,
+ feature_id: int = None,
):
"""Initializes the object.
@@ -36,10 +43,22 @@
env_control_flag: The environment variable name to override the feature
enablement. When set, 'true' or '1' means enable, other values means
disable.
+ feature_id: The ID of the feature that is controlled by rollout control
+ for metric collection purpose. Must be a positive integer.
"""
+ if rollout_percentage < 0 or rollout_percentage > 100:
+ raise ValueError(
+ 'Rollout percentage must be in [0, 100]. Got %s instead.'
+ % rollout_percentage
+ )
+ if feature_id is not None and feature_id <= 0:
+ raise ValueError(
+ 'Feature ID must be a positive integer. Got %s instead.' % feature_id
+ )
self._name = name
self._rollout_percentage = rollout_percentage
self._env_control_flag = env_control_flag
+ self._feature_id = feature_id
def _check_env_control_flag(self) -> bool | None:
"""Checks the environment variable to override the feature enablement.
@@ -51,6 +70,7 @@
return None
return os.environ[self._env_control_flag] in ('TRUE', 'True', 'true', '1')
+ @functools.cache
def is_enabled(self, username: str | None = None) -> bool:
"""Checks whether the current feature is enabled for the user.
@@ -69,6 +89,13 @@
'enabled' if override_flag_value else 'disabled',
self._env_control_flag,
)
+ if self._feature_id:
+ metrics.LocalDetectEvent(
+ detect_type=atest_enum.DetectType.ROLLOUT_CONTROLLED_FEATURE_ID_OVERRIDE,
+ result=self._feature_id
+ if override_flag_value
+ else -self._feature_id,
+ )
return override_flag_value
if username is None:
@@ -95,4 +122,18 @@
username,
)
+ if self._feature_id and 0 < self._rollout_percentage < 100:
+ metrics.LocalDetectEvent(
+ detect_type=atest_enum.DetectType.ROLLOUT_CONTROLLED_FEATURE_ID,
+ result=self._feature_id if is_enabled else -self._feature_id,
+ )
+
return is_enabled
+
+
+disable_bazel_mode_by_default = RolloutControlledFeature(
+ name='disable_bazel_mode_by_default',
+ rollout_percentage=0,
+ env_control_flag='DISABLE_BAZEL_MODE_BY_DEFAULT',
+ feature_id=1,
+)