(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:
thomas chaton 2022-09-13 16:50:36 +02:00 committed by GitHub
parent 16b4644e06
commit 96cc288f53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 34 additions and 107 deletions

View File

@ -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
-

View File

@ -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,

View File

@ -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:

View File

@ -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.

View File

@ -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

View File

@ -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!")