|
8 | 8 | from ydb.tests.olap.lib.ydb_cluster import YdbCluster
|
9 | 9 | from abc import abstractmethod, ABC
|
10 | 10 | from typing import Set, List, Dict, Any, Callable
|
| 11 | +from time import sleep |
11 | 12 |
|
12 | 13 |
|
13 | 14 | class TestContext:
|
@@ -59,6 +60,13 @@ class ScenarioTestHelper:
|
59 | 60 | sth.execute_scheme_query(DropTable(table_name))
|
60 | 61 | """
|
61 | 62 |
|
| 63 | + DEFAULT_RETRIABLE_ERRORS = { |
| 64 | + ydb.StatusCode.OVERLOADED, |
| 65 | + ydb.StatusCode.BAD_SESSION, |
| 66 | + ydb.StatusCode.CONNECTION_LOST, |
| 67 | + ydb.StatusCode.UNAVAILABLE, |
| 68 | + } |
| 69 | + |
62 | 70 | class Column:
|
63 | 71 | """A class that describes a table column."""
|
64 | 72 |
|
@@ -247,21 +255,37 @@ def _add_not_empty(p: str, dir: str):
|
247 | 255 | return result
|
248 | 256 |
|
249 | 257 | @staticmethod
|
250 |
| - def _run_with_expected_status(operation: callable, expected_status: ydb.StatusCode | Set[ydb.StatusCode]): |
| 258 | + def _run_with_expected_status( |
| 259 | + operation: callable, |
| 260 | + expected_status: ydb.StatusCode | Set[ydb.StatusCode], |
| 261 | + retriable_status: ydb.StatusCode | Set[ydb.StatusCode] = {}, |
| 262 | + n_retries=0, |
| 263 | + ): |
251 | 264 | if isinstance(expected_status, ydb.StatusCode):
|
252 | 265 | expected_status = {expected_status}
|
253 |
| - try: |
254 |
| - result = operation() |
255 |
| - if ydb.StatusCode.SUCCESS not in expected_status: |
256 |
| - pytest.fail( |
257 |
| - f'Unexpected status: must be in {repr(expected_status)}, but get {repr(ydb.StatusCode.SUCCESS)}' |
258 |
| - ) |
259 |
| - return result |
260 |
| - except ydb.issues.Error as e: |
261 |
| - allure.attach(f'{repr(e.status)}: {e}', 'request status', allure.attachment_type.TEXT) |
262 |
| - if e.status not in expected_status: |
263 |
| - pytest.fail(f'Unexpected status: must be in {repr(expected_status)}, but get {repr(e)}') |
264 |
| - return None |
| 266 | + if isinstance(retriable_status, ydb.StatusCode): |
| 267 | + retriable_status = {retriable_status} |
| 268 | + |
| 269 | + result = None |
| 270 | + error = None |
| 271 | + status = None |
| 272 | + for _ in range(n_retries + 1): |
| 273 | + try: |
| 274 | + result = operation() |
| 275 | + error = None |
| 276 | + status = ydb.StatusCode.SUCCESS |
| 277 | + except ydb.issues.Error as e: |
| 278 | + result = None |
| 279 | + error = e |
| 280 | + status = error.status |
| 281 | + allure.attach(f'{repr(status)}: {error}', 'request status', allure.attachment_type.TEXT) |
| 282 | + |
| 283 | + if status in expected_status: |
| 284 | + return result |
| 285 | + if status not in retriable_status: |
| 286 | + pytest.fail(f'Unexpected status: must be in {repr(expected_status)}, but get {repr(error or status)}') |
| 287 | + sleep(3) |
| 288 | + pytest.fail(f'Retries exceeded with unexpected status: must be in {repr(expected_status)}, but get {repr(error or status)}') |
265 | 289 |
|
266 | 290 | def _bulk_upsert_impl(
|
267 | 291 | self, tablename: str, data_generator: ScenarioTestHelper.IDataGenerator, expected_status: ydb.StatusCode | Set[ydb.StatusCode]
|
@@ -302,6 +326,8 @@ def execute_scheme_query(
|
302 | 326 | self,
|
303 | 327 | yqlble: ScenarioTestHelper.IYqlble,
|
304 | 328 | expected_status: ydb.StatusCode | Set[ydb.StatusCode] = ydb.StatusCode.SUCCESS,
|
| 329 | + retries=0, |
| 330 | + retriable_status: ydb.StatusCode | Set[ydb.StatusCode] = DEFAULT_RETRIABLE_ERRORS, |
305 | 331 | comment: str = '',
|
306 | 332 | ) -> None:
|
307 | 333 | """Run a schema query on the database under test.
|
@@ -330,7 +356,7 @@ def execute_scheme_query(
|
330 | 356 | yql = yqlble.to_yql(self.test_context)
|
331 | 357 | allure.attach(yql, 'request', allure.attachment_type.TEXT)
|
332 | 358 | self._run_with_expected_status(
|
333 |
| - lambda: YdbCluster.get_ydb_driver().table_client.session().create().execute_scheme(yql), expected_status |
| 359 | + lambda: YdbCluster.get_ydb_driver().table_client.session().create().execute_scheme(yql), expected_status, retriable_status, retries |
334 | 360 | )
|
335 | 361 |
|
336 | 362 | @classmethod
|
@@ -524,6 +550,22 @@ def get_table_rows_count(self, tablename: str, comment: str = '') -> int:
|
524 | 550 | result_set = self.execute_scan_query(f'SELECT count(*) FROM `{self.get_full_path(tablename)}`')
|
525 | 551 | return result_set.result_set.rows[0][0]
|
526 | 552 |
|
| 553 | + @allure.step('Describe table {path}') |
| 554 | + def describe_table(self, path: str, settings: ydb.DescribeTableSettings = None) -> ydb.TableSchemeEntry: |
| 555 | + """Get table description. |
| 556 | +
|
| 557 | + Args: |
| 558 | + path: Relative path to a table. |
| 559 | + settings: DescribeTableSettings. |
| 560 | +
|
| 561 | + Returns: |
| 562 | + TableSchemeEntry object. |
| 563 | + """ |
| 564 | + |
| 565 | + return self._run_with_expected_status( |
| 566 | + lambda: YdbCluster.get_ydb_driver().table_client.session().create().describe_table(self.get_full_path(path), settings), ydb.StatusCode.SUCCESS |
| 567 | + ) |
| 568 | + |
527 | 569 | @allure.step('List path {path}')
|
528 | 570 | def list_path(self, path: str) -> List[ydb.SchemeEntry]:
|
529 | 571 | """Recursively describe the path in the database under test.
|
|
0 commit comments