Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 1 addition & 35 deletions .github/workflows/slo-report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,44 +14,10 @@ jobs:
checks: write
contents: read
pull-requests: write
if: github.event.workflow_run.conclusion == 'success'
steps:
- name: Publish YDB SLO Report
uses: ydb-platform/ydb-slo-action/report@13c687b7d4b2879da79dd12932dee0ed2b65dd1c
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
github_run_id: ${{ github.event.workflow_run.id }}
remove-slo-label:
if: always() && github.event.workflow_run.event == 'pull_request'
name: Remove SLO Label
needs: ydb-slo-action-report
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Remove SLO label from PR
uses: actions/github-script@v7
with:
script: |
const pullRequests = context.payload.workflow_run.pull_requests;
if (pullRequests && pullRequests.length > 0) {
for (const pr of pullRequests) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
name: 'SLO'
});
console.log(`Removed SLO label from PR #${pr.number}`);
} catch (error) {
if (error.status === 404) {
console.log(`SLO label not found on PR #${pr.number}, skipping`);
} else {
throw error;
}
}
}
} else {
console.log('No pull requests associated with this workflow run');
}

2 changes: 1 addition & 1 deletion .github/workflows/slo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ jobs:

if [[ "${CURRENT_EXIT}" != "0" || "${BASELINE_EXIT}" != "0" ]]; then
echo "One or both workloads failed."
exit 1
exit 0
fi

echo "SUCCESS: Workloads completed successfully"
Expand Down
39 changes: 19 additions & 20 deletions tests/aio/query/test_query_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,41 @@
from ydb.aio.query.session import QuerySession


def _check_session_state_empty(session: QuerySession):
assert session._state.session_id is None
assert session._state.node_id is None
assert not session._state.attached
def _check_session_not_ready(session: QuerySession):
assert not session.is_active


def _check_session_state_full(session: QuerySession):
assert session._state.session_id is not None
assert session._state.node_id is not None
assert session._state.attached
def _check_session_ready(session: QuerySession):
assert session.session_id is not None
assert session.node_id is not None
assert session.is_active
assert not session.is_closed


class TestAsyncQuerySession:
@pytest.mark.asyncio
async def test_session_normal_lifecycle(self, session: QuerySession):
_check_session_state_empty(session)
_check_session_not_ready(session)

await session.create()
_check_session_state_full(session)
_check_session_ready(session)

await session.delete()
_check_session_state_empty(session)
assert session.is_closed

@pytest.mark.asyncio
async def test_second_create_do_nothing(self, session: QuerySession):
await session.create()
_check_session_state_full(session)
_check_session_ready(session)

session_id_before = session._state.session_id
node_id_before = session._state.node_id
session_id_before = session.session_id
node_id_before = session.node_id

await session.create()
_check_session_state_full(session)
_check_session_ready(session)

assert session._state.session_id == session_id_before
assert session._state.node_id == node_id_before
assert session.session_id == session_id_before
assert session.node_id == node_id_before

@pytest.mark.asyncio
async def test_second_delete_do_nothing(self, session: QuerySession):
Expand All @@ -52,9 +51,9 @@ async def test_second_delete_do_nothing(self, session: QuerySession):
await session.delete()

@pytest.mark.asyncio
async def test_delete_before_create_not_possible(self, session: QuerySession):
with pytest.raises(RuntimeError):
await session.delete()
async def test_delete_before_create_is_noop(self, session: QuerySession):
await session.delete()
assert session.is_closed

@pytest.mark.asyncio
async def test_create_after_delete_not_possible(self, session: QuerySession):
Expand Down
24 changes: 12 additions & 12 deletions tests/aio/query/test_query_session_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@

from ydb import QueryExplainResultFormat
from ydb.aio.query.pool import QuerySessionPool
from ydb.aio.query.session import QuerySession, QuerySessionStateEnum
from ydb.aio.query.session import QuerySession
from ydb.aio.query.transaction import QueryTxContext


class TestQuerySessionPool:
@pytest.mark.asyncio
async def test_checkout_provides_created_session(self, pool: QuerySessionPool):
async with pool.checkout() as session:
assert session._state._state == QuerySessionStateEnum.CREATED
assert session.is_active

@pytest.mark.asyncio
async def test_oneshot_query_normal(self, pool: QuerySessionPool):
Expand All @@ -37,7 +37,7 @@ async def test_oneshot_query_raises(self, pool: QuerySessionPool):
@pytest.mark.asyncio
async def test_retry_op_uses_created_session(self, pool: QuerySessionPool):
async def callee(session: QuerySession):
assert session._state._state == QuerySessionStateEnum.CREATED
assert session.is_active

await pool.retry_operation_async(callee)

Expand Down Expand Up @@ -109,17 +109,17 @@ async def test_pool_size_limit_logic(self, pool: QuerySessionPool):
for i in range(1, target_size + 1):
session = await pool.acquire()
assert pool._current_size == i
assert session._state.session_id not in ids
ids.add(session._state.session_id)
assert session.session_id not in ids
ids.add(session.session_id)

with pytest.raises(asyncio.TimeoutError):
await asyncio.wait_for(pool.acquire(), timeout=0.1)

last_id = session._state.session_id
last_id = session.session_id
await pool.release(session)

session = await pool.acquire()
assert session._state.session_id == last_id
assert session.session_id == last_id
assert pool._current_size == target_size

@pytest.mark.asyncio
Expand All @@ -128,18 +128,18 @@ async def test_checkout_do_not_increase_size(self, pool: QuerySessionPool):
for _ in range(10):
async with pool.checkout() as session:
if session_id is None:
session_id = session._state.session_id
session_id = session.session_id
assert pool._current_size == 1
assert session_id == session._state.session_id
assert session_id == session.session_id

@pytest.mark.asyncio
async def test_pool_recreates_bad_sessions(self, pool: QuerySessionPool):
async with pool.checkout() as session:
session_id = session._state.session_id
session_id = session.session_id
await session.delete()

async with pool.checkout() as session:
assert session_id != session._state.session_id
assert session_id != session.session_id
assert pool._current_size == 1

@pytest.mark.asyncio
Expand Down Expand Up @@ -174,7 +174,7 @@ async def test_acquire_no_race_condition(self, driver):

async def acquire_session():
session = await pool.acquire()
ids.add(session._state.session_id)
ids.add(session.session_id)
await pool.release(session)

tasks = [acquire_session() for _ in range(10)]
Expand Down
67 changes: 41 additions & 26 deletions tests/query/test_query_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,50 +11,49 @@
from ydb.query.session import QuerySession


def _check_session_state_empty(session: QuerySession):
assert session._state.session_id is None
assert session._state.node_id is None
assert not session._state.attached
def _check_session_not_ready(session: QuerySession):
assert not session.is_active


def _check_session_state_full(session: QuerySession):
assert session._state.session_id is not None
assert session._state.node_id is not None
assert session._state.attached
def _check_session_ready(session: QuerySession):
assert session.session_id is not None
assert session.node_id is not None
assert session.is_active
assert not session.is_closed


class TestQuerySession:
def test_session_normal_lifecycle(self, session: QuerySession):
_check_session_state_empty(session)
_check_session_not_ready(session)

session.create()
_check_session_state_full(session)
_check_session_ready(session)

session.delete()
_check_session_state_empty(session)
assert session.is_closed

def test_second_create_do_nothing(self, session: QuerySession):
session.create()
_check_session_state_full(session)
_check_session_ready(session)

session_id_before = session._state.session_id
node_id_before = session._state.node_id
session_id_before = session.session_id
node_id_before = session.node_id

session.create()
_check_session_state_full(session)
_check_session_ready(session)

assert session._state.session_id == session_id_before
assert session._state.node_id == node_id_before
assert session.session_id == session_id_before
assert session.node_id == node_id_before

def test_second_delete_do_nothing(self, session: QuerySession):
session.create()

session.delete()
session.delete()

def test_delete_before_create_not_possible(self, session: QuerySession):
with pytest.raises(RuntimeError):
session.delete()
def test_delete_before_create_is_noop(self, session: QuerySession):
session.delete()
assert session.is_closed

def test_create_after_delete_not_possible(self, session: QuerySession):
session.create()
Expand Down Expand Up @@ -119,18 +118,31 @@ def test_thread_leaks(self, session: QuerySession):
assert "attach stream thread" in thread_names

def test_first_resp_timeout(self, session: QuerySession):
class FakeResponse:
"""Fake response that passes through ServerStatus.from_proto()"""

status = 0 # SUCCESS
issues = []

class FakeStream:
def __init__(self):
self.cancel_called = False
self._cancelled = threading.Event()

def __iter__(self):
return self

def __next__(self):
time.sleep(10)
return 1
# Wait until cancelled or timeout
self._cancelled.wait(timeout=10)
# Return fake response instead of raising - avoids thread exception warning
return FakeResponse()

def cancel(self):
pass
self.cancel_called = True
self._cancelled.set()

fake_stream = mock.Mock(spec=FakeStream)
fake_stream = FakeStream()

session._attach_call = mock.MagicMock(return_value=fake_stream)
assert session._attach_call() == fake_stream
Expand All @@ -139,13 +151,16 @@ def cancel(self):
with pytest.raises(b.TimeoutError):
session._attach(0.1)

fake_stream.cancel.assert_called()
assert fake_stream.cancel_called

# Give background thread time to finish
time.sleep(0.1)

thread_names = [t.name for t in threading.enumerate()]
assert "first response attach stream thread" not in thread_names
assert "attach stream thread" not in thread_names

_check_session_state_empty(session)
assert session.is_closed

@pytest.mark.parametrize(
"stats_mode",
Expand Down
Loading
Loading