(hot fix) Resolve Boring App (#14684)
* resolve_boring_app * update * update * update * update * update * update * update * update * update * update * update * update * update
This commit is contained in:
parent
16b4644e06
commit
96cc288f53
|
@ -25,6 +25,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
- Resolved a bug where the state change detection using DeepDiff won't worked with Path, Drive objects ([#14465](https://github.com/Lightning-AI/lightning/pull/14465))
|
||||
|
||||
- Resolved a bug where the wrong client was passed to collect cloud logs ([#14684](https://github.com/Lightning-AI/lightning/pull/14684))
|
||||
|
||||
### Removed
|
||||
|
||||
-
|
||||
|
|
|
@ -32,6 +32,7 @@ from lightning_app.utilities.cli_helpers import _arrow_time_callback, _format_in
|
|||
from lightning_app.utilities.cloud import _get_project
|
||||
from lightning_app.utilities.cluster_logs import _cluster_logs_reader
|
||||
from lightning_app.utilities.login import Auth
|
||||
from lightning_app.utilities.logs_socket_api import _LightningLogsSocketAPI
|
||||
from lightning_app.utilities.network import LightningClient
|
||||
|
||||
logger = Logger(__name__)
|
||||
|
@ -151,7 +152,7 @@ def logs(app_name: str, components: List[str], follow: bool) -> None:
|
|||
raise click.ClickException(f"Component '{component}' does not exist in app {app_name}.")
|
||||
|
||||
log_reader = _app_logs_reader(
|
||||
client=client,
|
||||
logs_api_client=_LightningLogsSocketAPI(client.api_client),
|
||||
project_id=project.project_id,
|
||||
app_id=apps[app_name].id,
|
||||
component_names=components,
|
||||
|
|
|
@ -6,7 +6,6 @@ import subprocess
|
|||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import traceback
|
||||
from contextlib import contextmanager
|
||||
from multiprocessing import Process
|
||||
from subprocess import Popen
|
||||
|
@ -38,7 +37,6 @@ if _is_playwright_available():
|
|||
|
||||
|
||||
def _on_error_callback(ws_app, *_):
|
||||
print(traceback.format_exc())
|
||||
ws_app.close()
|
||||
|
||||
|
||||
|
@ -210,7 +208,9 @@ def run_cli(args) -> Generator:
|
|||
|
||||
@requires("playwright")
|
||||
@contextmanager
|
||||
def run_app_in_cloud(app_folder: str, app_name: str = "app.py", extra_args: [str] = []) -> Generator:
|
||||
def run_app_in_cloud(
|
||||
app_folder: str, app_name: str = "app.py", extra_args: List[str] = [], debug: bool = True
|
||||
) -> Generator:
|
||||
"""This utility is used to automate testing e2e application with lightning_app.ai."""
|
||||
# 1. Validate the provide app_folder is correct.
|
||||
if not os.path.exists(os.path.join(app_folder, "app.py")):
|
||||
|
@ -239,7 +239,8 @@ def run_app_in_cloud(app_folder: str, app_name: str = "app.py", extra_args: [str
|
|||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
env_copy = os.environ.copy()
|
||||
env_copy["PACKAGE_LIGHTNING"] = "1"
|
||||
env_copy["LIGHTNING_DEBUG"] = "1"
|
||||
if debug:
|
||||
env_copy["LIGHTNING_DEBUG"] = "1"
|
||||
shutil.copytree(app_folder, tmpdir, dirs_exist_ok=True)
|
||||
# TODO - add -no-cache to the command line.
|
||||
process = Popen(
|
||||
|
@ -308,7 +309,7 @@ def run_app_in_cloud(app_folder: str, app_name: str = "app.py", extra_args: [str
|
|||
""",
|
||||
[LIGHTNING_CLOUD_PROJECT_ID],
|
||||
)
|
||||
admin_page.goto(f"{Config.url}/{Config.username}/apps")
|
||||
admin_page.goto(f"{Config.url}/{Config.username}/apps", timeout=60 * 1000)
|
||||
|
||||
# Closing the Create Project dialog.
|
||||
try:
|
||||
|
@ -324,15 +325,6 @@ def run_app_in_cloud(app_folder: str, app_name: str = "app.py", extra_args: [str
|
|||
print("'Create Project' dialog not visible, skipping.")
|
||||
|
||||
admin_page.locator(f"text={name}").click()
|
||||
admin_page.evaluate(
|
||||
"""data => {
|
||||
window.localStorage.setItem('gridUserId', data[0]);
|
||||
window.localStorage.setItem('gridUserKey', data[1]);
|
||||
window.localStorage.setItem('gridUserToken', data[2]);
|
||||
}
|
||||
""",
|
||||
[Config.id, Config.key, token],
|
||||
)
|
||||
sleep(5)
|
||||
# Scroll to the bottom of the page. Used to capture all logs.
|
||||
admin_page.evaluate(
|
||||
|
@ -361,8 +353,9 @@ def run_app_in_cloud(app_folder: str, app_name: str = "app.py", extra_args: [str
|
|||
assert len(lightning_apps) == 1
|
||||
app_id = lightning_apps[0].id
|
||||
|
||||
process = Process(target=print_logs, kwargs={"app_id": app_id})
|
||||
process.start()
|
||||
if debug:
|
||||
process = Process(target=print_logs, kwargs={"app_id": app_id})
|
||||
process.start()
|
||||
|
||||
while True:
|
||||
try:
|
||||
|
@ -403,40 +396,11 @@ def run_app_in_cloud(app_folder: str, app_name: str = "app.py", extra_args: [str
|
|||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
has_finished = False
|
||||
while not has_finished:
|
||||
try:
|
||||
button = admin_page.locator('[data-cy="stop"]')
|
||||
try:
|
||||
button.wait_for(timeout=3 * 1000)
|
||||
button.click()
|
||||
except (playwright._impl._api_types.Error, playwright._impl._api_types.TimeoutError):
|
||||
pass
|
||||
context.close()
|
||||
browser.close()
|
||||
if debug:
|
||||
process.kill()
|
||||
|
||||
list_lightningapps = client.lightningapp_instance_service_list_lightningapp_instances(
|
||||
project.project_id
|
||||
)
|
||||
|
||||
for lightningapp in list_lightningapps.lightningapps:
|
||||
if lightningapp.name != name:
|
||||
continue
|
||||
try:
|
||||
res = client.lightningapp_instance_service_delete_lightningapp_instance(
|
||||
project_id=project.project_id,
|
||||
id=lightningapp.id,
|
||||
)
|
||||
assert res == {}
|
||||
except ApiException as e:
|
||||
print(f"Failed to delete {lightningapp.name}. Exception {e}")
|
||||
|
||||
process.kill()
|
||||
has_finished = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
Popen("lightning disconnect", shell=True).wait()
|
||||
context.close()
|
||||
browser.close()
|
||||
|
||||
|
||||
def wait_for(page, callback: Callable, *args, **kwargs) -> Any:
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import json
|
||||
import queue
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
from threading import Thread
|
||||
from typing import Callable, Iterator, List, Optional
|
||||
|
||||
|
@ -87,18 +86,22 @@ def _app_logs_reader(
|
|||
th.start()
|
||||
|
||||
# Print logs from queue when log event is available
|
||||
user_log_start = "<<< BEGIN USER_RUN_FLOW SECTION >>>"
|
||||
start_timestamp = None
|
||||
flow = "Your app has started. View it in your browser"
|
||||
work = "USER_RUN_WORK"
|
||||
start_timestamps = {}
|
||||
|
||||
# Print logs from queue when log event is available
|
||||
try:
|
||||
while True:
|
||||
log_event = read_queue.get(timeout=None if follow else 1.0)
|
||||
if user_log_start in log_event.message:
|
||||
start_timestamp = log_event.timestamp + timedelta(seconds=0.5)
|
||||
log_event: _LogEvent = read_queue.get(timeout=None if follow else 1.0)
|
||||
token = flow if log_event.component_name == "flow" else work
|
||||
if token in log_event.message:
|
||||
start_timestamps[log_event.component_name] = log_event.timestamp
|
||||
|
||||
if start_timestamp and log_event.timestamp > start_timestamp:
|
||||
yield log_event
|
||||
timestamp = start_timestamps.get(log_event.component_name, None)
|
||||
if timestamp and log_event.timestamp >= timestamp:
|
||||
if "launcher" not in log_event.message:
|
||||
yield log_event
|
||||
|
||||
except queue.Empty:
|
||||
# Empty is raised by queue.get if timeout is reached. Follow = False case.
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
import os
|
||||
|
||||
from lightning_cloud.openapi.rest import ApiException
|
||||
|
||||
from lightning_app.utilities.cloud import _get_project
|
||||
from lightning_app.utilities.network import LightningClient
|
||||
|
||||
|
||||
def pytest_timeout_cancel_timer(item):
|
||||
"""This hook deletes the Lightning App when timeout triggers."""
|
||||
|
||||
if item.name.endswith("_example_cloud"):
|
||||
name = os.getenv("LIGHTNING_APP_NAME")
|
||||
print(f"Timeout was triggered. Deleting the App {name}.")
|
||||
|
||||
client = LightningClient()
|
||||
project = _get_project(client)
|
||||
|
||||
lightning_apps = [
|
||||
app
|
||||
for app in client.lightningapp_instance_service_list_lightningapp_instances(
|
||||
project.project_id
|
||||
).lightningapps
|
||||
if app.name == name
|
||||
]
|
||||
|
||||
if not lightning_apps:
|
||||
return True
|
||||
|
||||
assert len(lightning_apps) == 1
|
||||
|
||||
lightning_app = lightning_apps[0]
|
||||
|
||||
try:
|
||||
res = client.lightningapp_instance_service_delete_lightningapp_instance(
|
||||
project_id=project.project_id,
|
||||
id=lightning_app.id,
|
||||
)
|
||||
assert res == {}
|
||||
|
||||
except ApiException as e:
|
||||
print(f"Failed to delete {name}. Exception {e}")
|
||||
|
||||
return True
|
|
@ -10,7 +10,11 @@ from lightning_app.testing.testing import run_app_in_cloud, wait_for
|
|||
|
||||
@pytest.mark.cloud
|
||||
def test_boring_app_example_cloud() -> None:
|
||||
with run_app_in_cloud(os.path.join(_PROJECT_ROOT, "examples/app_boring/"), app_name="app_dynamic.py") as (
|
||||
with run_app_in_cloud(
|
||||
os.path.join(_PROJECT_ROOT, "examples/app_boring/"),
|
||||
app_name="app_dynamic.py",
|
||||
debug=False,
|
||||
) as (
|
||||
_,
|
||||
view_page,
|
||||
fetch_logs,
|
||||
|
@ -19,15 +23,11 @@ def test_boring_app_example_cloud() -> None:
|
|||
|
||||
def check_hello_there(*_, **__):
|
||||
locator = view_page.frame_locator("iframe").locator('ul:has-text("Hello there!")')
|
||||
locator.wait_for(timeout=3 * 1000)
|
||||
if len(locator.all_text_contents()):
|
||||
return True
|
||||
|
||||
wait_for(view_page, check_hello_there)
|
||||
|
||||
for _ in fetch_logs():
|
||||
pass
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(logs, [name])
|
||||
lines = result.output.splitlines()
|
||||
|
@ -35,3 +35,4 @@ def test_boring_app_example_cloud() -> None:
|
|||
assert result.exit_code == 0
|
||||
assert result.exception is None
|
||||
assert any("http://0.0.0.0:8080" in line for line in lines)
|
||||
print("Succeeded App!")
|
||||
|
|
Loading…
Reference in New Issue