| # Copyright 2019 Google LLC |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Datastore model classes.""" |
| import collections |
| import datetime |
| import re |
| from typing import Optional |
| import uuid |
| |
| from protorpc import messages |
| from tradefed_cluster import common |
| from tradefed_cluster.util import ndb_shim as ndb |
| |
| |
| from multitest_transport.util import env |
| from multitest_transport.util import oauth2_util |
| |
| DEFAULT_CLUSTER = 'default' |
| NODE_CONFIG_ID = 1 |
| FILE_CLEANER_SETTINGS_ID = 1 |
| |
| # Maps cancellation reasons into human-readable strings. |
| _TEST_RUN_CANCEL_REASON_MAP = { |
| common.CancelReason.QUEUE_TIMEOUT: 'Queue timeout', |
| common.CancelReason.REQUEST_API: 'User requested', |
| common.CancelReason.COMMAND_ALREADY_CANCELED: 'Command already canceled', |
| common.CancelReason.REQUEST_ALREADY_CANCELED: 'Request already canceled', |
| common.CancelReason.COMMAND_NOT_EXECUTABLE: 'Invalid command', |
| common.CancelReason.INVALID_REQUEST: 'Invalid request', |
| } |
| |
| |
| class NameValuePair(ndb.Model): |
| """A generic name-value pair to store an option. |
| |
| Attributes: |
| name: a name. |
| value: a value. |
| """ |
| name = ndb.StringProperty(required=True) |
| value = ndb.StringProperty() |
| |
| @classmethod |
| def FromDict(cls, d): |
| return [cls(name=k, value=v) for k, v in d.items()] |
| |
| @classmethod |
| def ToDict(cls, pairs): |
| d = collections.OrderedDict() |
| for pair in pairs: |
| d[pair.name] = pair.value |
| return d |
| |
| |
| class NameMultiValuePair(ndb.Model): |
| """A generic name-multi value pair to store an option. |
| |
| Attributes: |
| name: a name. |
| values: a list of values. |
| """ |
| name = ndb.StringProperty(required=True) |
| values = ndb.StringProperty(repeated=True) |
| |
| @classmethod |
| def FromDict(cls, d): |
| return [cls(name=k, values=v) for k, v in d.items()] |
| |
| @classmethod |
| def ToDict(cls, pairs): |
| d = collections.OrderedDict() |
| for pair in pairs: |
| d[pair.name] = pair.values |
| return d |
| |
| |
| class AuthorizationState(messages.Enum): |
| """Authorization states.""" |
| NOT_APPLICABLE = 0 # Does not require authorization |
| AUTHORIZED = 1 # Requires authorization and has credentials |
| UNAUTHORIZED = 2 # Requires authorization and no credentials found |
| |
| |
| class AuthorizationMethod(messages.Enum): |
| """Authorization methods.""" |
| OAUTH2_SERVICE_ACCOUNT = 1 |
| |
| |
| class BuildItemPathType(messages.Enum): |
| """The path type of build item.""" |
| DIRECTORY_FILE = 1 |
| URL = 2 |
| |
| |
| class BuildChannelConfig(ndb.Model): |
| """A build channel config.""" |
| name = ndb.StringProperty(required=True) |
| provider_name = ndb.StringProperty(required=True) |
| options = ndb.StructuredProperty(NameValuePair, repeated=True) |
| credentials = oauth2_util.CredentialsProperty() |
| |
| |
| class ConfigSetStatus(messages.Enum): |
| NOT_IMPORTED = 0 |
| IMPORTED = 1 |
| UPDATABLE = 2 |
| |
| |
| class ConfigSetInfo(ndb.Model): |
| """Metadata for a config set. |
| |
| Attributes: |
| url: origin url where this config can be downloaded from |
| hash: SHA value of the file, used for comparing two files |
| name: name to display to the user |
| description: describes the contents of the file |
| last_update_time: timestamp when the file was last imported |
| """ |
| url = ndb.StringProperty(required=True) |
| hash = ndb.StringProperty() |
| name = ndb.StringProperty(required=True) |
| description = ndb.StringProperty() |
| last_update_time = ndb.StringProperty() |
| |
| |
| class ConfigSetInfoList(ndb.Model): |
| """A list of ConfigSetInfos.""" |
| config_set_infos = ndb.StructuredProperty(ConfigSetInfo, repeated=True) |
| |
| |
| class EventLogLevel(messages.Enum): |
| """Event log levels.""" |
| INFO = 1 |
| WARNING = 2 |
| ERROR = 3 |
| |
| |
| class EventLogEntry(ndb.Model): |
| """Event log entry. The optional parent key defines the related object. |
| |
| Attributes: |
| create_time: creation timestamp |
| level: log level |
| message: log message |
| """ |
| create_time = ndb.DateTimeProperty(auto_now_add=True) |
| level = ndb.EnumProperty(EventLogLevel, required=True) |
| message = ndb.TextProperty(required=True) |
| |
| |
| class TestResourceType(messages.Enum): |
| UNKNOWN = 0 |
| DEVICE_IMAGE = 1 |
| TEST_PACKAGE = 2 |
| SYSTEM_IMAGE = 3 |
| |
| |
| class TestResourceParameters(ndb.Model): |
| """Repeated properties of TestResourceObj and TestResourceDef. |
| |
| Because the test resources are StructuredProperty of tests, they cannot |
| directly contain repeated properties. The test resource models use this class |
| to wrap the repeated properties in a LocalStructuredProperty. |
| |
| Attributes: |
| decompress_files: the files to be decompressed from the downloaded file. |
| """ |
| decompress_files = ndb.StringProperty(repeated=True) |
| |
| @classmethod |
| def Clone(cls, obj): |
| """Copy all properties to a new object if it's not None.""" |
| return cls(decompress_files=obj.decompress_files) if obj else None |
| |
| |
| class TestResourceDef(ndb.Model): |
| """A test resource definition. |
| |
| Attributes: |
| name: a test resource name. |
| default_download_url: a default download URL. |
| test_resource_type: a test resource type. |
| decompress: whether the host should decompress the downloaded file. |
| decompress_dir: the directory where the host decompresses the file. |
| mount_zip: whether to mount zip file. |
| params: test resource parameters. |
| """ |
| name = ndb.StringProperty(required=True) |
| default_download_url = ndb.StringProperty() |
| test_resource_type = ndb.EnumProperty(TestResourceType) |
| decompress = ndb.BooleanProperty() |
| decompress_dir = ndb.StringProperty() |
| mount_zip = ndb.BooleanProperty() |
| params = ndb.LocalStructuredProperty(TestResourceParameters) |
| |
| def ToTestResourceObj(self): |
| """Create a TestResourceObj from this definition.""" |
| return TestResourceObj( |
| name=self.name, |
| url=self.default_download_url, |
| test_resource_type=self.test_resource_type, |
| decompress=self.decompress or False, |
| decompress_dir=self.decompress_dir or '', |
| mount_zip=self.mount_zip or False, |
| params=TestResourceParameters.Clone(self.params)) |
| |
| |
| class TestRunParameter(ndb.Model): |
| """Test run parameter. |
| |
| Attributes: |
| max_retry_on_test_failures: the max number of retry on test failure. |
| invocation_timeout_seconds: the maximum time for each invocation to run. If |
| an invocation(attempt) runs longer than a given timeout, it would be |
| force stopped. |
| output_idle_timeout_seconds: how long a test run's output can be idle before |
| attempting recovery |
| """ |
| max_retry_on_test_failures = ndb.IntegerProperty() |
| invocation_timeout_seconds = ndb.IntegerProperty() |
| output_idle_timeout_seconds = ndb.IntegerProperty() |
| |
| |
| class VisibilityType(messages.Enum): |
| PUBLIC = 0 # Test is visible to any user. |
| HIDDEN = 1 # Test is not visible to any user. |
| |
| |
| class Test(ndb.Model): |
| """A test. |
| |
| Attributes: |
| name: a test name. |
| description: user-friendly string that describes the test suite. |
| test_resource_defs: a list of test resource definitions. |
| command: a TF command line. |
| env_vars: a dict of environment variables to set before running a test. |
| output_file_patterns: a list of file patterns to collect after a test run. |
| result_file: XML result file path. |
| setup_scripts: a list of scripts to run before running a test. |
| jvm_options: a list of JVM options to be passed to TF. |
| java_properties: a dict of Java properties to be passed to TF. |
| context_file_dir: directory where the context file which needs to be passed |
| across attempts is located. |
| context_file_pattern: a regex pattern for the filename of the context file |
| which needs to be passed across attempts. |
| retry_command_line: a command line to use in retry invocations. attempts. |
| runner_sharding_args: extra args to enable runner sharding. It can contain a |
| reference to a desired shard count (e.g. ${TF_SHARD_COUNT}) |
| default_test_run_parameters: default test run parameters. |
| module_config_pattern: a regex pattern for module config files. |
| module_execution_args: extra args to run a specific module. |
| visibility_type: the visibility of a test. |
| upload_file_patterns: a list of regex patterns for the filenames of the test |
| artifacts which need to be uploaded to GCS. |
| """ |
| name = ndb.StringProperty(required=True) |
| description = ndb.StringProperty() |
| test_resource_defs = ndb.StructuredProperty(TestResourceDef, repeated=True) |
| command = ndb.TextProperty(required=True) |
| env_vars = ndb.StructuredProperty(NameValuePair, repeated=True) |
| output_file_patterns = ndb.StringProperty(repeated=True) |
| result_file = ndb.StringProperty() |
| setup_scripts = ndb.StringProperty(repeated=True) |
| jvm_options = ndb.StringProperty(repeated=True) |
| java_properties = ndb.StructuredProperty(NameValuePair, repeated=True) |
| context_file_dir = ndb.StringProperty() |
| context_file_pattern = ndb.StringProperty() |
| retry_command_line = ndb.TextProperty() |
| runner_sharding_args = ndb.StringProperty() |
| default_test_run_parameters = ndb.LocalStructuredProperty(TestRunParameter) |
| module_config_pattern = ndb.StringProperty() |
| module_execution_args = ndb.StringProperty() |
| visibility_type = ndb.EnumProperty( |
| VisibilityType, default=VisibilityType.PUBLIC |
| ) |
| upload_file_patterns = ndb.StringProperty(repeated=True) |
| |
| |
| class ShardingMode(messages.Enum): |
| RUNNER = 0 # Let a test runner to take care of sharding. |
| MODULE = 1 # Schedule each module as a separate command |
| |
| |
| class TradefedConfigObject(ndb.Model): |
| """A Tradefed object and its options. |
| |
| Attributes: |
| class_name: a Tradefed config class name. |
| option_values: a list of option name and value pairs. |
| """ |
| class_name = ndb.StringProperty(required=True) |
| option_values = ndb.LocalStructuredProperty(NameMultiValuePair, repeated=True) |
| |
| |
| class DeviceAction(ndb.Model): |
| """A device action. |
| |
| Attributes: |
| name: a device action name. |
| description: a description. |
| test_resource_defs: a list of test resource definitions. |
| tradefed_target_preparers: a list of Tradefed target preparers. |
| device_type: (obsolete) the type of the devices that require the device |
| action. |
| tradefed_options: key-value pairs to be added to Tradefed configuration. |
| device_spec: the regular expression of the device specs that require the |
| device action. |
| """ |
| name = ndb.StringProperty(required=True) |
| description = ndb.StringProperty() |
| test_resource_defs = ndb.LocalStructuredProperty( |
| TestResourceDef, repeated=True) |
| tradefed_target_preparers = ndb.LocalStructuredProperty( |
| TradefedConfigObject, repeated=True) |
| device_type = ndb.StringProperty() |
| tradefed_options = ndb.LocalStructuredProperty( |
| NameMultiValuePair, repeated=True) |
| device_spec = ndb.StringProperty() |
| |
| |
| class TestRunPhase(messages.Enum): |
| """Test run phases.""" |
| UNKNOWN = 0 # Invalid phase |
| BEFORE_RUN = 1 # Before run is started (but is created) |
| BEFORE_ATTEMPT = 2 # Before an attempt is created (task not sent to runner) |
| AFTER_ATTEMPT = 3 # After an attempt is completed (successfully or not) |
| AFTER_RUN = 4 # After run is completed (successfully or not) |
| ON_SUCCESS = 5 # After run is completed successfully (may have test failures) |
| ON_ERROR = 6 # After run fails to complete due to errors |
| MANUAL = 7 # Phase triggered manually |
| |
| |
| class TestRunAction(ndb.Model): |
| """Test run action, describes a specific hook execution. |
| |
| Attributes: |
| name: action name. |
| description: action description. |
| hook_class_name: run hook class identifier. |
| phases: phases during which hook should be triggered. |
| options: key-value pairs to use when configuring the hook. |
| tradefed_result_reporters: list of TF result reporters. |
| credentials: stored OAuth2 credentials. |
| """ |
| name = ndb.StringProperty(required=True) |
| description = ndb.StringProperty() |
| hook_class_name = ndb.StringProperty(required=True) |
| phases = ndb.EnumProperty(TestRunPhase, repeated=True) |
| options = ndb.LocalStructuredProperty(NameValuePair, repeated=True) |
| tradefed_result_reporters = ndb.LocalStructuredProperty( |
| TradefedConfigObject, repeated=True) |
| credentials = oauth2_util.CredentialsProperty() |
| |
| |
| class TestRunActionRef(ndb.Model): |
| """Test run action reference, with additional overridden options. |
| |
| Attributes: |
| action_key: test run action to execute. |
| options: additional key-value pairs to use when configuring the hook. |
| """ |
| action_key = ndb.KeyProperty(TestRunAction, required=True) |
| options = ndb.LocalStructuredProperty(NameValuePair, repeated=True) |
| |
| def ToAction(self): |
| """Converts this ref into a test run action.""" |
| action = self.action_key.get() |
| if not action: |
| raise ValueError('Test run action %s not found' % self.action_key) |
| options = NameValuePair.ToDict(action.options or []) |
| options.update(NameValuePair.ToDict(self.options or [])) |
| action.options = NameValuePair.FromDict(options) |
| return action |
| |
| |
| class TestResourceObj(ndb.Model): |
| """A test resource object. |
| |
| The "Obj" suffix was added to avoid name collistion with TFC TestResource. |
| |
| Attributes: |
| name: a test resource name. |
| url: a URL. |
| cache_url: a URL for a cached copy. |
| test_resource_type: a test resource type. |
| decompress: whether the host should decompress the downloaded file. |
| decompress_dir: the directory where the host decompresses the file. |
| mount_zip: whether to mount a zip file. |
| params: test resource parameters. |
| """ |
| name = ndb.StringProperty(required=True) |
| url = ndb.StringProperty() |
| cache_url = ndb.StringProperty() |
| test_resource_type = ndb.EnumProperty(TestResourceType) |
| decompress = ndb.BooleanProperty() |
| decompress_dir = ndb.StringProperty() |
| mount_zip = ndb.BooleanProperty() |
| params = ndb.LocalStructuredProperty(TestResourceParameters) |
| |
| |
| class TestRunConfig(ndb.Model): |
| """A test run config. |
| |
| Attributes: |
| test_key: a Test key. |
| cluster: a cluster to run a test in. |
| command: the command to run the test suite |
| retry_command: the command to retry this test run |
| device_specs: device requirements expressed in space-separated list of |
| key-value pairs (e.g. "product:bramble sim_state:LOADED"). |
| run_target: (deprecated) a run target. Only used when device_specs is empty. |
| run_count: a run count. |
| shard_count: a shard count. |
| sharding_mode: a sharding mode. |
| extra_args: a string containing extra arguments. |
| retry_extra_args: extra arguments used when retrying. |
| max_retry_on_test_failures: the max number of retry on test failure. |
| queue_timeout_seconds: how long a test run can stay in QUEUED state before |
| being cancelled |
| invocation_timeout_seconds: the maximum time for each invocation to run. If |
| an invocation(attempt) runs longer than a given timeout, it would be |
| force stopped. |
| output_idle_timeout_seconds: how long a test run's output can be idle before |
| attempting recovery |
| before_device_action_keys: device actions to execute before running a test. |
| test_run_action_refs: test run actions to execute during a test. |
| test_resource_objs: path to the files to use for test resources. |
| use_parallel_setup: a flag on whether to setup devices in parallel. |
| allow_partial_device_match: a flag on whether to allow partial device match |
| or not |
| """ |
| test_key = ndb.KeyProperty(kind=Test, required=True) |
| cluster = ndb.StringProperty(required=True, default=DEFAULT_CLUSTER) |
| command = ndb.TextProperty(required=True, default='') |
| retry_command = ndb.TextProperty(required=True, default='') |
| device_specs = ndb.StringProperty(repeated=True) |
| run_target = ndb.StringProperty() |
| run_count = ndb.IntegerProperty(required=True, default=1) |
| shard_count = ndb.IntegerProperty(required=True, default=1) |
| sharding_mode = ndb.EnumProperty( |
| ShardingMode, default=ShardingMode.RUNNER) |
| extra_args = ndb.StringProperty() # TODO: Deprecated |
| retry_extra_args = ndb.StringProperty() # TODO: Deprecated |
| max_retry_on_test_failures = ndb.IntegerProperty() |
| queue_timeout_seconds = ndb.IntegerProperty( |
| required=True, default=env.DEFAULT_QUEUE_TIMEOUT_SECONDS) |
| invocation_timeout_seconds = ndb.IntegerProperty( |
| default=env.DEFAULT_INVOCATION_TIMEOUT_SECONDS) |
| output_idle_timeout_seconds = ndb.IntegerProperty( |
| default=env.DEFAULT_OUTPUT_IDLE_TIMEOUT_SECONDS) |
| before_device_action_keys = ndb.KeyProperty(DeviceAction, repeated=True) |
| test_run_action_refs = ndb.LocalStructuredProperty( |
| TestRunActionRef, repeated=True) |
| test_resource_objs = ndb.LocalStructuredProperty( |
| TestResourceObj, repeated=True) |
| use_parallel_setup = ndb.BooleanProperty(default=True) |
| allow_partial_device_match = ndb.BooleanProperty(default=False) |
| |
| |
| class TestRunConfigList(ndb.Model): |
| """A list of test run configs. |
| |
| Attributes: |
| test_run_configs: the list of configs |
| """ |
| test_run_configs = ndb.LocalStructuredProperty(TestRunConfig, repeated=True) |
| |
| |
| class TestResourcePipe(ndb.Model): |
| """A pipe which defines where to get test resources from. |
| |
| Attributes: |
| name: a test resource name. |
| url: a url. |
| test_resource_type: a test resource type. |
| """ |
| name = ndb.StringProperty(required=True) |
| url = ndb.StringProperty() |
| test_resource_type = ndb.EnumProperty(TestResourceType) |
| |
| |
| class TestRunSequenceState(messages.Enum): |
| """Completion state for a TestRunSequence.""" |
| RUNNING = 0 |
| CANCELED = 1 |
| COMPLETED = 2 |
| ERROR = 3 |
| |
| |
| class TestRunSequence(ndb.Model): |
| """A list of test run configs to be scheduled as retries. |
| |
| Attributes: |
| state: completion state for this sequence |
| test_run_configs: remaining retries to schedule |
| finished_test_run_ids: list of completed runs scheduled for this sequence |
| """ |
| state = ndb.EnumProperty(TestRunSequenceState, required=True) |
| test_run_configs = ndb.LocalStructuredProperty(TestRunConfig, repeated=True) |
| finished_test_run_ids = ndb.StringProperty(repeated=True) |
| |
| |
| class TestPlan(ndb.Model): |
| """A test plan. |
| |
| Attributes: |
| name: a test resource name. |
| labels: list of strings users can use to categorize test runs and plans. |
| cron_exp: a CRON expression. |
| cron_exp_timezone: timezone to use when processing cron expression. |
| test_run_configs: a list of test run configs. |
| test_run_sequences: a list of test run sequences triggered when running. |
| """ |
| name = ndb.StringProperty(required=True) |
| labels = ndb.StringProperty(repeated=True) |
| cron_exp = ndb.StringProperty() |
| cron_exp_timezone = ndb.StringProperty(default='UTC') |
| test_run_configs = ndb.LocalStructuredProperty(TestRunConfig, repeated=True) |
| test_run_sequences = ndb.LocalStructuredProperty( |
| TestRunConfigList, repeated=True) |
| |
| @classmethod |
| def _post_delete_hook(cls, key, future): |
| status = TestPlanStatus.query(ancestor=key).get() |
| if status: |
| status.key.delete() |
| |
| |
| class TestPlanStatus(ndb.Model): |
| """A test plan status.""" |
| last_run_time = ndb.DateTimeProperty() |
| last_run_keys = ndb.KeyProperty(kind='TestRun', repeated=True) |
| last_run_error = ndb.StringProperty() |
| next_run_time = ndb.DateTimeProperty() |
| next_run_task_name = ndb.StringProperty() |
| |
| |
| class TestRunState(messages.Enum): |
| """Test run state values.""" |
| UNKNOWN = 0 # this is an invalid state |
| PENDING = 1 # A test run is waiting to be kicked-off. |
| QUEUED = 2 # A test run is queued by a runner. |
| RUNNING = 3 # A test run is running by a runner. |
| COMPLETED = 4 # A test run is completed. |
| CANCELED = 5 # A test run is canceled. |
| ERROR = 6 # A test run is finished with an error. |
| |
| FINAL_TEST_RUN_STATES = { |
| TestRunState.COMPLETED, |
| TestRunState.CANCELED, |
| TestRunState.ERROR |
| } |
| |
| |
| class TestContextObj(ndb.Model): |
| """A test context object. |
| |
| The "Obj" suffix was added to avoid name collision with TFC TestContext. |
| |
| Attributes: |
| command_line: a command line. |
| env_vars: environment variables. |
| test_resources: a list of TestResourceObj objects. |
| """ |
| command_line = ndb.TextProperty() |
| env_vars = ndb.LocalStructuredProperty(NameValuePair, repeated=True) |
| test_resources = ndb.LocalStructuredProperty(TestResourceObj, repeated=True) |
| |
| |
| def ToTimestamp(dt): |
| return int((dt - datetime.datetime(1970, 1, 1)).total_seconds()) |
| |
| |
| class TestPackageInfo(ndb.Model): |
| """A test package info. |
| |
| Attributes |
| build_number: test package build number. |
| target_architecture: test package arch. |
| name: A short name of test package. |
| full_name: A full name of test package. |
| version: test package version |
| """ |
| build_number = ndb.StringProperty() |
| target_architecture = ndb.StringProperty() |
| name = ndb.StringProperty() |
| fullname = ndb.StringProperty() |
| version = ndb.StringProperty() |
| |
| |
| class TestDeviceInfo(ndb.Model): |
| """A test package info. |
| |
| Attributes |
| device_serial: Serial identifying the device. It should be unique. |
| hostname: The name of the host this device is connected to. |
| run_target: Run target for the device. |
| build_id: Current build ID in the device. |
| product: Device product (Eg.: flounder). |
| sdk_version: SDK version of the device's build. |
| """ |
| device_serial = ndb.StringProperty() |
| hostname = ndb.StringProperty() |
| run_target = ndb.StringProperty() |
| build_id = ndb.StringProperty() |
| product = ndb.StringProperty() |
| sdk_version = ndb.StringProperty() |
| |
| |
| class TestRun(ndb.Model): |
| """A test run. |
| |
| Attributes: |
| prev_test_run_key: previous (parent) test run key. |
| user: a user who scheduled a test run. |
| labels: list of strings users can use to categorize test runs. |
| test_plan_key: a test plan key. |
| test: a Test object copy made at the test run schedule time. |
| test_run_config: a TestRunConfig object. |
| test_resources: a list of TestResourceObj objects. |
| state: a test run state. |
| is_finalized: True if post-run handlers were executed. |
| output_path: a path to store test outputs to. |
| output_url: a test output URL. |
| prev_test_context: a previous test context object. |
| next_test_context: a test context object from this test run. |
| test_package_info: a test package information |
| test_devices: a list of TestDeviceInfo of DUTs |
| request_id: a TFC request ID. |
| sequence_id: a TestRunSequence ID. |
| total_test_count: the number of total test cases. |
| failed_test_count: the number of failed test cases. |
| failed_test_run_count: the number of test modules that failed to execute. |
| create_time: time a test run is created. |
| update_time: time a test run is last updated. |
| request_event_time: last received TFC request event time. |
| attempt_event_time: last received TFC attempt event time. |
| before_device_actions: device actions used during the run. |
| test_run_actions: test run actions executed during the run. |
| hook_data: additional data used by hooks |
| cancel_reason: cancellation reason |
| error_reason: error reason |
| """ |
| prev_test_run_key = ndb.KeyProperty(kind='TestRun') |
| user = ndb.StringProperty() |
| labels = ndb.StringProperty(repeated=True) |
| test_plan_key = ndb.KeyProperty(TestPlan) |
| test = ndb.LocalStructuredProperty(Test) |
| test_run_config = ndb.LocalStructuredProperty(TestRunConfig) |
| test_resources = ndb.StructuredProperty(TestResourceObj, repeated=True) |
| state = ndb.EnumProperty(TestRunState, default=TestRunState.UNKNOWN) |
| is_finalized = ndb.BooleanProperty() |
| output_path = ndb.StringProperty() |
| output_url = ndb.StringProperty() |
| prev_test_context = ndb.LocalStructuredProperty(TestContextObj) |
| next_test_context = ndb.LocalStructuredProperty(TestContextObj) |
| test_package_info = ndb.StructuredProperty(TestPackageInfo) |
| test_devices = ndb.StructuredProperty(TestDeviceInfo, repeated=True) |
| request_id = ndb.StringProperty() |
| sequence_id = ndb.StringProperty() |
| total_test_count = ndb.IntegerProperty() |
| failed_test_count = ndb.IntegerProperty() |
| failed_test_run_count = ndb.IntegerProperty() |
| |
| create_time = ndb.DateTimeProperty(auto_now_add=True) |
| update_time = ndb.DateTimeProperty(auto_now_add=True) |
| request_event_time = ndb.DateTimeProperty() |
| attempt_event_time = ndb.DateTimeProperty() |
| |
| # Snapshot of the actions executed by the run |
| before_device_actions = ndb.LocalStructuredProperty( |
| DeviceAction, repeated=True) |
| test_run_actions = ndb.LocalStructuredProperty(TestRunAction, repeated=True) |
| hook_data = ndb.JsonProperty(default={}) |
| |
| cancel_reason = ndb.EnumProperty(common.CancelReason) |
| error_reason = ndb.TextProperty() |
| |
| @classmethod |
| def get_by_id(cls, id_, **kwargs): |
| try: |
| # support numeric (legacy) and uuid identifiers |
| id_ = int(id_) |
| except ValueError: |
| pass |
| return TestRun._get_by_id(id_, **kwargs) |
| |
| def _post_put_hook(self, future): |
| self.ToSummary().put() |
| |
| @classmethod |
| def _post_delete_hook(cls, key, future): |
| ndb.delete_multi(ndb.Query(ancestor=key).iter(keys_only=True)) |
| |
| def ToSummary(self): |
| return TestRunSummary( |
| parent=self.key, |
| id=self.key.id(), |
| prev_test_run_key=self.prev_test_run_key, |
| labels=self.labels, |
| test_name=self.test.name if self.test else None, |
| device_specs=( |
| self.test_run_config.device_specs if self.test_run_config else []), |
| run_target=( |
| self.test_run_config.run_target if self.test_run_config else None), |
| state=self.state, |
| test_package_info=self.test_package_info, |
| test_devices=self.test_devices, |
| total_test_count=self.total_test_count, |
| failed_test_count=self.failed_test_count, |
| failed_test_run_count=self.failed_test_run_count, |
| create_time=self.create_time, |
| update_time=self.update_time) |
| |
| def IsFinal(self): |
| """Returns whether a test run is in a final state. |
| |
| Returns: |
| True if a test run is in a final state. Otherwise false. |
| """ |
| return self.state in FINAL_TEST_RUN_STATES |
| |
| def IsRerun(self): |
| """Checks whether this is a rerun of another test run.""" |
| return self.prev_test_context is not None |
| |
| def IsLocalRerun(self): |
| """Checks whether this is a rerun using a local test run ID.""" |
| return self.prev_test_run_key is not None |
| |
| def IsRemoteRerun(self): |
| """Checks whether this is a rerun using an uploaded context file.""" |
| return self.IsRerun() and not self.IsLocalRerun() |
| |
| def GetRerunContextFile(self): |
| """Fetches the rerun context file if it exists.""" |
| if not self.prev_test_context or not self.prev_test_context.test_resources: |
| return None |
| return self.prev_test_context.test_resources[0] |
| |
| def GetContext(self): |
| """Returns a test run context dictionary.""" |
| ctx = { |
| 'MTT_USER': env.USER, |
| 'MTT_HOSTNAME': env.HOSTNAME, |
| 'MTT_VERSION': env.VERSION, |
| 'MTT_TEST_RUN_ID': self.key.id(), |
| 'MTT_TEST_ID': self.test_run_config.test_key.id(), |
| 'MTT_TEST_NAME': self.test.name, |
| 'MTT_TEST_PLAN_NAME': ( |
| self.test_plan_key.get().name if self.test_plan_key else ''), |
| 'MTT_TEST_RUN_CREATE_TIMESTAMP': ToTimestamp(self.create_time), |
| 'MTT_TEST_RUN_CREATE_TIMESTAMP_MILLIS': |
| ToTimestamp(self.create_time) * 1000, |
| } |
| |
| test_resource_map = {} |
| for r in self.test_resources: |
| m = re.match(r'mtt:///android_ci/(\S+)/(\S+)/(\S+)/.*', r.url) |
| if m: |
| test_resource_map[r.test_resource_type] = [ |
| r.url, m.group(1), m.group(2), m.group(3) |
| ] |
| else: |
| test_resource_map[r.test_resource_type] = [r.url, None, None, None] |
| for t in TestResourceType: |
| if t == TestResourceType.UNKNOWN: |
| continue |
| url, branch, target, build_id = test_resource_map.get( |
| t, [None, None, None, None]) |
| ctx['MTT_%s_URL' % t.name] = url or '' |
| ctx['MTT_%s_BRANCH' % t.name] = branch or '' |
| ctx['MTT_%s_BUILD_ID' % t.name] = build_id or '' |
| ctx['MTT_%s_TARGET' % t.name] = target or '' |
| return ctx |
| |
| def GetErrorInfo(self) -> Optional[str]: |
| """Returns additional information if run was cancelled or errored out.""" |
| if self.state == TestRunState.ERROR: |
| return self.error_reason |
| if self.state == TestRunState.CANCELED: |
| return _TEST_RUN_CANCEL_REASON_MAP.get(self.cancel_reason) |
| |
| |
| class TestRunSummary(ndb.Model): |
| """Partial test run information. |
| |
| Attributes: |
| prev_test_run_key: ID of the previous (parent) test run. |
| labels: list of strings users can use to categorize test runs. |
| test_name: name of the Test to run. |
| device_specs: device specs. |
| run_target: run target. |
| state: a test run state. |
| test_package_info: a test package information |
| test_devices: a list of TestDeviceInfo of DUTs |
| total_test_count: the number of total test cases. |
| failed_test_count: the number of failed test cases. |
| failed_test_run_count: the number of test modules that failed to execute. |
| create_time: time a test run is created. |
| update_time: time a test run is last updated. |
| """ |
| prev_test_run_key = ndb.KeyProperty(kind='TestRun') |
| labels = ndb.StringProperty(repeated=True) |
| test_name = ndb.StringProperty() |
| device_specs = ndb.StringProperty(repeated=True) |
| run_target = ndb.StringProperty() |
| state = ndb.EnumProperty(TestRunState, default=TestRunState.UNKNOWN) |
| test_package_info = ndb.StructuredProperty(TestPackageInfo) |
| test_devices = ndb.StructuredProperty(TestDeviceInfo, repeated=True) |
| total_test_count = ndb.IntegerProperty() |
| failed_test_count = ndb.IntegerProperty() |
| failed_test_run_count = ndb.IntegerProperty() |
| create_time = ndb.DateTimeProperty() |
| update_time = ndb.DateTimeProperty() |
| |
| |
| class ReportType(messages.Enum): |
| """Type of a report.""" |
| |
| REPORT_TYPE_UNKNOWN = 0 # Unknown test report. |
| CTS = 1 # Android Compatibility Test Suite. |
| CTS_VERIFIER = 2 # Android Compatibility Test Suite Verifier. |
| GTS = 3 # GMS Core Test Suite. |
| CAT = 4 # CTS Auto Test Suite. |
| WTS = 5 # Wearable Test Suite. |
| WTS_VERIFIER = 6 # Wearable Test Suite Verifier. |
| TVTS = 7 # TV Test Suite. |
| CRT = 8 # Cast Ready Test. |
| PCTS = 9 # Automotive Projected Test Suite. |
| VTS = 10 # Vendor Test Suite (Treble). |
| ATS = 11 # Automotive Test Suite. |
| ATS_VERIFIER = 12 # Automotive Test Suite Verifier. |
| GOATS = 13 # Android Go Test Suite. |
| STS = 14 # Security Test Suite. |
| CTS_INSTANT = 15 # Instant App mode of CTS. |
| BTS_V2 = 17 # Athena Report (BTS v2). |
| ENTS = 18 # Exposure Notification Test Suite. |
| CTS_ON_GSI = 19 # Compatibility Test Suite on GSI (go/android-gsi). |
| CATBOX = 20 # CATBox Test Suite. |
| APTS = 21 # Android Performance Test Suite. |
| DTS = 22 # Driveable Test Suite. |
| GTS_VERIFIER = 23 # GMS Core Manual Test Suite. |
| WTS_CHECKLIST = 25 # Wear Test Suite Checklist. |
| GCATBOX = 26 # GAS CATBox Test Suite. |
| WPTS = 27 # Wear Performance Test Suite. |
| |
| |
| class ApfeReport(ndb.Model): |
| """An APFE report. |
| |
| Attributes: |
| name: the unique resource name. |
| type: report type, for example, CTS. |
| company_id: the unique company identifier. |
| company_name: company name. |
| device_name: the name of the device which this build belongs to. |
| product_name: the name of the product which this build belongs to. |
| model_name: model name, for example, Nexus 6. |
| build_fingerprint: build fingerprint. |
| """ |
| |
| name = ndb.StringProperty() |
| type = ndb.EnumProperty(ReportType) |
| company_id = ndb.IntegerProperty() |
| company_name = ndb.StringProperty() |
| device_name = ndb.StringProperty() |
| product_name = ndb.StringProperty() |
| model_name = ndb.StringProperty() |
| build_fingerprint = ndb.StringProperty() |
| |
| |
| class NodeConfig(ndb.Model): |
| """A MTT node configuration. |
| |
| Attributes: |
| env_vars: default environment vars. |
| test_resource_default_download_urls: default download URLs for test |
| resources. |
| """ |
| env_vars = ndb.LocalStructuredProperty(NameValuePair, repeated=True) |
| test_resource_default_download_urls = ndb.LocalStructuredProperty( |
| NameValuePair, repeated=True) |
| |
| |
| def GetNodeConfig(): |
| """Returns a node config object. |
| |
| Returns: |
| a NodeConfig object. |
| """ |
| obj = ndb.Key(NodeConfig, NODE_CONFIG_ID).get() |
| if not obj: |
| obj = NodeConfig(id=NODE_CONFIG_ID) |
| obj.put() |
| return obj |
| |
| |
| class PrivateNodeConfig(ndb.Model): |
| """Non-shareable node configs. |
| |
| Attributes: |
| ndb_version: Latest version the database has been updated to |
| default_credentials: default service account credentials |
| metrics_enabled: True to collect usage metrics. |
| gms_client_id: Optional user-provided label to identify their company |
| setup_wizard_completed: If false, trigger the setup wizard on startup |
| server_uuid: node's unique identifier. |
| """ |
| ndb_version = ndb.IntegerProperty() |
| default_credentials = oauth2_util.CredentialsProperty() |
| metrics_enabled = ndb.BooleanProperty() |
| gms_client_id = ndb.StringProperty() |
| setup_wizard_completed = ndb.BooleanProperty() |
| server_uuid = ndb.StringProperty(required=True, default=str(uuid.uuid4())) |
| |
| |
| def GetPrivateNodeConfig(): |
| """Returns a private node config object. |
| |
| Returns: |
| a PrivateNodeConfig object. |
| """ |
| obj = PrivateNodeConfig.query().get() |
| if obj is None: |
| obj = PrivateNodeConfig() |
| obj.put() |
| return obj |
| |
| |
| class TestResourceTracker(ndb.Model): |
| """Test resource download tracker. |
| |
| Serves as a lock to determine the thread that should perform the download, and |
| holds the download metadata. |
| |
| Attributes |
| update_time: Latest update timestamp. |
| download_progress: Download progress (between 0 and 1). |
| completed: True if download is finished. |
| error: Optional error encountered during download. |
| """ |
| _use_cache = False |
| _use_memcache = False |
| update_time = ndb.DateTimeProperty(auto_now=True) |
| download_progress = ndb.FloatProperty(required=True, default=0.0) |
| completed = ndb.BooleanProperty() |
| error = ndb.StringProperty() |
| |
| |
| def IsLocalId(model_id): |
| """Returns true if the given id is a local id, false otherwise.""" |
| try: |
| int(model_id) |
| except ValueError: |
| return False |
| return True |
| |
| |
| class FileCleanerOperationType(messages.Enum): |
| ARCHIVE = 0 |
| DELETE = 1 |
| |
| |
| class FileCleanerCriterionType(messages.Enum): |
| LAST_ACCESS_TIME = 0 |
| LAST_MODIFIED_TIME = 1 |
| NAME_MATCH = 2 |
| SYSTEM_AVAILABLE_SPACE = 3 |
| |
| |
| class FileCleanerTargetType(messages.Enum): |
| FILE = 0 |
| DIRECTORY = 1 |
| |
| |
| class FileCleanerOperation(ndb.Model): |
| """File cleaner operation. |
| |
| Attributes: |
| type: operation type. |
| params: operation parameters. |
| """ |
| type = ndb.EnumProperty(FileCleanerOperationType, required=True) |
| params = ndb.LocalStructuredProperty(NameValuePair, repeated=True) |
| |
| |
| class FileCleanerCriterion(ndb.Model): |
| """File cleaner criterion. |
| |
| Attributes: |
| type: criterion type. |
| params: criterion parameters. |
| """ |
| type = ndb.EnumProperty(FileCleanerCriterionType, required=True) |
| params = ndb.LocalStructuredProperty(NameValuePair, repeated=True) |
| |
| |
| class FileCleanerPolicy(ndb.Model): |
| """File cleaner policy, which combines one operation and several criteria. |
| |
| Attributes: |
| name: policy name, should be unique. |
| target: policy target. |
| operation: the operation to apply to targets. |
| criteria: a list of criteria to select targets. |
| """ |
| name = ndb.StringProperty(required=True) |
| target = ndb.EnumProperty(FileCleanerTargetType) |
| operation = ndb.LocalStructuredProperty(FileCleanerOperation, required=True) |
| criteria = ndb.LocalStructuredProperty(FileCleanerCriterion, repeated=True) |
| |
| |
| class FileCleanerConfig(ndb.Model): |
| """File cleaner config. |
| |
| Attributes: |
| name: config name. |
| description: describes the config. |
| directories: directories to apply the policies to. |
| policy_names: name of policies to be used. |
| """ |
| name = ndb.StringProperty(required=True) |
| description = ndb.StringProperty() |
| directories = ndb.StringProperty(repeated=True) |
| policy_names = ndb.StringProperty(repeated=True) |
| |
| |
| class FileCleanerSettings(ndb.Model): |
| """File cleaner settings, with combines policies and configs. |
| |
| Attributes: |
| policies: file cleaner policies. |
| configs: file cleaner configs. |
| """ |
| policies = ndb.LocalStructuredProperty(FileCleanerPolicy, repeated=True) |
| configs = ndb.LocalStructuredProperty(FileCleanerConfig, repeated=True) |
| |
| |
| DEFAULT_FILE_CLEANER_SETTINGS = FileCleanerSettings( |
| policies=[ |
| FileCleanerPolicy( |
| name='Archive directories not modified', |
| target=FileCleanerTargetType.DIRECTORY, |
| operation=FileCleanerOperation( |
| type=FileCleanerOperationType.ARCHIVE), |
| criteria=[ |
| FileCleanerCriterion( |
| type=FileCleanerCriterionType.LAST_MODIFIED_TIME, |
| params=[NameValuePair(name='ttl', value='7 days')]) |
| ]), |
| FileCleanerPolicy( |
| name='Delete directories not modified and accessed', |
| target=FileCleanerTargetType.DIRECTORY, |
| operation=FileCleanerOperation( |
| type=FileCleanerOperationType.DELETE), |
| criteria=[ |
| FileCleanerCriterion( |
| type=FileCleanerCriterionType.LAST_MODIFIED_TIME, |
| params=[NameValuePair(name='ttl', value='7 days')]), |
| FileCleanerCriterion( |
| type=FileCleanerCriterionType.LAST_ACCESS_TIME, |
| params=[NameValuePair(name='ttl', value='7 days')]) |
| ]) |
| ], |
| configs=[ |
| FileCleanerConfig( |
| name='Clean up test results', |
| description='Clean up test results', |
| directories=[ |
| '{}/{}/test_runs'.format(env.STORAGE_PATH, env.GCS_BUCKET_NAME) |
| ], |
| policy_names=['Archive directories not modified']), |
| FileCleanerConfig( |
| name='Clean up test work files', |
| description='Clean up test work files', |
| directories=['{}/tmp'.format(env.STORAGE_PATH)], |
| policy_names=['Delete directories not modified and accessed']) |
| ]) |
| |
| |
| def GetFileCleanerSettings(): |
| """Returns a file cleaner settings object. |
| |
| Returns: |
| a FileCleanerSettings object. |
| """ |
| obj = ndb.Key(FileCleanerSettings, FILE_CLEANER_SETTINGS_ID).get() |
| return obj or DEFAULT_FILE_CLEANER_SETTINGS |
| |
| |
| class ApfeBuild(ndb.Model): |
| """An APFE build. |
| |
| Attributes: |
| name: the unique resource name. |
| """ |
| |
| name = ndb.StringProperty() |
| |
| |
| class XtsRequirementsDetectionStatus(messages.Enum): |
| NOT_STARTED = 0 # Xts requirements detection is not yet started. |
| SIGNALS_COLLECTING = 1 # A task is collecting build integrity signals. |
| ANALYSIS_RUNNING = 2 # A build analysis on APA side is running. |
| COMPLETED = 3 # Xts requirements detection is completed. |
| CANCELED = 4 # Xts requirements detection is canceled. |
| ERROR = 5 # Xts requirements detection is aborted with an error. |
| |
| |
| FINAL_XTS_REQUIREMENT_DETECTION_STATES = { |
| XtsRequirementsDetectionStatus.COMPLETED, |
| XtsRequirementsDetectionStatus.CANCELED, |
| XtsRequirementsDetectionStatus.ERROR, |
| } |
| |
| |
| class Build(ndb.Model): |
| """A build. |
| |
| Attributes: |
| name: a build name |
| fingerprint: fingerprint of a build. |
| file_url: file URL of a build. |
| size: size of a build. |
| labels: list of strings users can use to categorize builds. |
| create_time: time a build is created. |
| update_time: time a build is last updated. |
| detection_status: status of xts requirements detection for a build. |
| detection_start_time: time an xts requirements detection started. |
| detection_error_reason: error reason, only used for ERROR detection status. |
| detection_test_run_key: test run key of xts requirements detection for a |
| build. |
| """ |
| name = ndb.StringProperty(required=True) |
| fingerprint = ndb.StringProperty(required=True) |
| file_url = ndb.StringProperty(required=True) |
| size = ndb.IntegerProperty() |
| labels = ndb.StringProperty(repeated=True) |
| create_time = ndb.DateTimeProperty(auto_now_add=True) |
| update_time = ndb.DateTimeProperty(auto_now=True) |
| detection_status = ndb.EnumProperty( |
| XtsRequirementsDetectionStatus, |
| default=XtsRequirementsDetectionStatus.NOT_STARTED, |
| ) |
| detection_start_time = ndb.DateTimeProperty() |
| detection_error_reason = ndb.TextProperty() |
| detection_test_run_key = ndb.KeyProperty(TestRun) |
| |
| @classmethod |
| def _post_delete_hook(cls, key, future): |
| keys_to_delete = RequiredReport.query( |
| RequiredReport.build_key == key |
| ).fetch(keys_only=True) |
| ndb.delete_multi(keys_to_delete) |
| ndb.delete_multi(ndb.Query(ancestor=key).iter(keys_only=True)) |
| |
| def IsFinalDetectionStatus(self): |
| """Returns whether an xts requirements detection is in a final state. |
| |
| Returns: |
| True if an xts requirements detection is in a final state. Otherwise |
| false. |
| """ |
| return self.detection_status in FINAL_XTS_REQUIREMENT_DETECTION_STATES |
| |
| |
| class RequiredReport(ndb.Model): |
| """A required report for a build. |
| |
| Attributes: |
| build_key: the build key. |
| type: the type of a report. |
| test_plans: the qualified test plans of a report. |
| available: whether the report is available on APFE. |
| test_run_key: the test run key a required report is associated with. |
| """ |
| |
| build_key = ndb.KeyProperty(Build, required=True) |
| type = ndb.EnumProperty(ReportType, required=True) |
| test_plans = ndb.StringProperty(repeated=True) |
| available = ndb.BooleanProperty() |
| test_run_key = ndb.KeyProperty(TestRun) |
| |
| |
| class RequestInfo(ndb.Model): |
| request_json_str = ndb.TextProperty(required=True) |
| |
| @classmethod |
| def get_by_id(cls, id_, **kwargs): |
| return RequestInfo._get_by_id(id_, **kwargs) |