From ad7d3360cf032fa58e830b891b028f3a921d4220 Mon Sep 17 00:00:00 2001 From: Chuck McAndrew <6248903+dcmcand@users.noreply.github.com> Date: Thu, 1 Feb 2024 10:53:51 -0500 Subject: [PATCH 01/60] add initial user journey test --- conda-store-server/pyproject.toml | 9 ++ .../tests/user_journeys/helpers/helpers.py | 145 ++++++++++++++++++ .../test_data/simple_environment.yaml | 6 + .../tests/user_journeys/test_user_journeys.py | 42 +++++ 4 files changed, 202 insertions(+) create mode 100644 conda-store-server/tests/user_journeys/helpers/helpers.py create mode 100644 conda-store-server/tests/user_journeys/test_data/simple_environment.yaml create mode 100644 conda-store-server/tests/user_journeys/test_user_journeys.py diff --git a/conda-store-server/pyproject.toml b/conda-store-server/pyproject.toml index 38cca0ed7..bdce874b6 100644 --- a/conda-store-server/pyproject.toml +++ b/conda-store-server/pyproject.toml @@ -80,6 +80,8 @@ playwright-test = [ ] integration-test = ["pytest ../tests/test_api.py ../tests/test_metrics.py"] +user-journey-test = ["pytest -m user-journey"] + [tool.hatch.build.hooks.custom] [tool.hatch.build.targets.sdist.hooks.custom] @@ -101,3 +103,10 @@ ignore = [ [tool.check-wheel-contents] # ignore alembic migrations https://github.com/jwodder/check-wheel-contents?tab=readme-ov-file#w004--module-is-not-located-at-importable-path ignore = ["W004"] + +[tool.pytest.ini_options] +markers = [ + "playwright: mark a test as a playwright test", + "integration: mark a test as an integration test", + "user-journey: mark a test as a user journey test", +] \ No newline at end of file diff --git a/conda-store-server/tests/user_journeys/helpers/helpers.py b/conda-store-server/tests/user_journeys/helpers/helpers.py new file mode 100644 index 000000000..feb1b0e14 --- /dev/null +++ b/conda-store-server/tests/user_journeys/helpers/helpers.py @@ -0,0 +1,145 @@ +"""Helper functions for user journeys.""" +from datetime import datetime, timedelta, timezone +from enum import Enum + +import requests + +TIMEOUT = 10 + + +class APIBuildStatus(Enum): + """Enum for API build status.""" + COMPLETED = "COMPLETED" + FAILED = "FAILED" + QUEUED = "QUEUED" + BUILDING = "BUILDING" + + +class APIHelper: + """ + Helper class for making requests to the API. + These methods are used to build tests for user journeys + """ + + def __init__(self, + base_url: str, token: str = "", + username: str = "username", password: str = "password" + ) -> None: + self.base_url = base_url + self.token = token + if not token: + # Log in if no token is provided to set the token + self._login(username, password) + + def make_request( + self, endpoint: str, method: str = "GET", json_data: dict = None, + headers: dict = None, timeout: int = TIMEOUT) -> requests.Response: + """ Make a request to the API. """ + url = f"{self.base_url}/{endpoint}" + headers = headers or {} + headers["Authorization"] = f"Bearer {self.token}" + response = requests.request( + method, url, json=json_data, headers=headers, timeout=timeout) + response.raise_for_status() + return response + + def _login(self, username: str, password: str) -> None: + """ Log in to the API and set an access token.""" + json_data = {"username": username, "password": password} + response = requests.post( + f"{self.base_url}/login", json=json_data, timeout=TIMEOUT) + cookies = response.cookies.get_dict() + token_response = requests.post( + f"{self.base_url}/api/v1/token", cookies=cookies, timeout=TIMEOUT) + data = token_response.json() + self.token = data["data"]["token"] + + +def get_current_time() -> datetime: + """ Get the current time. """ + return datetime.now(timezone.utc) + + +def get_time_in_future(hours: int) -> datetime: + """ Get the time in the future.""" + current_time = get_current_time() + future_time = current_time + timedelta(hours=hours) + return future_time + + +def get_iso8601_time(hours: int) -> str: + """ Get the time in the future in ISO 8601 format. """ + future_time = get_time_in_future(hours) + iso_format = future_time.isoformat() + return iso_format + + +def create_namespace(api: APIHelper, namespace: str) -> requests.Response: + """ Create a namespace. """ + return api.make_request( + f"api/v1/namespace/{namespace}", method="POST") + + +def create_token( + api: APIHelper, namespace: str, role: str, + default_namespace: str = "default" + ) -> requests.Response: + """ Create a token with a specified role in a specified namespace. """ + one_hour_in_future = get_time_in_future(1) + json_data = { + "primary_namespace": default_namespace, + "expiration": one_hour_in_future.isoformat(), + "role_bindings": { + f"{namespace}/*": [role] + } + } + return api.make_request( + "api/v1/token", method="POST", json_data=json_data) + + +def create_environment( + api: APIHelper, namespace: str, specification_path: str + ) -> requests.Response: + """ + Create an environment. + The environment specification is read + from a conda environment.yaml file. + """ + with open(specification_path, "r", encoding="utf-8") as file: + specification_content = file.read() + + json_data = { + "namespace": namespace, + "specification": specification_content + } + + return api.make_request( + "api/v1/specification", method="POST", json_data=json_data) + + +def wait_for_successful_build( + api: APIHelper, build_id: str + ) -> requests.Response: + """ Wait for a build to complete.""" + status = APIBuildStatus.QUEUED.value + while status != APIBuildStatus.COMPLETED.value: + response = api.make_request( + f"api/v1/build/{build_id}", method="GET") + status = response.json()["data"]["status"] + if status == APIBuildStatus.FAILED.value: + raise AssertionError("Build failed") + return response + + +def delete_environment( + api: APIHelper, namespace: str, environment_name: str + ) -> requests.Response: + """ Delete an environment.""" + return api.make_request( + f"api/v1/environment/{namespace}/{environment_name}", + method="DELETE") + + +def delete_namespace(api: APIHelper, namespace: str) -> requests.Response: + """ Delete a namespace.""" + return api.make_request(f"api/v1/namespace/{namespace}", method="DELETE") \ No newline at end of file diff --git a/conda-store-server/tests/user_journeys/test_data/simple_environment.yaml b/conda-store-server/tests/user_journeys/test_data/simple_environment.yaml new file mode 100644 index 000000000..faa1ab42e --- /dev/null +++ b/conda-store-server/tests/user_journeys/test_data/simple_environment.yaml @@ -0,0 +1,6 @@ +name: simple-test-environment +channels: + - conda-forge +dependencies: + - python ==3.10 + - fastapi diff --git a/conda-store-server/tests/user_journeys/test_user_journeys.py b/conda-store-server/tests/user_journeys/test_user_journeys.py new file mode 100644 index 000000000..e838b5c51 --- /dev/null +++ b/conda-store-server/tests/user_journeys/test_user_journeys.py @@ -0,0 +1,42 @@ +"""User journey tests for the API.""" +import os +import uuid + +from helpers import helpers as h +import pytest + + +@pytest.fixture(scope="session") +def base_url() -> str: + """Get the base URL for the API.""" + base = os.getenv("CONDA_STORE_BASE_URL", "http://localhost:8080") + return f"{base}/conda-store" + + +@pytest.fixture(scope="session") +def token(base_url) -> str: + """Get the token for the API.""" + return os.getenv("CONDA_STORE_TOKEN", "") + + +@pytest.mark.user_journey +@pytest.mark.parametrize("filename", [ + ("tests/user_journeys/test_data/simple_environment.yaml"), + # ("test_data/complex-environment.yaml") + ]) +def test_admin_user_can_create_environment(base_url, token, filename) -> None: + """Test that an admin user can create an environment.""" + namespace = uuid.uuid4().hex # Generate a random namespace + print(os.path.abspath(filename)) + api = h.APIHelper(base_url=base_url, token=token) + specification_path = f"{filename}" + response = h.create_environment(api, namespace, specification_path) + assert response.status_code == 200 + data = response.json()["data"] + assert "build_id" in data + build_id = data["build_id"] + assert build_id is not None + build = h.wait_for_successful_build(api, build_id) + environment_name = build.json()["data"]["specification"]["name"] + h.delete_environment(api, namespace, environment_name) + h.delete_namespace(api, namespace) From 4d07354f6936ed2cc5c577c70f7f61d5b71769d7 Mon Sep 17 00:00:00 2001 From: Chuck McAndrew <6248903+dcmcand@users.noreply.github.com> Date: Thu, 1 Feb 2024 10:58:15 -0500 Subject: [PATCH 02/60] update ci tests --- .github/workflows/tests.yaml | 6 +++++- conda-store-server/pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index d5b9ead5d..c078552b3 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -62,7 +62,7 @@ jobs: - name: "Unit tests ✅" run: | - pytest -m "not extended_prefix" tests + pytest -m "not extended_prefix and not user_journey" tests # https://github.com/actions/runner-images/issues/1052 - name: "Windows extended prefix unit tests ✅" @@ -125,6 +125,10 @@ jobs: export PYTHONPATH=$PYTHONPATH:$PWD pytest ../tests/test_api.py ../tests/test_metrics.py + - name: "Run user journey tests ✅" + run: | + pytest -m "user_journey" + - name: "Get Docker logs 🔍" if: ${{ failure() }} run: | diff --git a/conda-store-server/pyproject.toml b/conda-store-server/pyproject.toml index bdce874b6..b214e768c 100644 --- a/conda-store-server/pyproject.toml +++ b/conda-store-server/pyproject.toml @@ -108,5 +108,5 @@ ignore = ["W004"] markers = [ "playwright: mark a test as a playwright test", "integration: mark a test as an integration test", - "user-journey: mark a test as a user journey test", + "user_journey: mark a test as a user journey test", ] \ No newline at end of file From 4790b8d50a22b6327bcad3321f2ec0f2f49d57af Mon Sep 17 00:00:00 2001 From: Chuck McAndrew <6248903+dcmcand@users.noreply.github.com> Date: Thu, 1 Feb 2024 11:38:45 -0500 Subject: [PATCH 03/60] add readme and lint --- conda-store-server/pyproject.toml | 4 +- .../tests/user_journeys/README.md | 59 +++++++++++++++++++ .../tests/user_journeys/helpers/helpers.py | 2 +- .../tests/user_journeys/test_user_journeys.py | 2 +- 4 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 conda-store-server/tests/user_journeys/README.md diff --git a/conda-store-server/pyproject.toml b/conda-store-server/pyproject.toml index b214e768c..c5c3e6093 100644 --- a/conda-store-server/pyproject.toml +++ b/conda-store-server/pyproject.toml @@ -80,7 +80,7 @@ playwright-test = [ ] integration-test = ["pytest ../tests/test_api.py ../tests/test_metrics.py"] -user-journey-test = ["pytest -m user-journey"] +user-journey-test = ["pytest -m user_journey"] [tool.hatch.build.hooks.custom] @@ -109,4 +109,4 @@ markers = [ "playwright: mark a test as a playwright test", "integration: mark a test as an integration test", "user_journey: mark a test as a user journey test", -] \ No newline at end of file +] diff --git a/conda-store-server/tests/user_journeys/README.md b/conda-store-server/tests/user_journeys/README.md new file mode 100644 index 000000000..97c3f6ebc --- /dev/null +++ b/conda-store-server/tests/user_journeys/README.md @@ -0,0 +1,59 @@ +# User Journey Tests + +This repository contains user journey tests for the API. User journey tests are end-to-end tests that simulate real user scenarios to ensure the API functions correctly in different scenarios. + +These tests will use the high privileged token to create a randomly named namespace (using a UUID) to prevent conflicts with existing namespaces. At the end of the test, it will delete any environments created and then delete the namespace. + +## Prerequisites + +These tests are blackbox tests and need a running server to test against. This can be a local conda-store instance started using docker compose, or a remote instance. You will need the base url of the server and a token for an admin user to run these tests. + +## Setup + +### Local setup + +To run locally using docker compose all you need to do is start conda store. + +From the project base run `docker compose up` + +### Remote setup + +To run these tests against a remote server, you need to set 2 environmental variables + +1. `CONDA_STORE_BASE_URL` - this is the base url of your conda server. + + For example, if you access your conda-store-server at `https://example.com` you would run `export CONDA_STORE_BASE_URL='https://example.com`. + + Do not include the `/conda-store/` suffix. + + Do include the port if needed. For example: `export CONDA_STORE_BASE_URL='http://localhost:8080` + +2. `CONDA_STORE_TOKEN` - this should be the token of an admin user. + + This token will let the tests create the tokens, permissions, namespaces, and environments needed for these tests to run successfully + + To generate a token, while logged in as a high privileged user, go to `https://your-conda-store-url/conda-store/admin/user/` and click on `Create token`. + + Copy that token value and export it `export CONDA_STORE_TOKEN='my_token_value'` + +## Running the tests + +To run the tests run `pytest -m user_journey` from the `conda-store-server` directory + +## Current scenarios tested + +* An admin user can create a simple environment in a shared namespace and once the environment is built, can delete the environment + +## Planned scenarios to be implemented + +* A admin can create a complex environment in a shared namespace and once the environment is built, can delete the environment + +* A developer can create a simple environment in a shared namespace and once the environment is built, can delete the environment + +* A developer can create a complex environment in a shared namespace and once the environment is built, can delete the environment + +* A developer can create a create environment in a shared namespace and once the environment is built, can modify the environment, then can mark the first build as active. + +* A developer can create a simple environment in a shared namespace and once the environment is built, can get the lockfile for the environment + +* A developer can create a failing environment in a shared namespace and once the environment has failed, can get the logs for the failed build \ No newline at end of file diff --git a/conda-store-server/tests/user_journeys/helpers/helpers.py b/conda-store-server/tests/user_journeys/helpers/helpers.py index feb1b0e14..0576d159a 100644 --- a/conda-store-server/tests/user_journeys/helpers/helpers.py +++ b/conda-store-server/tests/user_journeys/helpers/helpers.py @@ -142,4 +142,4 @@ def delete_environment( def delete_namespace(api: APIHelper, namespace: str) -> requests.Response: """ Delete a namespace.""" - return api.make_request(f"api/v1/namespace/{namespace}", method="DELETE") \ No newline at end of file + return api.make_request(f"api/v1/namespace/{namespace}", method="DELETE") diff --git a/conda-store-server/tests/user_journeys/test_user_journeys.py b/conda-store-server/tests/user_journeys/test_user_journeys.py index e838b5c51..ea96a15d7 100644 --- a/conda-store-server/tests/user_journeys/test_user_journeys.py +++ b/conda-store-server/tests/user_journeys/test_user_journeys.py @@ -2,8 +2,8 @@ import os import uuid -from helpers import helpers as h import pytest +from helpers import helpers as h @pytest.fixture(scope="session") From 7b193dc1fd7ee9fe9d79ec4dc30723a005a21794 Mon Sep 17 00:00:00 2001 From: Chuck McAndrew <6248903+dcmcand@users.noreply.github.com> Date: Thu, 1 Feb 2024 11:41:06 -0500 Subject: [PATCH 04/60] seperate user journey test into new job --- .github/workflows/tests.yaml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index c078552b3..7f49f5354 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -125,6 +125,42 @@ jobs: export PYTHONPATH=$PYTHONPATH:$PWD pytest ../tests/test_api.py ../tests/test_metrics.py + - name: "Get Docker logs 🔍" + if: ${{ failure() }} + run: | + docker-compose logs + + user-journey-test-conda-store-server: + name: "integration-test conda-store-server" + runs-on: ubuntu-latest + defaults: + run: + shell: bash -el {0} + working-directory: conda-store-server + steps: + - name: "Checkout Repository 🛎" + uses: actions/checkout@v4 + + - name: "Set up env 🐍" + uses: conda-incubator/setup-miniconda@v2 + with: + environment-file: conda-store-server/environment-dev.yaml + miniforge-version: latest + + - name: "Install build dependencies 📦" + run: | + pip install hatch + sudo apt install wait-for-it -y + + - name: "Deploy docker-compose" + run: | + docker-compose up -d + docker ps + + wait-for-it localhost:5432 # postgresql + wait-for-it localhost:9000 # minio + wait-for-it localhost:8080 # conda-store-server + - name: "Run user journey tests ✅" run: | pytest -m "user_journey" From 81881df0ab5ee50fc9cea517def0300e092f7d36 Mon Sep 17 00:00:00 2001 From: Chuck McAndrew <6248903+dcmcand@users.noreply.github.com> Date: Thu, 1 Feb 2024 11:43:09 -0500 Subject: [PATCH 05/60] fix name of user-journey-test in gha and lint --- .github/workflows/tests.yaml | 2 +- conda-store-server/tests/user_journeys/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 7f49f5354..b2eb7d93a 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -131,7 +131,7 @@ jobs: docker-compose logs user-journey-test-conda-store-server: - name: "integration-test conda-store-server" + name: "user-journey-test conda-store-server" runs-on: ubuntu-latest defaults: run: diff --git a/conda-store-server/tests/user_journeys/README.md b/conda-store-server/tests/user_journeys/README.md index 97c3f6ebc..8843a6dcc 100644 --- a/conda-store-server/tests/user_journeys/README.md +++ b/conda-store-server/tests/user_journeys/README.md @@ -56,4 +56,4 @@ To run the tests run `pytest -m user_journey` from the `conda-store-server` dire * A developer can create a simple environment in a shared namespace and once the environment is built, can get the lockfile for the environment -* A developer can create a failing environment in a shared namespace and once the environment has failed, can get the logs for the failed build \ No newline at end of file +* A developer can create a failing environment in a shared namespace and once the environment has failed, can get the logs for the failed build From 08548a29d17ad28f40487dde293d13cab4ccf669 Mon Sep 17 00:00:00 2001 From: Chuck McAndrew <6248903+dcmcand@users.noreply.github.com> Date: Thu, 1 Feb 2024 12:52:10 -0500 Subject: [PATCH 06/60] update gha --- .github/workflows/tests.yaml | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index b2eb7d93a..c078552b3 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -125,42 +125,6 @@ jobs: export PYTHONPATH=$PYTHONPATH:$PWD pytest ../tests/test_api.py ../tests/test_metrics.py - - name: "Get Docker logs 🔍" - if: ${{ failure() }} - run: | - docker-compose logs - - user-journey-test-conda-store-server: - name: "user-journey-test conda-store-server" - runs-on: ubuntu-latest - defaults: - run: - shell: bash -el {0} - working-directory: conda-store-server - steps: - - name: "Checkout Repository 🛎" - uses: actions/checkout@v4 - - - name: "Set up env 🐍" - uses: conda-incubator/setup-miniconda@v2 - with: - environment-file: conda-store-server/environment-dev.yaml - miniforge-version: latest - - - name: "Install build dependencies 📦" - run: | - pip install hatch - sudo apt install wait-for-it -y - - - name: "Deploy docker-compose" - run: | - docker-compose up -d - docker ps - - wait-for-it localhost:5432 # postgresql - wait-for-it localhost:9000 # minio - wait-for-it localhost:8080 # conda-store-server - - name: "Run user journey tests ✅" run: | pytest -m "user_journey" From 44182465443abb159c0d32fd7d1446c5a2386249 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Wed, 24 Jan 2024 22:31:26 +0100 Subject: [PATCH 07/60] Add a test for parallel builds Fixes #548. --- tests/test_api.py | 96 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/tests/test_api.py b/tests/test_api.py index bc16f80c0..711fbea1b 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -9,6 +9,8 @@ """ import asyncio +import collections +import datetime import json import time import uuid @@ -467,6 +469,100 @@ def test_create_specification_auth(testclient): assert r.data.namespace.name == namespace +def test_create_specification_parallel_auth(testclient): + namespace = "default" + environment_name = f"pytest-{uuid.uuid4()}" + + # Builds different versions to avoid caching + versions = ["6.2.0", "6.2.1", "6.2.2", "6.2.3", "6.2.4", "6.2.5", "7.1.1", "7.1.2", "7.3.1", "7.4.0"] + num_builds = len(versions) + limit_seconds = 60 * 15 + build_ids = collections.deque([]) + + # Spins up 'num_builds' builds and adds them to 'build_ids' + for version in versions: + testclient.login() + response = testclient.post( + "api/v1/specification", + json={ + "namespace": namespace, + "specification": json.dumps({ + "name": environment_name, + "channels": ["main"], + "dependencies": [f"pytest={version}"], + }), + }, + timeout=10, + ) + response.raise_for_status() + r = schema.APIPostSpecification.parse_obj(response.json()) + assert r.status == schema.APIStatus.OK + build_ids.append(r.data.build_id) + + # How long it takes to do a single build + build_delta = None + + # Checks whether the builds are done (in the order they were scheduled in) + start = datetime.datetime.now() + while True: + # Prints the current build ids in the queue. Visually, if the server is + # configured to run N jobs in parallel, the queue should have N jobs + # less almost instantly once the first batch is done processing. After + # that, the queue should keep shrinking at a steady pace after each of + # the workers is done + print("build_ids", build_ids) + + # Checks whether the time limit is reached + now = datetime.datetime.now() + delta_seconds = (now - start).total_seconds() + if delta_seconds > limit_seconds: + break + + # Measures how long it takes to do a single build + if not build_delta and len(build_ids) < num_builds: + build_delta = delta_seconds + + # Gets the oldest build in the queue as it's the one that's most likely + # to be done + try: + build_id = build_ids.popleft() + except IndexError: + break + + # Checks the status + response = testclient.get(f"api/v1/build/{build_id}", timeout=10) + response.raise_for_status() + r = schema.APIGetBuild.parse_obj(response.json()) + assert r.status == schema.APIStatus.OK + assert r.data.specification.name == environment_name + + # Exit immediately on failure + assert r.data.status != 'FAILED' + + # If not done, adds the id back to the end of the queue + if r.data.status != 'COMPLETED': + build_ids.append(build_id) + + # Adds a small delay to avoid making too many requests too fast. The + # build takes significantly longer than 1 second, so this shouldn't + # impact the measurements + time.sleep(1) + + # Measures how long it took to run the loop + loop_delta = (datetime.datetime.now() - start).total_seconds() + print("build_delta", build_delta) + print("loop_delta", loop_delta) + + # If there are jobs in the queue, the loop didn't complete in the allocated + # time. So something went wrong, like a build getting stuck or parallel + # builds not working + assert len(build_ids) == 0 + + # If all jobs are done twice as fast as they would do sequentially, then + # parallel builds are working + assert loop_delta < build_delta * (num_builds / 2) + + # Only testing size values that will always cause errors. Smaller values could # cause errors as well, but would be flaky since the test conda-store state # directory might have different lengths on different systems, for instance, From e98582a1703a281532138e01f0c1e4b6dc3f65fa Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Thu, 25 Jan 2024 04:38:06 +0100 Subject: [PATCH 08/60] Use a per-environment cache to not overwrite dependencies --- .../action/install_lockfile.py | 32 ++++++++++++++++++- .../conda_store_server/build.py | 3 ++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/conda-store-server/conda_store_server/action/install_lockfile.py b/conda-store-server/conda_store_server/action/install_lockfile.py index 23d7d5c94..cf61af52f 100644 --- a/conda-store-server/conda_store_server/action/install_lockfile.py +++ b/conda-store-server/conda_store_server/action/install_lockfile.py @@ -1,6 +1,8 @@ import json +import os import pathlib import sys +import tempfile import typing from conda_store_server import action @@ -16,6 +18,32 @@ def action_install_lockfile( with lockfile_filename.open("w") as f: json.dump(conda_lock_spec, f) + # This essentially disables caching by having a dedicated cache per + # environment. This is necessary because concurrent jobs override packages + # in the shared cache, which leads to failed builds. + # + # For example, packages A and B use the same dependency. Package A starts + # the download process and puts the dependency in the cache. Package B sees + # that the dependency is in the cache and tries to use it, but the + # dependency can be in an invalid state, such as being not fully-copied or + # downloaded, because the cache operations are not atomic, so the build of + # package B fails. + # + # This is a known problem in conda and I'm not aware of any solution besides + # just disabling the cache by having a per-environment temporary cache + # directory. The name of the temporary directory is returned from this + # action so that the directory can be deleted once the package is done + # building, as that requires having access to dependencies from the cache. + # + # Here are the relevant issues from the upstream repo, but they were closed + # without any solution: + # https://github.com/conda/conda/issues/9447 + # https://github.com/conda/conda/issues/5997 + # https://github.com/conda/conda/issues/7741 + tmp_pkgs_dir = tempfile.TemporaryDirectory() + env = os.environ.copy() + env["CONDA_PKGS_DIRS"] = tmp_pkgs_dir.name + command = [ sys.executable, "-m", @@ -29,4 +57,6 @@ def action_install_lockfile( str(lockfile_filename), ] - context.run(command, check=True) + context.run(command, check=True, env=env) + + return tmp_pkgs_dir diff --git a/conda-store-server/conda_store_server/build.py b/conda-store-server/conda_store_server/build.py index d0257be88..f8eaf5f33 100644 --- a/conda-store-server/conda_store_server/build.py +++ b/conda-store-server/conda_store_server/build.py @@ -197,6 +197,7 @@ def build_conda_environment(db: Session, conda_store, build): conda_lock_spec=conda_lock_spec, conda_prefix=conda_prefix, ) + tmp_pkgs_dir = context.result append_to_logs( db, conda_store, @@ -224,6 +225,8 @@ def build_conda_environment(db: Session, conda_store, build): context = action.action_get_conda_prefix_stats(conda_prefix) build.size = context.result["disk_usage"] + tmp_pkgs_dir.cleanup() + set_build_completed(db, conda_store, build) # Always mark build as failed first since other functions may throw an # exception From bc8c946da2d5ba88e8c7063115277cc0bb0eeda0 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Thu, 25 Jan 2024 18:30:07 +0100 Subject: [PATCH 09/60] Explicitly check for None --- tests/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index 711fbea1b..bc9aaff38 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -519,7 +519,7 @@ def test_create_specification_parallel_auth(testclient): break # Measures how long it takes to do a single build - if not build_delta and len(build_ids) < num_builds: + if build_delta is not None and len(build_ids) < num_builds: build_delta = delta_seconds # Gets the oldest build in the queue as it's the one that's most likely From 13b99dae040cd91ae021a793602dd110435d5b09 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Thu, 25 Jan 2024 18:59:28 +0100 Subject: [PATCH 10/60] Invert the condition --- tests/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index bc9aaff38..d72eaba30 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -519,7 +519,7 @@ def test_create_specification_parallel_auth(testclient): break # Measures how long it takes to do a single build - if build_delta is not None and len(build_ids) < num_builds: + if build_delta is None and len(build_ids) < num_builds: build_delta = delta_seconds # Gets the oldest build in the queue as it's the one that's most likely From f5642680d1365124e24c7d870d67bba891e9e57e Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Thu, 25 Jan 2024 19:26:35 +0100 Subject: [PATCH 11/60] Improve wording --- .../conda_store_server/action/install_lockfile.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/conda-store-server/conda_store_server/action/install_lockfile.py b/conda-store-server/conda_store_server/action/install_lockfile.py index cf61af52f..a46498b3c 100644 --- a/conda-store-server/conda_store_server/action/install_lockfile.py +++ b/conda-store-server/conda_store_server/action/install_lockfile.py @@ -18,9 +18,9 @@ def action_install_lockfile( with lockfile_filename.open("w") as f: json.dump(conda_lock_spec, f) - # This essentially disables caching by having a dedicated cache per - # environment. This is necessary because concurrent jobs override packages - # in the shared cache, which leads to failed builds. + # This essentially disables caching by having a dedicated cache per build. + # This is necessary because concurrent jobs override packages in the shared + # cache, which leads to failed builds. # # For example, packages A and B use the same dependency. Package A starts # the download process and puts the dependency in the cache. Package B sees @@ -30,10 +30,10 @@ def action_install_lockfile( # package B fails. # # This is a known problem in conda and I'm not aware of any solution besides - # just disabling the cache by having a per-environment temporary cache - # directory. The name of the temporary directory is returned from this - # action so that the directory can be deleted once the package is done - # building, as that requires having access to dependencies from the cache. + # just disabling the cache by having a per-build temporary cache directory. + # The name of the temporary directory is returned from this action so that + # the directory can be deleted once the package is done building, as that + # requires having access to dependencies from the cache. # # Here are the relevant issues from the upstream repo, but they were closed # without any solution: From afd30baecf251f7d9e56e63c838e2d1ae4038954 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Thu, 25 Jan 2024 19:27:10 +0100 Subject: [PATCH 12/60] Minor comment fix --- tests/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index d72eaba30..fdd3391cf 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -536,7 +536,7 @@ def test_create_specification_parallel_auth(testclient): assert r.status == schema.APIStatus.OK assert r.data.specification.name == environment_name - # Exit immediately on failure + # Exits immediately on failure assert r.data.status != 'FAILED' # If not done, adds the id back to the end of the queue From 407d2588035c8d5a4d952923af881d812648b661 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Thu, 25 Jan 2024 23:11:18 +0100 Subject: [PATCH 13/60] Print deltas between subsequent builds --- tests/test_api.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index fdd3391cf..c85ebd614 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -504,6 +504,8 @@ def test_create_specification_parallel_auth(testclient): # Checks whether the builds are done (in the order they were scheduled in) start = datetime.datetime.now() + prev = None + prev_builds = num_builds while True: # Prints the current build ids in the queue. Visually, if the server is # configured to run N jobs in parallel, the queue should have N jobs @@ -514,13 +516,16 @@ def test_create_specification_parallel_auth(testclient): # Checks whether the time limit is reached now = datetime.datetime.now() - delta_seconds = (now - start).total_seconds() - if delta_seconds > limit_seconds: + if (now - start).total_seconds() > limit_seconds: break # Measures how long it takes to do a single build - if build_delta is None and len(build_ids) < num_builds: - build_delta = delta_seconds + if len(build_ids) < prev_builds: + if prev is not None: + build_delta = (now - prev).total_seconds() + print("build_delta", build_delta) + prev_builds = len(build_ids) + prev = now # Gets the oldest build in the queue as it's the one that's most likely # to be done @@ -548,20 +553,11 @@ def test_create_specification_parallel_auth(testclient): # impact the measurements time.sleep(1) - # Measures how long it took to run the loop - loop_delta = (datetime.datetime.now() - start).total_seconds() - print("build_delta", build_delta) - print("loop_delta", loop_delta) - # If there are jobs in the queue, the loop didn't complete in the allocated # time. So something went wrong, like a build getting stuck or parallel # builds not working assert len(build_ids) == 0 - # If all jobs are done twice as fast as they would do sequentially, then - # parallel builds are working - assert loop_delta < build_delta * (num_builds / 2) - # Only testing size values that will always cause errors. Smaller values could # cause errors as well, but would be flaky since the test conda-store state From 091ffe5606d029a66bb0ac9e40e3250759e58c6a Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Fri, 26 Jan 2024 19:52:01 +0100 Subject: [PATCH 14/60] Add a statistical test --- tests/test_api.py | 65 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index c85ebd614..73f317a99 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -12,6 +12,7 @@ import collections import datetime import json +import statistics import time import uuid from functools import partial @@ -500,7 +501,7 @@ def test_create_specification_parallel_auth(testclient): build_ids.append(r.data.build_id) # How long it takes to do a single build - build_delta = None + build_deltas = [] # Checks whether the builds are done (in the order they were scheduled in) start = datetime.datetime.now() @@ -524,6 +525,7 @@ def test_create_specification_parallel_auth(testclient): if prev is not None: build_delta = (now - prev).total_seconds() print("build_delta", build_delta) + build_deltas.append(build_delta) prev_builds = len(build_ids) prev = now @@ -558,6 +560,67 @@ def test_create_specification_parallel_auth(testclient): # builds not working assert len(build_ids) == 0 + # Because this is an integration test, we cannot change the server + # c.CondaStoreWorker.concurrency value, which is set to 4 by default. But + # it's possible to devise a statistical test based on locally collected + # data, using 'build_deltas' above: + # + # concurrency = 4 with 2 CPUs, so equivalent to concurrency = 2: + # c4_2cpu = [ + # 1.027987, + # 67.234371, + # 1.272288, + # 43.966526, + # 7.171627, + # 68.563222, + # 4.143675, + # 46.872263, + # 1.018258, + # ] + # + # concurrency = 1, same machine: + # c1 = [ + # 19.394085, + # 33.70623, + # 22.815429, + # 62.555845, + # 68.438333, + # 29.794979, + # 63.370743, + # 32.118421, + # 29.88376, + # ] + # + # Here's another set of measurements from a different machine, which should + # have 4 CPUs, with concurrency = 4: + # ci = [ + # 1.02644, + # 33.591736, + # 1.016269, + # 19.299738, + # 7.115025, + # 20.342442, + # 12.222805, + # 6.103751, + # 1.016237, + # ] + # + # These values will vary depending on workload and the number of CPUs. But + # the main observation here is this: if parallel builds are working, + # there will be a number of values that are relatively small compared to the + # time it takes to run a single build when not running concurrently. + # + # So the test below looks at the value of the first quartile, which is the + # median of the lower half of the dataset, where all these small values will + # be located, and compares it to a certain threshold, which is unlikely to + # be reached based on how long it takes a single build to run + # non-concurrently on average: + threshold = 5 + quartiles = statistics.quantiles(build_deltas, method='inclusive') + print("build_deltas", build_deltas) + print("stats", min(build_deltas), quartiles) + assert quartiles[0] < threshold + # Only testing size values that will always cause errors. Smaller values could # cause errors as well, but would be flaky since the test conda-store state From c60823aa1d10e03cdfd0f2af637abfcb7291ebfc Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 5 Feb 2024 01:05:51 +0100 Subject: [PATCH 15/60] Fix the caching bug --- .../action/download_packages.py | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/conda-store-server/conda_store_server/action/download_packages.py b/conda-store-server/conda_store_server/action/download_packages.py index c775eb648..034ab5aca 100644 --- a/conda-store-server/conda_store_server/action/download_packages.py +++ b/conda-store-server/conda_store_server/action/download_packages.py @@ -1,10 +1,21 @@ +import os import pathlib import shutil import typing +# This import is needed to avoid the following error on conda imports: +# AttributeError: 'Logger' object has no attribute 'trace' +import conda.gateways.logging # noqa import conda_package_handling.api import conda_package_streaming.url import filelock +from conda.core.package_cache_data import ( + PackageCacheRecord, + PackageRecord, + getsize, + read_index_json, + write_as_json_to_file, +) from conda_store_server import action, conda_utils @@ -28,6 +39,7 @@ def action_fetch_and_extract_conda_packages( file_path = pkgs_dir / filename count_message = f"{packages_searched} of {total_packages}" with filelock.FileLock(str(lock_filename)): + file_path_str = str(file_path) if file_path.exists(): context.log.info(f"SKIPPING {filename} | FILE EXISTS\n") else: @@ -38,4 +50,35 @@ def action_fetch_and_extract_conda_packages( ) = conda_package_streaming.url.conda_reader_for_url(url) with file_path.open("wb") as f: shutil.copyfileobj(conda_package_stream, f) - conda_package_handling.api.extract(str(file_path)) + conda_package_handling.api.extract(file_path_str) + + # Otherwise .conda, do nothing in that case + ext = ".tar.bz2" + if file_path_str.endswith(ext): + extracted_dir = pathlib.Path(file_path_str.replace(ext, "")) + # This file is used to parse cache records via PackageCacheRecord in conda + repodata_file = extracted_dir / "info" / "repodata_record.json" + index_file = extracted_dir / "info" / "index.json" + assert index_file.exists() + + if not repodata_file.exists(): + raw_json_record = read_index_json(extracted_dir) + fn = os.path.basename(file_path_str) + md5 = package["hash"]["md5"] + size = getsize(file_path_str) + + package_cache_record = PackageCacheRecord.from_objects( + raw_json_record, + url=url, + fn=fn, + md5=md5, + size=size, + package_tarball_full_path=file_path_str, + extracted_package_dir=str(extracted_dir), + ) + + repodata_record = PackageRecord.from_objects( + package_cache_record + ) + write_as_json_to_file(repodata_file, repodata_record) + assert repodata_file.exists() From edb501b4c956d649f70f03cd5bcad91f57aab8f6 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 5 Feb 2024 03:00:59 +0100 Subject: [PATCH 16/60] Do not use temporary caches --- .../action/install_lockfile.py | 32 +------------------ .../conda_store_server/build.py | 3 -- 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/conda-store-server/conda_store_server/action/install_lockfile.py b/conda-store-server/conda_store_server/action/install_lockfile.py index a46498b3c..23d7d5c94 100644 --- a/conda-store-server/conda_store_server/action/install_lockfile.py +++ b/conda-store-server/conda_store_server/action/install_lockfile.py @@ -1,8 +1,6 @@ import json -import os import pathlib import sys -import tempfile import typing from conda_store_server import action @@ -18,32 +16,6 @@ def action_install_lockfile( with lockfile_filename.open("w") as f: json.dump(conda_lock_spec, f) - # This essentially disables caching by having a dedicated cache per build. - # This is necessary because concurrent jobs override packages in the shared - # cache, which leads to failed builds. - # - # For example, packages A and B use the same dependency. Package A starts - # the download process and puts the dependency in the cache. Package B sees - # that the dependency is in the cache and tries to use it, but the - # dependency can be in an invalid state, such as being not fully-copied or - # downloaded, because the cache operations are not atomic, so the build of - # package B fails. - # - # This is a known problem in conda and I'm not aware of any solution besides - # just disabling the cache by having a per-build temporary cache directory. - # The name of the temporary directory is returned from this action so that - # the directory can be deleted once the package is done building, as that - # requires having access to dependencies from the cache. - # - # Here are the relevant issues from the upstream repo, but they were closed - # without any solution: - # https://github.com/conda/conda/issues/9447 - # https://github.com/conda/conda/issues/5997 - # https://github.com/conda/conda/issues/7741 - tmp_pkgs_dir = tempfile.TemporaryDirectory() - env = os.environ.copy() - env["CONDA_PKGS_DIRS"] = tmp_pkgs_dir.name - command = [ sys.executable, "-m", @@ -57,6 +29,4 @@ def action_install_lockfile( str(lockfile_filename), ] - context.run(command, check=True, env=env) - - return tmp_pkgs_dir + context.run(command, check=True) diff --git a/conda-store-server/conda_store_server/build.py b/conda-store-server/conda_store_server/build.py index f8eaf5f33..d0257be88 100644 --- a/conda-store-server/conda_store_server/build.py +++ b/conda-store-server/conda_store_server/build.py @@ -197,7 +197,6 @@ def build_conda_environment(db: Session, conda_store, build): conda_lock_spec=conda_lock_spec, conda_prefix=conda_prefix, ) - tmp_pkgs_dir = context.result append_to_logs( db, conda_store, @@ -225,8 +224,6 @@ def build_conda_environment(db: Session, conda_store, build): context = action.action_get_conda_prefix_stats(conda_prefix) build.size = context.result["disk_usage"] - tmp_pkgs_dir.cleanup() - set_build_completed(db, conda_store, build) # Always mark build as failed first since other functions may throw an # exception From 22ed66a0b4c5d2a8260169fdf85e6374af89d261 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 5 Feb 2024 03:21:54 +0100 Subject: [PATCH 17/60] Remove asserts --- .../conda_store_server/action/download_packages.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/conda-store-server/conda_store_server/action/download_packages.py b/conda-store-server/conda_store_server/action/download_packages.py index 034ab5aca..b015df61e 100644 --- a/conda-store-server/conda_store_server/action/download_packages.py +++ b/conda-store-server/conda_store_server/action/download_packages.py @@ -58,8 +58,6 @@ def action_fetch_and_extract_conda_packages( extracted_dir = pathlib.Path(file_path_str.replace(ext, "")) # This file is used to parse cache records via PackageCacheRecord in conda repodata_file = extracted_dir / "info" / "repodata_record.json" - index_file = extracted_dir / "info" / "index.json" - assert index_file.exists() if not repodata_file.exists(): raw_json_record = read_index_json(extracted_dir) @@ -81,4 +79,3 @@ def action_fetch_and_extract_conda_packages( package_cache_record ) write_as_json_to_file(repodata_file, repodata_record) - assert repodata_file.exists() From d3464104577227f72fc6d8059fe29f58057c6fb7 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 5 Feb 2024 03:49:03 +0100 Subject: [PATCH 18/60] Fix cache query failures by creating the magic file --- .../action/download_packages.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/conda-store-server/conda_store_server/action/download_packages.py b/conda-store-server/conda_store_server/action/download_packages.py index b015df61e..fcb74f7b5 100644 --- a/conda-store-server/conda_store_server/action/download_packages.py +++ b/conda-store-server/conda_store_server/action/download_packages.py @@ -9,6 +9,8 @@ import conda_package_handling.api import conda_package_streaming.url import filelock +from conda.base.constants import PACKAGE_CACHE_MAGIC_FILE +from conda.common.path import expand from conda.core.package_cache_data import ( PackageCacheRecord, PackageRecord, @@ -16,6 +18,7 @@ read_index_json, write_as_json_to_file, ) +from conda.gateways.disk.update import touch from conda_store_server import action, conda_utils @@ -39,6 +42,27 @@ def action_fetch_and_extract_conda_packages( file_path = pkgs_dir / filename count_message = f"{packages_searched} of {total_packages}" with filelock.FileLock(str(lock_filename)): + # This magic file, which is currently set to "urls.txt", is used + # to check cache permissions in conda, see _check_writable in + # PackageCacheData. Sometimes this file is not yet created while + # this action is running. But without this magic file, + # PackageCacheData cache query functions, like query_all, will + # return nothing, so packages will be downloaded outside of this + # action, which will corrupt the cache. Without the magic file, + # this error might be thrown when installing the lockfile: + # File "/opt/conda/lib/python3.10/site-packages/conda/misc.py", line 110, in explicit + # raise AssertionError("No package cache records found") + # The code below is from create_package_cache_directory in + # conda, which creates the package cache, but we only need the + # magic file part here: + cache_magic_file = os.path.join(pkgs_dir, PACKAGE_CACHE_MAGIC_FILE) + sudo_safe = expand(pkgs_dir).startswith(expand("~")) + touch( + cache_magic_file, + mkdir=True, + sudo_safe=sudo_safe, + ) + file_path_str = str(file_path) if file_path.exists(): context.log.info(f"SKIPPING {filename} | FILE EXISTS\n") From 71465c4c0cf8c3f614770248e1f4a212d032fa5b Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 5 Feb 2024 04:07:36 +0100 Subject: [PATCH 19/60] Check if the magic file exists first --- .../conda_store_server/action/download_packages.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/conda-store-server/conda_store_server/action/download_packages.py b/conda-store-server/conda_store_server/action/download_packages.py index fcb74f7b5..026e1a73a 100644 --- a/conda-store-server/conda_store_server/action/download_packages.py +++ b/conda-store-server/conda_store_server/action/download_packages.py @@ -55,13 +55,10 @@ def action_fetch_and_extract_conda_packages( # The code below is from create_package_cache_directory in # conda, which creates the package cache, but we only need the # magic file part here: - cache_magic_file = os.path.join(pkgs_dir, PACKAGE_CACHE_MAGIC_FILE) - sudo_safe = expand(pkgs_dir).startswith(expand("~")) - touch( - cache_magic_file, - mkdir=True, - sudo_safe=sudo_safe, - ) + cache_magic_file = pkgs_dir / PACKAGE_CACHE_MAGIC_FILE + if not cache_magic_file.exists(): + sudo_safe = expand(pkgs_dir).startswith(expand("~")) + touch(cache_magic_file, mkdir=True, sudo_safe=sudo_safe) file_path_str = str(file_path) if file_path.exists(): From cdd54952723968ea93757b5b9446fc4ff141fcce Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 5 Feb 2024 04:34:22 +0100 Subject: [PATCH 20/60] Unlink right away to avoid race conditions --- conda-store-server/conda_store_server/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/conda-store-server/conda_store_server/utils.py b/conda-store-server/conda_store_server/utils.py index 386561599..382546713 100644 --- a/conda-store-server/conda_store_server/utils.py +++ b/conda-store-server/conda_store_server/utils.py @@ -20,8 +20,12 @@ class BuildPathError(CondaStoreError): def symlink(source, target): - if os.path.islink(target): + # Do not use an if block to check whether the file exists, this is prone to + # race conditions. Try unlinking right away instead + try: os.unlink(target) + except FileNotFoundError: + pass os.symlink(source, target) From 92931837d23bd811f82437f13f6a06601630df9f Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 5 Feb 2024 05:56:41 +0100 Subject: [PATCH 21/60] Better document package cache issues --- .../action/download_packages.py | 80 +++++++++++++++++-- 1 file changed, 74 insertions(+), 6 deletions(-) diff --git a/conda-store-server/conda_store_server/action/download_packages.py b/conda-store-server/conda_store_server/action/download_packages.py index 026e1a73a..4d8476f50 100644 --- a/conda-store-server/conda_store_server/action/download_packages.py +++ b/conda-store-server/conda_store_server/action/download_packages.py @@ -44,14 +44,18 @@ def action_fetch_and_extract_conda_packages( with filelock.FileLock(str(lock_filename)): # This magic file, which is currently set to "urls.txt", is used # to check cache permissions in conda, see _check_writable in - # PackageCacheData. Sometimes this file is not yet created while - # this action is running. But without this magic file, - # PackageCacheData cache query functions, like query_all, will - # return nothing, so packages will be downloaded outside of this - # action, which will corrupt the cache. Without the magic file, - # this error might be thrown when installing the lockfile: + # PackageCacheData. + # + # Sometimes this file is not yet created while this action is + # running. Without this magic file, PackageCacheData cache + # query functions, like query_all, will return nothing. + # + # If the magic file is not present, this error might be thrown + # during the lockfile install action: + # # File "/opt/conda/lib/python3.10/site-packages/conda/misc.py", line 110, in explicit # raise AssertionError("No package cache records found") + # # The code below is from create_package_cache_directory in # conda, which creates the package cache, but we only need the # magic file part here: @@ -73,6 +77,70 @@ def action_fetch_and_extract_conda_packages( shutil.copyfileobj(conda_package_stream, f) conda_package_handling.api.extract(file_path_str) + # This code is needed to avoid failures when building in + # parallel while using the shared cache. + # + # There are tarballs that only have the info/index.json file + # and no info/repodata_record.json. The latter is used to + # interact with the cache, so _make_single_record from + # PackageCacheData would create the missing json file during + # the lockfile install action. + # + # The code that does that in conda is similar to the code + # below. However, there is an important difference. The + # code in conda would fail to read the url and return None + # here: + # + # url = self._urls_data.get_url(package_filename) + # + # And that would result in the channel field of the json + # file being set to "". This is a problem because + # the channel is used when querying cache entries, via + # match_individual from MatchSpec, which would always result + # in a mismatch because the proper channel value is + # different. + # + # That would make conda think that the package is not + # available in the cache, so it would try to download it + # outside of this action, where no locking is implemented. + # + # As of now, conda's cache is not atomic, so the same + # dependencies requested by different builds would overwrite + # each other causing random failures during the build + # process. + # + # To avoid this problem, the code below does what the code + # in conda does but also sets the url properly, which would + # make the channel match properly during the query process + # later. So no dependencies would be downloaded outside of + # this action and cache corruption is prevented. + # + # To illustrate, here's a diff of an old conda entry, which + # didn't work, versus the new one created by this action: + # + # --- /tmp/old.txt 2024-02-05 01:08:16.879751010 +0100 + # +++ /tmp/new.txt 2024-02-05 01:08:02.919319887 +0100 + # @@ -2,7 +2,7 @@ + # "arch": "x86_64", + # "build": "conda_forge", + # "build_number": 0, + # - "channel": "", + # + "channel": "https://conda.anaconda.org/conda-forge/linux-64", + # "constrains": [], + # "depends": [], + # "features": "", + # @@ -15,5 +15,6 @@ + # "subdir": "linux-64", + # "timestamp": 1578324546067, + # "track_features": "", + # + "url": "https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2", + # "version": "0.1" + # } + # + # Also see the comment above about the cache magic file. + # Without the magic file, cache queries would fail even if + # repodata_record.json files have proper channels specified. + # Otherwise .conda, do nothing in that case ext = ".tar.bz2" if file_path_str.endswith(ext): From a8f1a4c465886c50b854a2482d18ca25c30917f2 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 5 Feb 2024 19:33:49 +0100 Subject: [PATCH 22/60] Increase the number of test iterations --- tests/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index 73f317a99..731df4525 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -661,7 +661,7 @@ def test_create_specification_auth_env_name_too_long(testclient, size): # Try checking that the status is 'FAILED' is_updated = False - for _ in range(5): + for _ in range(10): time.sleep(5) # check for the given build From 7427811955397791987339af2946f21177432a93 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 5 Feb 2024 20:09:18 +0100 Subject: [PATCH 23/60] Increase test build timeout --- tests/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index 731df4525..a1c971637 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1138,7 +1138,7 @@ def test_api_cancel_build_auth(testclient): new_build_id = r.data.build_id # Delay to ensure the build kicks off - build_timeout = 180 + build_timeout = 10 * 60 building = False start = time.time() while time.time() - start < build_timeout: From c49015ba85c800146ca73fc04e67bde2838fc9a0 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 5 Feb 2024 22:06:54 +0100 Subject: [PATCH 24/60] Increase the test timeout again --- tests/test_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index a1c971637..4daf735a5 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -661,8 +661,8 @@ def test_create_specification_auth_env_name_too_long(testclient, size): # Try checking that the status is 'FAILED' is_updated = False - for _ in range(10): - time.sleep(5) + for _ in range(60): + time.sleep(10) # check for the given build response = testclient.get(f"api/v1/build/{build_id}") From ed1c4ce1def0c88c678eb96542daba1415456ea0 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 5 Feb 2024 22:48:59 +0100 Subject: [PATCH 25/60] Print build log on failure --- tests/test_api.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index 4daf735a5..06c408473 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -543,8 +543,14 @@ def test_create_specification_parallel_auth(testclient): assert r.status == schema.APIStatus.OK assert r.data.specification.name == environment_name + # Gets build logs + def get_logs(): + response = testclient.get(f"api/v1/build/{build_id}/logs", timeout=10) + response.raise_for_status() + return response.text + # Exits immediately on failure - assert r.data.status != 'FAILED' + assert r.data.status != 'FAILED', get_logs() # If not done, adds the id back to the end of the queue if r.data.status != 'COMPLETED': From aea0360eef877b305eae873280b7568ea74f84e2 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Tue, 6 Feb 2024 00:59:29 +0100 Subject: [PATCH 26/60] Avoid a race condition on symlink --- conda-store-server/conda_store_server/utils.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/conda-store-server/conda_store_server/utils.py b/conda-store-server/conda_store_server/utils.py index 382546713..10051de4a 100644 --- a/conda-store-server/conda_store_server/utils.py +++ b/conda-store-server/conda_store_server/utils.py @@ -8,6 +8,8 @@ import sys import time +from filelock import FileLock + class CondaStoreError(Exception): @property @@ -20,13 +22,14 @@ class BuildPathError(CondaStoreError): def symlink(source, target): - # Do not use an if block to check whether the file exists, this is prone to - # race conditions. Try unlinking right away instead - try: - os.unlink(target) - except FileNotFoundError: - pass - os.symlink(source, target) + # Multiple builds call this, so this lock avoids race conditions on unlink + # and symlink operations + with FileLock(f"{target}.lock"): + try: + os.unlink(target) + except FileNotFoundError: + pass + os.symlink(source, target) def chmod(directory, permissions): From 986975c3abb8ee6d9863a3419f1a61f0b32a78fe Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Tue, 6 Feb 2024 05:04:33 +0100 Subject: [PATCH 27/60] Fix the unknown channel issue for `.conda` archives --- .../action/download_packages.py | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/conda-store-server/conda_store_server/action/download_packages.py b/conda-store-server/conda_store_server/action/download_packages.py index 4d8476f50..bd0c187ac 100644 --- a/conda-store-server/conda_store_server/action/download_packages.py +++ b/conda-store-server/conda_store_server/action/download_packages.py @@ -10,7 +10,7 @@ import conda_package_streaming.url import filelock from conda.base.constants import PACKAGE_CACHE_MAGIC_FILE -from conda.common.path import expand +from conda.common.path import expand, strip_pkg_extension from conda.core.package_cache_data import ( PackageCacheRecord, PackageRecord, @@ -141,30 +141,28 @@ def action_fetch_and_extract_conda_packages( # Without the magic file, cache queries would fail even if # repodata_record.json files have proper channels specified. - # Otherwise .conda, do nothing in that case - ext = ".tar.bz2" - if file_path_str.endswith(ext): - extracted_dir = pathlib.Path(file_path_str.replace(ext, "")) - # This file is used to parse cache records via PackageCacheRecord in conda - repodata_file = extracted_dir / "info" / "repodata_record.json" + extracted_dir = pathlib.Path(strip_pkg_extension(file_path_str)[0]) + # This file is used to parse cache records via PackageCacheRecord in conda + repodata_file = extracted_dir / "info" / "repodata_record.json" - if not repodata_file.exists(): - raw_json_record = read_index_json(extracted_dir) - fn = os.path.basename(file_path_str) - md5 = package["hash"]["md5"] - size = getsize(file_path_str) + if not repodata_file.exists(): + raw_json_record = read_index_json(extracted_dir) + fn = os.path.basename(file_path_str) + md5 = package["hash"]["md5"] + size = getsize(file_path_str) - package_cache_record = PackageCacheRecord.from_objects( - raw_json_record, - url=url, - fn=fn, - md5=md5, - size=size, - package_tarball_full_path=file_path_str, - extracted_package_dir=str(extracted_dir), - ) + package_cache_record = PackageCacheRecord.from_objects( + raw_json_record, + url=url, + fn=fn, + md5=md5, + size=size, + package_tarball_full_path=file_path_str, + extracted_package_dir=str(extracted_dir), + ) - repodata_record = PackageRecord.from_objects( - package_cache_record - ) - write_as_json_to_file(repodata_file, repodata_record) + repodata_record = PackageRecord.from_objects( + package_cache_record + ) + + write_as_json_to_file(repodata_file, repodata_record) From 50db50cbbfcece9ee3e4f2fd086f3a957e172fad Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Tue, 6 Feb 2024 06:45:21 +0100 Subject: [PATCH 28/60] Avoid a race condition when accessing extracted archive --- .../action/download_packages.py | 203 ++++++++++-------- 1 file changed, 110 insertions(+), 93 deletions(-) diff --git a/conda-store-server/conda_store_server/action/download_packages.py b/conda-store-server/conda_store_server/action/download_packages.py index bd0c187ac..1ef2b1c89 100644 --- a/conda-store-server/conda_store_server/action/download_packages.py +++ b/conda-store-server/conda_store_server/action/download_packages.py @@ -1,6 +1,7 @@ import os import pathlib import shutil +import tempfile import typing # This import is needed to avoid the following error on conda imports: @@ -64,105 +65,121 @@ def action_fetch_and_extract_conda_packages( sudo_safe = expand(pkgs_dir).startswith(expand("~")) touch(cache_magic_file, mkdir=True, sudo_safe=sudo_safe) - file_path_str = str(file_path) if file_path.exists(): context.log.info(f"SKIPPING {filename} | FILE EXISTS\n") else: - context.log.info(f"DOWNLOAD {filename} | {count_message}\n") - ( - filename, - conda_package_stream, - ) = conda_package_streaming.url.conda_reader_for_url(url) - with file_path.open("wb") as f: - shutil.copyfileobj(conda_package_stream, f) - conda_package_handling.api.extract(file_path_str) + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_dir = pathlib.Path(tmp_dir) + file_path = tmp_dir / filename + file_path_str = str(file_path) + context.log.info(f"DOWNLOAD {filename} | {count_message}\n") + ( + filename, + conda_package_stream, + ) = conda_package_streaming.url.conda_reader_for_url(url) + with file_path.open("wb") as f: + shutil.copyfileobj(conda_package_stream, f) + conda_package_handling.api.extract(file_path_str) - # This code is needed to avoid failures when building in - # parallel while using the shared cache. - # - # There are tarballs that only have the info/index.json file - # and no info/repodata_record.json. The latter is used to - # interact with the cache, so _make_single_record from - # PackageCacheData would create the missing json file during - # the lockfile install action. - # - # The code that does that in conda is similar to the code - # below. However, there is an important difference. The - # code in conda would fail to read the url and return None - # here: - # - # url = self._urls_data.get_url(package_filename) - # - # And that would result in the channel field of the json - # file being set to "". This is a problem because - # the channel is used when querying cache entries, via - # match_individual from MatchSpec, which would always result - # in a mismatch because the proper channel value is - # different. - # - # That would make conda think that the package is not - # available in the cache, so it would try to download it - # outside of this action, where no locking is implemented. - # - # As of now, conda's cache is not atomic, so the same - # dependencies requested by different builds would overwrite - # each other causing random failures during the build - # process. - # - # To avoid this problem, the code below does what the code - # in conda does but also sets the url properly, which would - # make the channel match properly during the query process - # later. So no dependencies would be downloaded outside of - # this action and cache corruption is prevented. - # - # To illustrate, here's a diff of an old conda entry, which - # didn't work, versus the new one created by this action: - # - # --- /tmp/old.txt 2024-02-05 01:08:16.879751010 +0100 - # +++ /tmp/new.txt 2024-02-05 01:08:02.919319887 +0100 - # @@ -2,7 +2,7 @@ - # "arch": "x86_64", - # "build": "conda_forge", - # "build_number": 0, - # - "channel": "", - # + "channel": "https://conda.anaconda.org/conda-forge/linux-64", - # "constrains": [], - # "depends": [], - # "features": "", - # @@ -15,5 +15,6 @@ - # "subdir": "linux-64", - # "timestamp": 1578324546067, - # "track_features": "", - # + "url": "https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2", - # "version": "0.1" - # } - # - # Also see the comment above about the cache magic file. - # Without the magic file, cache queries would fail even if - # repodata_record.json files have proper channels specified. + # This code is needed to avoid failures when building in + # parallel while using the shared cache. + # + # There are tarballs that only have the info/index.json file + # and no info/repodata_record.json. The latter is used to + # interact with the cache, so _make_single_record from + # PackageCacheData would create the missing json file during + # the lockfile install action. + # + # The code that does that in conda is similar to the code + # below. However, there is an important difference. The + # code in conda would fail to read the url and return None + # here: + # + # url = self._urls_data.get_url(package_filename) + # + # And that would result in the channel field of the json + # file being set to "". This is a problem because + # the channel is used when querying cache entries, via + # match_individual from MatchSpec, which would always result + # in a mismatch because the proper channel value is + # different. + # + # That would make conda think that the package is not + # available in the cache, so it would try to download it + # outside of this action, where no locking is implemented. + # + # As of now, conda's cache is not atomic, so the same + # dependencies requested by different builds would overwrite + # each other causing random failures during the build + # process. + # + # To avoid this problem, the code below does what the code + # in conda does but also sets the url properly, which would + # make the channel match properly during the query process + # later. So no dependencies would be downloaded outside of + # this action and cache corruption is prevented. + # + # To illustrate, here's a diff of an old conda entry, which + # didn't work, versus the new one created by this action: + # + # --- /tmp/old.txt 2024-02-05 01:08:16.879751010 +0100 + # +++ /tmp/new.txt 2024-02-05 01:08:02.919319887 +0100 + # @@ -2,7 +2,7 @@ + # "arch": "x86_64", + # "build": "conda_forge", + # "build_number": 0, + # - "channel": "", + # + "channel": "https://conda.anaconda.org/conda-forge/linux-64", + # "constrains": [], + # "depends": [], + # "features": "", + # @@ -15,5 +15,6 @@ + # "subdir": "linux-64", + # "timestamp": 1578324546067, + # "track_features": "", + # + "url": "https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2", + # "version": "0.1" + # } + # + # Also see the comment above about the cache magic file. + # Without the magic file, cache queries would fail even if + # repodata_record.json files have proper channels specified. - extracted_dir = pathlib.Path(strip_pkg_extension(file_path_str)[0]) - # This file is used to parse cache records via PackageCacheRecord in conda - repodata_file = extracted_dir / "info" / "repodata_record.json" + extracted_dir = pathlib.Path( + strip_pkg_extension(file_path_str)[0] + ) + # This file is used to parse cache records via PackageCacheRecord in conda + repodata_file = extracted_dir / "info" / "repodata_record.json" - if not repodata_file.exists(): - raw_json_record = read_index_json(extracted_dir) - fn = os.path.basename(file_path_str) - md5 = package["hash"]["md5"] - size = getsize(file_path_str) + if not repodata_file.exists(): + raw_json_record = read_index_json(extracted_dir) + fn = os.path.basename(file_path_str) + md5 = package["hash"]["md5"] + size = getsize(file_path_str) - package_cache_record = PackageCacheRecord.from_objects( - raw_json_record, - url=url, - fn=fn, - md5=md5, - size=size, - package_tarball_full_path=file_path_str, - extracted_package_dir=str(extracted_dir), - ) + package_cache_record = PackageCacheRecord.from_objects( + raw_json_record, + url=url, + fn=fn, + md5=md5, + size=size, + package_tarball_full_path=file_path_str, + extracted_package_dir=str(extracted_dir), + ) - repodata_record = PackageRecord.from_objects( - package_cache_record - ) + repodata_record = PackageRecord.from_objects( + package_cache_record + ) + write_as_json_to_file(repodata_file, repodata_record) - write_as_json_to_file(repodata_file, repodata_record) + # This is to ensure _make_single_record in conda never + # sees the extracted package directory without our + # repodata_record file being there. Otherwise, conda + # would attempt to create the repodata file, with the + # channel field set to "", which would make the + # above code pointless. Using symlinks here would be + # better since those are atomic on Linux, but I don't + # want to create any permanent directories on the + # filesystem. + os.rename(file_path, pkgs_dir / file_path.name) + os.rename(extracted_dir, pkgs_dir / extracted_dir.name) From a3b8fed720934cee2259759c17953c638282c53c Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Wed, 7 Feb 2024 17:20:42 +0100 Subject: [PATCH 29/60] Try swapping the `rename` calls --- .../conda_store_server/action/download_packages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conda-store-server/conda_store_server/action/download_packages.py b/conda-store-server/conda_store_server/action/download_packages.py index 1ef2b1c89..1fccfe20f 100644 --- a/conda-store-server/conda_store_server/action/download_packages.py +++ b/conda-store-server/conda_store_server/action/download_packages.py @@ -181,5 +181,5 @@ def action_fetch_and_extract_conda_packages( # better since those are atomic on Linux, but I don't # want to create any permanent directories on the # filesystem. - os.rename(file_path, pkgs_dir / file_path.name) os.rename(extracted_dir, pkgs_dir / extracted_dir.name) + os.rename(file_path, pkgs_dir / file_path.name) From 413a35adb3f14d0840282c93cbf0faf7da5baa23 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Wed, 7 Feb 2024 18:19:52 +0100 Subject: [PATCH 30/60] Use the `shutil` module for copying --- .../conda_store_server/action/download_packages.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/conda-store-server/conda_store_server/action/download_packages.py b/conda-store-server/conda_store_server/action/download_packages.py index 1fccfe20f..6416e8e23 100644 --- a/conda-store-server/conda_store_server/action/download_packages.py +++ b/conda-store-server/conda_store_server/action/download_packages.py @@ -181,5 +181,9 @@ def action_fetch_and_extract_conda_packages( # better since those are atomic on Linux, but I don't # want to create any permanent directories on the # filesystem. - os.rename(extracted_dir, pkgs_dir / extracted_dir.name) - os.rename(file_path, pkgs_dir / file_path.name) + shutil.copytree( + extracted_dir, + pkgs_dir / extracted_dir.name, + dirs_exist_ok=True, + ) + shutil.copyfile(file_path, pkgs_dir / file_path.name) From b89863b1d918a342ec5a67011191cae047dbf86a Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Wed, 7 Feb 2024 18:51:45 +0100 Subject: [PATCH 31/60] Use `dest_dir` when calling `extract` --- .../conda_store_server/action/download_packages.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/conda-store-server/conda_store_server/action/download_packages.py b/conda-store-server/conda_store_server/action/download_packages.py index 6416e8e23..51b81b97c 100644 --- a/conda-store-server/conda_store_server/action/download_packages.py +++ b/conda-store-server/conda_store_server/action/download_packages.py @@ -72,6 +72,9 @@ def action_fetch_and_extract_conda_packages( tmp_dir = pathlib.Path(tmp_dir) file_path = tmp_dir / filename file_path_str = str(file_path) + extracted_dir = pathlib.Path( + strip_pkg_extension(file_path_str)[0] + ) context.log.info(f"DOWNLOAD {filename} | {count_message}\n") ( filename, @@ -79,7 +82,9 @@ def action_fetch_and_extract_conda_packages( ) = conda_package_streaming.url.conda_reader_for_url(url) with file_path.open("wb") as f: shutil.copyfileobj(conda_package_stream, f) - conda_package_handling.api.extract(file_path_str) + conda_package_handling.api.extract( + file_path_str, dest_dir=extracted_dir + ) # This code is needed to avoid failures when building in # parallel while using the shared cache. @@ -145,9 +150,6 @@ def action_fetch_and_extract_conda_packages( # Without the magic file, cache queries would fail even if # repodata_record.json files have proper channels specified. - extracted_dir = pathlib.Path( - strip_pkg_extension(file_path_str)[0] - ) # This file is used to parse cache records via PackageCacheRecord in conda repodata_file = extracted_dir / "info" / "repodata_record.json" From 61b4ec088ccfb88ecffc815392850a9e1aef57d7 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Wed, 7 Feb 2024 18:53:40 +0100 Subject: [PATCH 32/60] Add a helper string variable --- .../conda_store_server/action/download_packages.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conda-store-server/conda_store_server/action/download_packages.py b/conda-store-server/conda_store_server/action/download_packages.py index 51b81b97c..3ac94c1dc 100644 --- a/conda-store-server/conda_store_server/action/download_packages.py +++ b/conda-store-server/conda_store_server/action/download_packages.py @@ -75,6 +75,7 @@ def action_fetch_and_extract_conda_packages( extracted_dir = pathlib.Path( strip_pkg_extension(file_path_str)[0] ) + extracted_dir_str = str(extracted_dir) context.log.info(f"DOWNLOAD {filename} | {count_message}\n") ( filename, @@ -166,7 +167,7 @@ def action_fetch_and_extract_conda_packages( md5=md5, size=size, package_tarball_full_path=file_path_str, - extracted_package_dir=str(extracted_dir), + extracted_package_dir=extracted_dir_str, ) repodata_record = PackageRecord.from_objects( From 48f24a6322dfd653541d5a2dfd9e4dbd9df4d8ab Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Wed, 7 Feb 2024 20:52:59 +0100 Subject: [PATCH 33/60] Set `symlinks=True` --- .../conda_store_server/action/download_packages.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conda-store-server/conda_store_server/action/download_packages.py b/conda-store-server/conda_store_server/action/download_packages.py index 3ac94c1dc..3076cc873 100644 --- a/conda-store-server/conda_store_server/action/download_packages.py +++ b/conda-store-server/conda_store_server/action/download_packages.py @@ -187,6 +187,7 @@ def action_fetch_and_extract_conda_packages( shutil.copytree( extracted_dir, pkgs_dir / extracted_dir.name, + symlinks=True, dirs_exist_ok=True, ) shutil.copyfile(file_path, pkgs_dir / file_path.name) From e41c6d8fff88061814d4bc5120db8dbb5a1b1446 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Wed, 7 Feb 2024 21:02:38 +0100 Subject: [PATCH 34/60] Try using `move` and `rmtree` --- .../conda_store_server/action/download_packages.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/conda-store-server/conda_store_server/action/download_packages.py b/conda-store-server/conda_store_server/action/download_packages.py index 3076cc873..41a29ce29 100644 --- a/conda-store-server/conda_store_server/action/download_packages.py +++ b/conda-store-server/conda_store_server/action/download_packages.py @@ -184,10 +184,6 @@ def action_fetch_and_extract_conda_packages( # better since those are atomic on Linux, but I don't # want to create any permanent directories on the # filesystem. - shutil.copytree( - extracted_dir, - pkgs_dir / extracted_dir.name, - symlinks=True, - dirs_exist_ok=True, - ) - shutil.copyfile(file_path, pkgs_dir / file_path.name) + shutil.rmtree(pkgs_dir / extracted_dir.name, ignore_errors=True) + shutil.move(extracted_dir, pkgs_dir / extracted_dir.name) + shutil.move(file_path, pkgs_dir / file_path.name) From 83bdce8d828dba0b60938a7fc30095fbfd4c6e82 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Wed, 7 Feb 2024 22:11:19 +0100 Subject: [PATCH 35/60] Increase `threshold` --- tests/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index 06c408473..44ac807b0 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -621,7 +621,7 @@ def get_logs(): # be located, and compares it to a certain threshold, which is unlikely to # be reached based on how long it takes a single build to run # non-concurrently on average: - threshold = 5 + threshold = 10 quartiles = statistics.quantiles(build_deltas, method='inclusive') print("build_deltas", build_deltas) print("stats", min(build_deltas), quartiles) From 93cddf4d947cf054d45ed45b567be3222135d60e Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Thu, 8 Feb 2024 22:09:08 +0100 Subject: [PATCH 36/60] Remove the `repodata_file.exists()` check --- .../action/download_packages.py | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/conda-store-server/conda_store_server/action/download_packages.py b/conda-store-server/conda_store_server/action/download_packages.py index 41a29ce29..e62885539 100644 --- a/conda-store-server/conda_store_server/action/download_packages.py +++ b/conda-store-server/conda_store_server/action/download_packages.py @@ -154,26 +154,25 @@ def action_fetch_and_extract_conda_packages( # This file is used to parse cache records via PackageCacheRecord in conda repodata_file = extracted_dir / "info" / "repodata_record.json" - if not repodata_file.exists(): - raw_json_record = read_index_json(extracted_dir) - fn = os.path.basename(file_path_str) - md5 = package["hash"]["md5"] - size = getsize(file_path_str) + raw_json_record = read_index_json(extracted_dir) + fn = os.path.basename(file_path_str) + md5 = package["hash"]["md5"] + size = getsize(file_path_str) - package_cache_record = PackageCacheRecord.from_objects( - raw_json_record, - url=url, - fn=fn, - md5=md5, - size=size, - package_tarball_full_path=file_path_str, - extracted_package_dir=extracted_dir_str, - ) + package_cache_record = PackageCacheRecord.from_objects( + raw_json_record, + url=url, + fn=fn, + md5=md5, + size=size, + package_tarball_full_path=file_path_str, + extracted_package_dir=extracted_dir_str, + ) - repodata_record = PackageRecord.from_objects( - package_cache_record - ) - write_as_json_to_file(repodata_file, repodata_record) + repodata_record = PackageRecord.from_objects( + package_cache_record + ) + write_as_json_to_file(repodata_file, repodata_record) # This is to ensure _make_single_record in conda never # sees the extracted package directory without our From 8414d17b2d647e298fa834e1063f0abfc33b8254 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Fri, 9 Feb 2024 00:27:10 +0100 Subject: [PATCH 37/60] Update the comment --- .../conda_store_server/action/download_packages.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/conda-store-server/conda_store_server/action/download_packages.py b/conda-store-server/conda_store_server/action/download_packages.py index e62885539..93273f3ba 100644 --- a/conda-store-server/conda_store_server/action/download_packages.py +++ b/conda-store-server/conda_store_server/action/download_packages.py @@ -90,11 +90,14 @@ def action_fetch_and_extract_conda_packages( # This code is needed to avoid failures when building in # parallel while using the shared cache. # - # There are tarballs that only have the info/index.json file - # and no info/repodata_record.json. The latter is used to - # interact with the cache, so _make_single_record from - # PackageCacheData would create the missing json file during - # the lockfile install action. + # Package tarballs contain the info/index.json file, + # which is used by conda to create the + # info/repodata_record.json file. The latter is used to + # interact with the cache. _make_single_record from + # PackageCacheData would create the repodata json file + # if it's not present, which would happen in conda-store + # during the lockfile install action. The repodata file + # is not created if it already exists. # # The code that does that in conda is similar to the code # below. However, there is an important difference. The From 3ac06bf1e7bbc05d02607fe6b862382e16ce7e42 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 12 Feb 2024 17:17:54 +0100 Subject: [PATCH 38/60] Add `conda-package-streaming` to yaml files --- conda-store-server/environment-dev.yaml | 1 + conda-store-server/environment-macos-dev.yaml | 1 + conda-store-server/environment-windows-dev.yaml | 1 + conda-store-server/environment.yaml | 1 + 4 files changed, 4 insertions(+) diff --git a/conda-store-server/environment-dev.yaml b/conda-store-server/environment-dev.yaml index fe30479a0..b7dc836ad 100644 --- a/conda-store-server/environment-dev.yaml +++ b/conda-store-server/environment-dev.yaml @@ -12,6 +12,7 @@ dependencies: - conda-pack - conda-lock >=1.0.5 - conda-package-handling + - conda-package-streaming # web server - celery - flower diff --git a/conda-store-server/environment-macos-dev.yaml b/conda-store-server/environment-macos-dev.yaml index 6ece10f5d..3cab8855d 100644 --- a/conda-store-server/environment-macos-dev.yaml +++ b/conda-store-server/environment-macos-dev.yaml @@ -12,6 +12,7 @@ dependencies: - conda-lock >=1.0.5 - mamba - conda-package-handling + - conda-package-streaming # web server - celery - flower diff --git a/conda-store-server/environment-windows-dev.yaml b/conda-store-server/environment-windows-dev.yaml index 6ece10f5d..3cab8855d 100644 --- a/conda-store-server/environment-windows-dev.yaml +++ b/conda-store-server/environment-windows-dev.yaml @@ -12,6 +12,7 @@ dependencies: - conda-lock >=1.0.5 - mamba - conda-package-handling + - conda-package-streaming # web server - celery - flower diff --git a/conda-store-server/environment.yaml b/conda-store-server/environment.yaml index f3eaa0614..f3b8e40f9 100644 --- a/conda-store-server/environment.yaml +++ b/conda-store-server/environment.yaml @@ -11,6 +11,7 @@ dependencies: - conda-pack - conda-lock >=1.0.5 - conda-package-handling + - conda-package-streaming # web server - celery - flower From e55f8777f3fb9ddcf55119946bac3f86b96adad9 Mon Sep 17 00:00:00 2001 From: Chuck McAndrew <6248903+dcmcand@users.noreply.github.com> Date: Mon, 19 Feb 2024 14:44:27 -0500 Subject: [PATCH 39/60] respond to feedback --- .../tests/user_journeys/README.md | 66 +++++--- .../tests/user_journeys/helpers/helpers.py | 145 ------------------ .../tests/user_journeys/test_user_journeys.py | 33 ++-- .../tests/user_journeys/utils/api_utils.py | 136 ++++++++++++++++ .../tests/user_journeys/utils/time_utils.py | 20 +++ 5 files changed, 218 insertions(+), 182 deletions(-) delete mode 100644 conda-store-server/tests/user_journeys/helpers/helpers.py create mode 100644 conda-store-server/tests/user_journeys/utils/api_utils.py create mode 100644 conda-store-server/tests/user_journeys/utils/time_utils.py diff --git a/conda-store-server/tests/user_journeys/README.md b/conda-store-server/tests/user_journeys/README.md index 8843a6dcc..5cd46f8f1 100644 --- a/conda-store-server/tests/user_journeys/README.md +++ b/conda-store-server/tests/user_journeys/README.md @@ -1,59 +1,83 @@ # User Journey Tests -This repository contains user journey tests for the API. User journey tests are end-to-end tests that simulate real user scenarios to ensure the API functions correctly in different scenarios. +This repository contains user journey tests for the API. User journey tests +are end-to-end tests that simulate real user scenarios to ensure the API +functions correctly in different scenarios. -These tests will use the high privileged token to create a randomly named namespace (using a UUID) to prevent conflicts with existing namespaces. At the end of the test, it will delete any environments created and then delete the namespace. +These tests will use the high privileged token to create a randomly named +namespace (using a UUID) to prevent conflicts with existing namespaces. At the +end of the test, it will delete any environments created and then delete the +namespace. ## Prerequisites -These tests are blackbox tests and need a running server to test against. This can be a local conda-store instance started using docker compose, or a remote instance. You will need the base url of the server and a token for an admin user to run these tests. +These tests are blackbox tests and need a running server to test against. This +can be a local conda-store instance started using docker compose, or a remote +instance. You will need the base url of the server and a token for an admin +user to run these tests. ## Setup ### Local setup -To run locally using docker compose all you need to do is start conda store. +To run locally using docker compose all you need to do is start conda-store. -From the project base run `docker compose up` +From the project base, run `docker compose up`. ### Remote setup -To run these tests against a remote server, you need to set 2 environmental variables +To run these tests against a remote server, you need to set 2 environment +variables: -1. `CONDA_STORE_BASE_URL` - this is the base url of your conda server. +1. `CONDA_STORE_BASE_URL` - this is the base url of your conda-store-server. - For example, if you access your conda-store-server at `https://example.com` you would run `export CONDA_STORE_BASE_URL='https://example.com`. + For example, if you access your conda-store-server at `https://example.com`, + you would run `export CONDA_STORE_BASE_URL='https://example.com'`. - Do not include the `/conda-store/` suffix. + **Do not include the `/conda-store/` suffix.** - Do include the port if needed. For example: `export CONDA_STORE_BASE_URL='http://localhost:8080` + Do include the port if needed. + For example: `export CONDA_STORE_BASE_URL='http://localhost:8080'`. 2. `CONDA_STORE_TOKEN` - this should be the token of an admin user. - This token will let the tests create the tokens, permissions, namespaces, and environments needed for these tests to run successfully + This token will let the tests create the tokens, permissions, namespaces, + and environments needed for these tests to run successfully. - To generate a token, while logged in as a high privileged user, go to `https://your-conda-store-url/conda-store/admin/user/` and click on `Create token`. + To generate a token, while logged in as a high-privileged user, go to + `https:///conda-store/admin/user/` and click on + `Create token`. - Copy that token value and export it `export CONDA_STORE_TOKEN='my_token_value'` + Copy that token value and export it: + `export CONDA_STORE_TOKEN='my_token_value'`. ## Running the tests -To run the tests run `pytest -m user_journey` from the `conda-store-server` directory +To run the tests run `pytest -m user_journey` from the `conda-store-server` +directory. ## Current scenarios tested -* An admin user can create a simple environment in a shared namespace and once the environment is built, can delete the environment +* An admin user can create a simple environment in a shared namespace and, once + the environment is built, can delete the environment. ## Planned scenarios to be implemented -* A admin can create a complex environment in a shared namespace and once the environment is built, can delete the environment +* An admin can create a complex environment in a shared namespace and, once the + environment is built, can delete the environment -* A developer can create a simple environment in a shared namespace and once the environment is built, can delete the environment +* A developer can create a simple environment in a shared namespace and, once + the environment is built, can delete the environment -* A developer can create a complex environment in a shared namespace and once the environment is built, can delete the environment +* A developer can create a complex environment in a shared namespace and, once + the environment is built, can delete the environment -* A developer can create a create environment in a shared namespace and once the environment is built, can modify the environment, then can mark the first build as active. +* A developer can create an environment in a shared namespace and, once the + environment is built, can modify the environment, then can mark the first + build as active -* A developer can create a simple environment in a shared namespace and once the environment is built, can get the lockfile for the environment +* A developer can create a simple environment in a shared namespace and, once + the environment is built, can get the lockfile for the environment -* A developer can create a failing environment in a shared namespace and once the environment has failed, can get the logs for the failed build +* A developer can create a failing environment in a shared namespace and, once + the environment has failed, can get the logs for the failed build. diff --git a/conda-store-server/tests/user_journeys/helpers/helpers.py b/conda-store-server/tests/user_journeys/helpers/helpers.py deleted file mode 100644 index 0576d159a..000000000 --- a/conda-store-server/tests/user_journeys/helpers/helpers.py +++ /dev/null @@ -1,145 +0,0 @@ -"""Helper functions for user journeys.""" -from datetime import datetime, timedelta, timezone -from enum import Enum - -import requests - -TIMEOUT = 10 - - -class APIBuildStatus(Enum): - """Enum for API build status.""" - COMPLETED = "COMPLETED" - FAILED = "FAILED" - QUEUED = "QUEUED" - BUILDING = "BUILDING" - - -class APIHelper: - """ - Helper class for making requests to the API. - These methods are used to build tests for user journeys - """ - - def __init__(self, - base_url: str, token: str = "", - username: str = "username", password: str = "password" - ) -> None: - self.base_url = base_url - self.token = token - if not token: - # Log in if no token is provided to set the token - self._login(username, password) - - def make_request( - self, endpoint: str, method: str = "GET", json_data: dict = None, - headers: dict = None, timeout: int = TIMEOUT) -> requests.Response: - """ Make a request to the API. """ - url = f"{self.base_url}/{endpoint}" - headers = headers or {} - headers["Authorization"] = f"Bearer {self.token}" - response = requests.request( - method, url, json=json_data, headers=headers, timeout=timeout) - response.raise_for_status() - return response - - def _login(self, username: str, password: str) -> None: - """ Log in to the API and set an access token.""" - json_data = {"username": username, "password": password} - response = requests.post( - f"{self.base_url}/login", json=json_data, timeout=TIMEOUT) - cookies = response.cookies.get_dict() - token_response = requests.post( - f"{self.base_url}/api/v1/token", cookies=cookies, timeout=TIMEOUT) - data = token_response.json() - self.token = data["data"]["token"] - - -def get_current_time() -> datetime: - """ Get the current time. """ - return datetime.now(timezone.utc) - - -def get_time_in_future(hours: int) -> datetime: - """ Get the time in the future.""" - current_time = get_current_time() - future_time = current_time + timedelta(hours=hours) - return future_time - - -def get_iso8601_time(hours: int) -> str: - """ Get the time in the future in ISO 8601 format. """ - future_time = get_time_in_future(hours) - iso_format = future_time.isoformat() - return iso_format - - -def create_namespace(api: APIHelper, namespace: str) -> requests.Response: - """ Create a namespace. """ - return api.make_request( - f"api/v1/namespace/{namespace}", method="POST") - - -def create_token( - api: APIHelper, namespace: str, role: str, - default_namespace: str = "default" - ) -> requests.Response: - """ Create a token with a specified role in a specified namespace. """ - one_hour_in_future = get_time_in_future(1) - json_data = { - "primary_namespace": default_namespace, - "expiration": one_hour_in_future.isoformat(), - "role_bindings": { - f"{namespace}/*": [role] - } - } - return api.make_request( - "api/v1/token", method="POST", json_data=json_data) - - -def create_environment( - api: APIHelper, namespace: str, specification_path: str - ) -> requests.Response: - """ - Create an environment. - The environment specification is read - from a conda environment.yaml file. - """ - with open(specification_path, "r", encoding="utf-8") as file: - specification_content = file.read() - - json_data = { - "namespace": namespace, - "specification": specification_content - } - - return api.make_request( - "api/v1/specification", method="POST", json_data=json_data) - - -def wait_for_successful_build( - api: APIHelper, build_id: str - ) -> requests.Response: - """ Wait for a build to complete.""" - status = APIBuildStatus.QUEUED.value - while status != APIBuildStatus.COMPLETED.value: - response = api.make_request( - f"api/v1/build/{build_id}", method="GET") - status = response.json()["data"]["status"] - if status == APIBuildStatus.FAILED.value: - raise AssertionError("Build failed") - return response - - -def delete_environment( - api: APIHelper, namespace: str, environment_name: str - ) -> requests.Response: - """ Delete an environment.""" - return api.make_request( - f"api/v1/environment/{namespace}/{environment_name}", - method="DELETE") - - -def delete_namespace(api: APIHelper, namespace: str) -> requests.Response: - """ Delete a namespace.""" - return api.make_request(f"api/v1/namespace/{namespace}", method="DELETE") diff --git a/conda-store-server/tests/user_journeys/test_user_journeys.py b/conda-store-server/tests/user_journeys/test_user_journeys.py index ea96a15d7..54cd90406 100644 --- a/conda-store-server/tests/user_journeys/test_user_journeys.py +++ b/conda-store-server/tests/user_journeys/test_user_journeys.py @@ -1,9 +1,8 @@ """User journey tests for the API.""" import os -import uuid import pytest -from helpers import helpers as h +import utils.api_utils as utils @pytest.fixture(scope="session") @@ -20,23 +19,25 @@ def token(base_url) -> str: @pytest.mark.user_journey -@pytest.mark.parametrize("filename", [ - ("tests/user_journeys/test_data/simple_environment.yaml"), - # ("test_data/complex-environment.yaml") - ]) -def test_admin_user_can_create_environment(base_url, token, filename) -> None: +@pytest.mark.parametrize( + "specification_path", + [ + ("tests/user_journeys/test_data/simple_environment.yaml"), + ], +) +def test_admin_user_can_create_environment( + base_url: str, token: str, specification_path: str +) -> None: """Test that an admin user can create an environment.""" - namespace = uuid.uuid4().hex # Generate a random namespace - print(os.path.abspath(filename)) - api = h.APIHelper(base_url=base_url, token=token) - specification_path = f"{filename}" - response = h.create_environment(api, namespace, specification_path) - assert response.status_code == 200 + namespace = utils.gen_random_namespace() + api = utils.API(base_url=base_url, token=token) + utils.create_namespace(api, namespace) + response = utils.create_environment(api, namespace, specification_path) data = response.json()["data"] assert "build_id" in data build_id = data["build_id"] assert build_id is not None - build = h.wait_for_successful_build(api, build_id) + build = utils.wait_for_successful_build(api, build_id) environment_name = build.json()["data"]["specification"]["name"] - h.delete_environment(api, namespace, environment_name) - h.delete_namespace(api, namespace) + utils.delete_environment(api, namespace, environment_name) + utils.delete_namespace(api, namespace) diff --git a/conda-store-server/tests/user_journeys/utils/api_utils.py b/conda-store-server/tests/user_journeys/utils/api_utils.py new file mode 100644 index 000000000..6d1b2c750 --- /dev/null +++ b/conda-store-server/tests/user_journeys/utils/api_utils.py @@ -0,0 +1,136 @@ +"""Helper functions for user journeys.""" +from enum import Enum +import time + +import uuid + +import requests +import time_utils + +TIMEOUT = 10 + + +class BuildStatus(Enum): + """Enum for API build status.""" + QUEUED = "QUEUED" + BUILDING = "BUILDING" + COMPLETED = "COMPLETED" + FAILED = "FAILED" + + +class API: + """ + Helper class for making requests to the API. + These methods are used to build tests for user journeys + """ + + def __init__(self, + base_url: str, token: str = "", + username: str = "username", password: str = "password" + ) -> None: + self.base_url = base_url + self.token = token + if not token: + # Log in if no token is provided to set the token + self._login(username, password) + + def _make_request( + self, endpoint: str, method: str = "GET", json_data: dict = None, + headers: dict = None, timeout: int = TIMEOUT) -> requests.Response: + """ Make a request to the API. """ + url = f"{self.base_url}/{endpoint}" + headers = headers or {} + headers["Authorization"] = f"Bearer {self.token}" + response = requests.request( + method, url, json=json_data, headers=headers, timeout=timeout) + response.raise_for_status() + return response + + def _login(self, username: str, password: str) -> None: + """ Log in to the API and set an access token.""" + json_data = {"username": username, "password": password} + response = requests.post( + f"{self.base_url}/login", json=json_data, timeout=TIMEOUT) + cookies = response.cookies.get_dict() + token_response = requests.post( + f"{self.base_url}/api/v1/token", cookies=cookies, timeout=TIMEOUT) + data = token_response.json() + self.token = data["data"]["token"] + + def create_namespace(self, namespace: str) -> requests.Response: + """ Create a namespace.""" + return self._make_request( + f"api/v1/namespace/{namespace}", method="POST") + + def create_token( + self, namespace: str, role: str, + default_namespace: str = "default" + ) -> requests.Response: + """ Create a token with a specified role in a specified namespace. """ + one_hour_in_future = time_utils.get_time_in_future(1) + json_data = { + "primary_namespace": default_namespace, + "expiration": one_hour_in_future.isoformat(), + "role_bindings": { + f"{namespace}/*": [role] + } + } + return self._make_request( + "api/v1/token", method="POST", json_data=json_data) + + def create_environment( + self, namespace: str, specification_path: str + ) -> requests.Response: + """ + Create an environment. + The environment specification is read + from a conda environment.yaml file. + """ + with open(specification_path, "r", encoding="utf-8") as file: + specification_content = file.read() + + json_data = { + "namespace": namespace, + "specification": specification_content + } + + return self._make_request( + "api/v1/specification", method="POST", json_data=json_data) + + def wait_for_successful_build( + self, build_id: str, + max_iterations: int = 100, sleep_time: int = 5 + ) -> requests.Response: + """ Wait for a build to complete.""" + status = BuildStatus.QUEUED.value + iterations = 0 + while status != BuildStatus.COMPLETED.value: + if iterations > max_iterations: + raise TimeoutError("Timed out waiting for build") + response = self._make_request( + f"api/v1/build/{build_id}", method="GET") + status = response.json()["data"]["status"] + if status == BuildStatus.FAILED.value: + raise AssertionError("Build failed") + iterations += 1 + time.sleep(sleep_time) + return response + + def delete_environment( + self, namespace: str, environment_name: str + ) -> requests.Response: + """ Delete an environment.""" + return self._make_request( + f"api/v1/environment/{namespace}/{environment_name}", + method="DELETE") + + def delete_namespace(self, namespace: str) -> requests.Response: + """ Delete a namespace.""" + return self._make_request( + f"api/v1/namespace/{namespace}", method="DELETE" + ) + + @staticmethod + def gen_random_namespace() -> str: + """ Generate a random namespace.""" + return uuid.uuid4().hex diff --git a/conda-store-server/tests/user_journeys/utils/time_utils.py b/conda-store-server/tests/user_journeys/utils/time_utils.py new file mode 100644 index 000000000..d61c203c8 --- /dev/null +++ b/conda-store-server/tests/user_journeys/utils/time_utils.py @@ -0,0 +1,20 @@ +from datetime import datetime, timedelta, timezone + + +def get_current_time() -> datetime: + """ Get the current time. """ + return datetime.now(timezone.utc) + + +def get_time_in_future(hours: int) -> datetime: + """ Get the time in the future.""" + current_time = get_current_time() + future_time = current_time + timedelta(hours=hours) + return future_time + + +def get_iso8601_time(hours: int) -> str: + """ Get the time in the future in ISO 8601 format. """ + future_time = get_time_in_future(hours) + iso_format = future_time.isoformat() + return iso_format From 8c652b1ef852365c0b5b7aea7d812ff8ecca3788 Mon Sep 17 00:00:00 2001 From: Chuck McAndrew <6248903+dcmcand@users.noreply.github.com> Date: Mon, 19 Feb 2024 15:43:03 -0500 Subject: [PATCH 40/60] lint --- .../tests/user_journeys/utils/api_utils.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/conda-store-server/tests/user_journeys/utils/api_utils.py b/conda-store-server/tests/user_journeys/utils/api_utils.py index 6d1b2c750..5f40147a4 100644 --- a/conda-store-server/tests/user_journeys/utils/api_utils.py +++ b/conda-store-server/tests/user_journeys/utils/api_utils.py @@ -1,8 +1,7 @@ """Helper functions for user journeys.""" -from enum import Enum import time - import uuid +from enum import Enum import requests import time_utils @@ -67,10 +66,9 @@ def create_token( default_namespace: str = "default" ) -> requests.Response: """ Create a token with a specified role in a specified namespace. """ - one_hour_in_future = time_utils.get_time_in_future(1) json_data = { "primary_namespace": default_namespace, - "expiration": one_hour_in_future.isoformat(), + "expiration": time_utils.get_iso8601_time(1), "role_bindings": { f"{namespace}/*": [role] } @@ -110,8 +108,7 @@ def wait_for_successful_build( response = self._make_request( f"api/v1/build/{build_id}", method="GET") status = response.json()["data"]["status"] - if status == BuildStatus.FAILED.value: - raise AssertionError("Build failed") + assert status != BuildStatus.FAILED.value, "Build failed" iterations += 1 time.sleep(sleep_time) return response From a748b34789e0eb65de9a0928e822035ecdb62890 Mon Sep 17 00:00:00 2001 From: Chuck McAndrew <6248903+dcmcand@users.noreply.github.com> Date: Mon, 19 Feb 2024 16:12:02 -0500 Subject: [PATCH 41/60] fix test --- .../tests/user_journeys/test_user_journeys.py | 12 ++++++------ .../tests/user_journeys/utils/__init__.py | 0 .../tests/user_journeys/utils/api_utils.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 conda-store-server/tests/user_journeys/utils/__init__.py diff --git a/conda-store-server/tests/user_journeys/test_user_journeys.py b/conda-store-server/tests/user_journeys/test_user_journeys.py index 54cd90406..1d4ee7d23 100644 --- a/conda-store-server/tests/user_journeys/test_user_journeys.py +++ b/conda-store-server/tests/user_journeys/test_user_journeys.py @@ -29,15 +29,15 @@ def test_admin_user_can_create_environment( base_url: str, token: str, specification_path: str ) -> None: """Test that an admin user can create an environment.""" - namespace = utils.gen_random_namespace() + namespace = utils.API.gen_random_namespace() api = utils.API(base_url=base_url, token=token) - utils.create_namespace(api, namespace) - response = utils.create_environment(api, namespace, specification_path) + api.create_namespace(namespace) + response = api.create_environment(namespace, specification_path) data = response.json()["data"] assert "build_id" in data build_id = data["build_id"] assert build_id is not None - build = utils.wait_for_successful_build(api, build_id) + build = api.wait_for_successful_build(build_id) environment_name = build.json()["data"]["specification"]["name"] - utils.delete_environment(api, namespace, environment_name) - utils.delete_namespace(api, namespace) + api.delete_environment(namespace, environment_name) + api.delete_namespace(namespace) diff --git a/conda-store-server/tests/user_journeys/utils/__init__.py b/conda-store-server/tests/user_journeys/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/conda-store-server/tests/user_journeys/utils/api_utils.py b/conda-store-server/tests/user_journeys/utils/api_utils.py index 5f40147a4..3968e0c41 100644 --- a/conda-store-server/tests/user_journeys/utils/api_utils.py +++ b/conda-store-server/tests/user_journeys/utils/api_utils.py @@ -4,7 +4,7 @@ from enum import Enum import requests -import time_utils +import utils.time_utils as time_utils TIMEOUT = 10 From 8da5fcb74230b6eb3288c4f21579aa43ee020c44 Mon Sep 17 00:00:00 2001 From: Chuck McAndrew <6248903+dcmcand@users.noreply.github.com> Date: Wed, 21 Feb 2024 21:47:23 -0500 Subject: [PATCH 42/60] Update conda-store-server/tests/user_journeys/README.md Co-authored-by: Nikita Karetnikov --- conda-store-server/tests/user_journeys/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conda-store-server/tests/user_journeys/README.md b/conda-store-server/tests/user_journeys/README.md index 5cd46f8f1..aec8c1160 100644 --- a/conda-store-server/tests/user_journeys/README.md +++ b/conda-store-server/tests/user_journeys/README.md @@ -4,7 +4,7 @@ This repository contains user journey tests for the API. User journey tests are end-to-end tests that simulate real user scenarios to ensure the API functions correctly in different scenarios. -These tests will use the high privileged token to create a randomly named +These tests will use the high-privileged token to create a randomly-named namespace (using a UUID) to prevent conflicts with existing namespaces. At the end of the test, it will delete any environments created and then delete the namespace. From 46284686944fa2ec3a4f1af97abbb3b75ea4c78f Mon Sep 17 00:00:00 2001 From: Chuck McAndrew <6248903+dcmcand@users.noreply.github.com> Date: Wed, 21 Feb 2024 21:47:38 -0500 Subject: [PATCH 43/60] Update conda-store-server/tests/user_journeys/README.md Co-authored-by: Nikita Karetnikov --- conda-store-server/tests/user_journeys/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conda-store-server/tests/user_journeys/README.md b/conda-store-server/tests/user_journeys/README.md index aec8c1160..7e0dbceb6 100644 --- a/conda-store-server/tests/user_journeys/README.md +++ b/conda-store-server/tests/user_journeys/README.md @@ -53,7 +53,7 @@ variables: ## Running the tests -To run the tests run `pytest -m user_journey` from the `conda-store-server` +To run the tests, run `pytest -m user_journey` from the `conda-store-server` directory. ## Current scenarios tested From 94d82dc0245e8b428d1ae715845890f5cd181219 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Tue, 27 Feb 2024 13:27:56 +0100 Subject: [PATCH 44/60] MAINT - Run linter on tests and docs (#770) --- .pre-commit-config.yaml | 4 +- conda-store-server/tests/conftest.py | 29 +++-- conda-store-server/tests/test_actions.py | 98 ++++++++++------- conda-store-server/tests/test_auth.py | 76 +++++++------ conda-store-server/tests/test_db_api.py | 51 ++++++--- conda-store-server/tests/test_server.py | 51 +++++---- conda-store-server/tests/test_usage.py | 5 +- .../tests/user_journeys/utils/api_utils.py | 103 +++++++++--------- .../tests/user_journeys/utils/time_utils.py | 6 +- tests/conftest.py | 11 +- tests/test_api.py | 84 ++++++++------ tests/test_playwright.py | 5 +- 12 files changed, 305 insertions(+), 218 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 64c2419e7..164a8bf16 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,14 +13,14 @@ repos: rev: 23.9.1 hooks: - id: black - exclude: "examples|tests|docs" + exclude: "examples|tests/assets" - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. rev: "v0.0.289" hooks: - id: ruff - exclude: "examples|tests|docs" + exclude: "examples|tests/assets" args: ["--fix"] - repo: https://github.com/pycqa/isort diff --git a/conda-store-server/tests/conftest.py b/conda-store-server/tests/conftest.py index b2cfc9b47..32ed0bda5 100644 --- a/conda-store-server/tests/conftest.py +++ b/conda-store-server/tests/conftest.py @@ -1,5 +1,4 @@ import datetime -import os import pathlib import sys @@ -7,18 +6,32 @@ import yaml from fastapi.testclient import TestClient -from conda_store_server import action, api, app, dbutil, schema, storage, testing, utils # isort:skip +from conda_store_server import ( # isort:skip + action, + api, + app, + dbutil, + schema, + storage, + testing, + utils, +) + from conda_store_server.server import app as server_app # isort:skip @pytest.fixture def celery_config(tmp_path, conda_store): config = conda_store.celery_config - config["traitlets"] = {"CondaStore": { - "database_url": conda_store.database_url, - "store_directory": conda_store.store_directory, - }} - config["beat_schedule_filename"] = str(tmp_path / ".conda-store" / "celerybeat-schedule") + config["traitlets"] = { + "CondaStore": { + "database_url": conda_store.database_url, + "store_directory": conda_store.store_directory, + } + } + config["beat_schedule_filename"] = str( + tmp_path / ".conda-store" / "celerybeat-schedule" + ) return config @@ -38,7 +51,7 @@ def conda_store_config(tmp_path, request): CondaStore=dict( storage_class=storage.LocalStorage, store_directory=str(store_directory), - database_url=f"sqlite:///{filename}?check_same_thread=False" + database_url=f"sqlite:///{filename}?check_same_thread=False", ) ) diff --git a/conda-store-server/tests/test_actions.py b/conda-store-server/tests/test_actions.py index df5d91a65..02f710481 100644 --- a/conda-store-server/tests/test_actions.py +++ b/conda-store-server/tests/test_actions.py @@ -1,11 +1,9 @@ import asyncio import datetime -import os import pathlib import re import subprocess import sys -import tempfile import pytest import yarl @@ -20,7 +18,6 @@ utils, ) from conda_store_server.server.auth import DummyAuthentication -from fastapi import Request from fastapi.responses import RedirectResponse from traitlets import TraitError @@ -35,12 +32,20 @@ def test_function(context): context.run(["cmd", "/c", "echo subprocess"]) context.run("echo subprocess_stdout", shell=True) context.run("echo subprocess_stderr>&2", shell=True) - context.run("echo subprocess_stderr_no_redirect>&2", shell=True, redirect_stderr=False) + context.run( + "echo subprocess_stderr_no_redirect>&2", + shell=True, + redirect_stderr=False, + ) else: context.run(["echo", "subprocess"]) context.run("echo subprocess_stdout", shell=True) context.run("echo subprocess_stderr 1>&2", shell=True) - context.run("echo subprocess_stderr_no_redirect 1>&2", shell=True, redirect_stderr=False) + context.run( + "echo subprocess_stderr_no_redirect 1>&2", + shell=True, + redirect_stderr=False, + ) context.log.info("log") return pathlib.Path.cwd() @@ -85,9 +90,9 @@ def test_solve_lockfile_valid_conda_flags(conda_store, simple_specification): # Checks that conda_flags is used by conda-lock def test_solve_lockfile_invalid_conda_flags(conda_store, simple_specification): - with pytest.raises(Exception, match=( - r"Command.*--this-is-invalid.*returned non-zero exit status" - )): + with pytest.raises( + Exception, match=(r"Command.*--this-is-invalid.*returned non-zero exit status") + ): action.action_solve_lockfile( conda_command=conda_store.conda_command, specification=simple_specification, @@ -120,7 +125,9 @@ def test_solve_lockfile_multiple_platforms(conda_store, specification, request): "simple_specification_with_pip", ], ) -def test_generate_constructor_installer(conda_store, specification_name, request, tmp_path): +def test_generate_constructor_installer( + conda_store, specification_name, request, tmp_path +): specification = request.getfixturevalue(specification_name) installer_dir = tmp_path / "installer_dir" @@ -139,27 +146,27 @@ def test_generate_constructor_installer(conda_store, specification_name, request tmp_dir = tmp_path / "tmp" # Runs the installer - out_dir = pathlib.Path(tmp_dir) / 'out' - if sys.platform == 'win32': - subprocess.check_output([installer, '/S', f'/D={out_dir}']) + out_dir = pathlib.Path(tmp_dir) / "out" + if sys.platform == "win32": + subprocess.check_output([installer, "/S", f"/D={out_dir}"]) else: - subprocess.check_output([installer, '-b', '-p', str(out_dir)]) + subprocess.check_output([installer, "-b", "-p", str(out_dir)]) # Checks the output directory assert out_dir.exists() - lib_dir = out_dir / 'lib' - if specification_name == 'simple_specification': - if sys.platform == 'win32': - assert any(str(x).endswith('zlib.dll') for x in out_dir.iterdir()) - elif sys.platform == 'darwin': - assert any(str(x).endswith('libz.dylib') for x in lib_dir.iterdir()) + lib_dir = out_dir / "lib" + if specification_name == "simple_specification": + if sys.platform == "win32": + assert any(str(x).endswith("zlib.dll") for x in out_dir.iterdir()) + elif sys.platform == "darwin": + assert any(str(x).endswith("libz.dylib") for x in lib_dir.iterdir()) else: - assert any(str(x).endswith('libz.so') for x in lib_dir.iterdir()) + assert any(str(x).endswith("libz.so") for x in lib_dir.iterdir()) else: # Uses rglob to not depend on the version of the python # directory, which is where site-packages is located - flask = pathlib.Path('site-packages') / 'flask' - assert any(str(x).endswith(str(flask)) for x in out_dir.rglob('*')) + flask = pathlib.Path("site-packages") / "flask" + assert any(str(x).endswith(str(flask)) for x in out_dir.rglob("*")) def test_fetch_and_extract_conda_packages(tmp_path, simple_conda_lock): @@ -199,7 +206,7 @@ def test_generate_conda_export(conda_store, conda_prefix): ) # The env name won't be correct because conda only sets the env name when # an environment is in an envs dir. See the discussion on PR #549. - context.result['name'] = 'test-prefix' + context.result["name"] = "test-prefix" schema.CondaSpecification.parse_obj(context.result) @@ -215,9 +222,12 @@ def test_generate_conda_pack(tmp_path, conda_prefix): assert output_filename.exists() -@pytest.mark.xfail(reason=( - "Generating Docker images is currently not supported, see " - "https://github.com/conda-incubator/conda-store/issues/666")) +@pytest.mark.xfail( + reason=( + "Generating Docker images is currently not supported, see " + "https://github.com/conda-incubator/conda-store/issues/666" + ) +) def test_generate_conda_docker(conda_store, conda_prefix): action.action_generate_conda_docker( conda_prefix=conda_prefix, @@ -253,7 +263,9 @@ def test_remove_conda_prefix(tmp_path, simple_conda_lock): assert not conda_prefix.exists() -@pytest.mark.skipif(sys.platform == "win32", reason="permissions are not supported on Windows") +@pytest.mark.skipif( + sys.platform == "win32", reason="permissions are not supported on Windows" +) def test_set_conda_prefix_permissions(tmp_path, conda_store, simple_conda_lock): conda_prefix = tmp_path / "test" @@ -322,14 +334,23 @@ def test_add_lockfile_packages( ], ) def test_api_get_build_lockfile( - request, conda_store, db, simple_specification_with_pip, conda_prefix, is_legacy_build, build_key_version + request, + conda_store, + db, + simple_specification_with_pip, + conda_prefix, + is_legacy_build, + build_key_version, ): # sets build_key_version if build_key_version == 0: # invalid - with pytest.raises(TraitError, match=( - r"c.CondaStore.build_key_version: invalid build key version: 0, " - r"expected: \(1, 2\)" - )): + with pytest.raises( + TraitError, + match=( + r"c.CondaStore.build_key_version: invalid build key version: 0, " + r"expected: \(1, 2\)" + ), + ): conda_store.build_key_version = build_key_version return # invalid, nothing more to test conda_store.build_key_version = build_key_version @@ -389,7 +410,8 @@ def authorize_request(self, *args, **kwargs): namespace=namespace, environment_name=environment.name, build_id=build_id, - )) + ) + ) if key == "": # legacy build: returns pinned package list @@ -404,6 +426,7 @@ def authorize_request(self, *args, **kwargs): # new build: redirects to lockfile generated by conda-lock def lockfile_url(build_key): return f"lockfile/{build_key}.yml" + if build_key_version == 1: build_key = ( "c7afdeffbe2bda7d16ca69beecc8bebeb29280a95d4f3ed92849e4047710923b-" @@ -414,13 +437,13 @@ def lockfile_url(build_key): else: raise ValueError(f"unexpected build_key_version: {build_key_version}") assert type(res) is RedirectResponse - assert key == res.headers['location'] + assert key == res.headers["location"] assert build.build_key == build_key assert BuildKey.get_build_key(build) == build_key assert build.parse_build_key(build_key) == 12345678 assert BuildKey.parse_build_key(build_key) == 12345678 assert lockfile_url(build_key) == build.conda_lock_key - assert lockfile_url(build_key) == res.headers['location'] + assert lockfile_url(build_key) == res.headers["location"] assert res.status_code == 307 @@ -464,7 +487,8 @@ def authorize_request(self, *args, **kwargs): conda_store=conda_store, auth=auth, build_id=build_id, - )) + ) + ) # redirects to installer def installer_url(build_key): @@ -472,7 +496,7 @@ def installer_url(build_key): return f"installer/{build_key}.{ext}" assert type(res) is RedirectResponse - assert build.constructor_installer_key == res.headers['location'] + assert build.constructor_installer_key == res.headers["location"] assert installer_url(build.build_key) == build.constructor_installer_key assert res.status_code == 307 diff --git a/conda-store-server/tests/test_auth.py b/conda-store-server/tests/test_auth.py index db39ab724..99145d5ff 100644 --- a/conda-store-server/tests/test_auth.py +++ b/conda-store-server/tests/test_auth.py @@ -129,7 +129,6 @@ def test_expired_token(): ], ) def test_authorization(conda_store, entity_bindings, arn, permissions, authorized): - authorization = RBACAuthorizationBackend( authentication_db=conda_store.session_factory ) @@ -187,7 +186,14 @@ def test_authorization(conda_store, entity_bindings, arn, permissions, authorize ], ) @pytest.mark.parametrize("role_mappings_version", [1, 2]) -def test_end_to_end_auth_flow_v1(conda_store_server, testclient, authenticate, role_mappings_version, role, permissions): +def test_end_to_end_auth_flow_v1( + conda_store_server, + testclient, + authenticate, + role_mappings_version, + role, + permissions, +): # Configures authentication namespace = f"this-{uuid.uuid4()}" other_namespace = f"other-{uuid.uuid4()}" @@ -209,40 +215,41 @@ def test_end_to_end_auth_flow_v1(conda_store_server, testclient, authenticate, r authorization = RBACAuthorizationBackend( authentication_db=conda_store.session_factory, - role_mappings_version = role_mappings_version, + role_mappings_version=role_mappings_version, ) + def authorize(): return authorization.authorize( - AuthenticationToken( - primary_namespace=token_model.primary_namespace, - role_bindings=token_model.role_bindings, - ), - f"{other_namespace}/example-name", - permissions, - ) + AuthenticationToken( + primary_namespace=token_model.primary_namespace, + role_bindings=token_model.role_bindings, + ), + f"{other_namespace}/example-name", + permissions, + ) + # No default roles assert authorize() is False # Creates new namespaces for n in (namespace, other_namespace): - response = testclient.post( - f"api/v1/namespace/{n}" - ) + response = testclient.post(f"api/v1/namespace/{n}") response.raise_for_status() # Deletes roles to start with a clean state response = testclient.put( - f"api/v1/namespace/{namespace}", - json={"role_mappings": {}} + f"api/v1/namespace/{namespace}", json={"role_mappings": {}} ) response.raise_for_status() # Creates role for 'namespace' with access to 'other_namespace' response = testclient.put( f"api/v1/namespace/{namespace}", - json={"role_mappings": { - f"{other_namespace}/ex*-name": [role], - }} + json={ + "role_mappings": { + f"{other_namespace}/ex*-name": [role], + } + }, ) response.raise_for_status() @@ -254,8 +261,7 @@ def authorize(): # Deletes created roles response = testclient.put( - f"api/v1/namespace/{namespace}", - json={"role_mappings": {}} + f"api/v1/namespace/{namespace}", json={"role_mappings": {}} ) response.raise_for_status() @@ -273,7 +279,14 @@ def authorize(): ], ) @pytest.mark.parametrize("role_mappings_version", [1, 2]) -def test_end_to_end_auth_flow_v2(conda_store_server, testclient, authenticate, role_mappings_version, role, permissions): +def test_end_to_end_auth_flow_v2( + conda_store_server, + testclient, + authenticate, + role_mappings_version, + role, + permissions, +): # Configures authentication namespace = f"this-{uuid.uuid4()}" other_namespace = f"other-{uuid.uuid4()}" @@ -295,8 +308,9 @@ def test_end_to_end_auth_flow_v2(conda_store_server, testclient, authenticate, r authorization = RBACAuthorizationBackend( authentication_db=conda_store.session_factory, - role_mappings_version = role_mappings_version, + role_mappings_version=role_mappings_version, ) + def authorize(): return authorization.authorize( AuthenticationToken( @@ -306,29 +320,23 @@ def authorize(): f"{other_namespace}/example-name", permissions, ) + # No default roles assert authorize() is False # Creates new namespaces for n in (namespace, other_namespace): - response = testclient.post( - f"api/v1/namespace/{n}" - ) + response = testclient.post(f"api/v1/namespace/{n}") response.raise_for_status() # Deletes roles to start with a clean state - response = testclient.delete( - f"api/v1/namespace/{other_namespace}/roles" - ) + response = testclient.delete(f"api/v1/namespace/{other_namespace}/roles") response.raise_for_status() # Creates role for 'namespace' with access to 'other_namespace' response = testclient.post( f"api/v1/namespace/{other_namespace}/role", - json={ - "other_namespace": namespace, - "role": role - }, + json={"other_namespace": namespace, "role": role}, ) response.raise_for_status() @@ -339,9 +347,7 @@ def authorize(): assert authorize() is False # Deletes created roles - response = testclient.delete( - f"api/v1/namespace/{other_namespace}/roles" - ) + response = testclient.delete(f"api/v1/namespace/{other_namespace}/roles") response.raise_for_status() # Should fail again diff --git a/conda-store-server/tests/test_db_api.py b/conda-store-server/tests/test_db_api.py index 4c9e229b5..25b86cdf8 100644 --- a/conda-store-server/tests/test_db_api.py +++ b/conda-store-server/tests/test_db_api.py @@ -98,29 +98,44 @@ def test_namespace_role_mapping_v2(db, editor_role): assert len(api.list_namespaces(db).all()) == 4 # Creates role mappings - api.create_namespace_role(db, name=namespace_name, other=other_namespace_name1, role="admin") - api.create_namespace_role(db, name=namespace_name, other=other_namespace_name2, role="admin") - api.create_namespace_role(db, name=namespace_name, other=other_namespace_name3, role="viewer") + api.create_namespace_role( + db, name=namespace_name, other=other_namespace_name1, role="admin" + ) + api.create_namespace_role( + db, name=namespace_name, other=other_namespace_name2, role="admin" + ) + api.create_namespace_role( + db, name=namespace_name, other=other_namespace_name3, role="viewer" + ) db.commit() # Attempts to create a role mapping with an invalid role with pytest.raises(ValueError, match=r"invalid role=invalid-role"): - api.create_namespace_role(db, name=namespace_name, other=other_namespace_name3, role="invalid-role") + api.create_namespace_role( + db, name=namespace_name, other=other_namespace_name3, role="invalid-role" + ) db.commit() # Attempts to create a role mapping violating the uniqueness constraint with pytest.raises( - Exception, - match=(r"UNIQUE constraint failed: " - r"namespace_role_mapping_v2.namespace_id, " - r"namespace_role_mapping_v2.other_namespace_id")): + Exception, + match=( + r"UNIQUE constraint failed: " + r"namespace_role_mapping_v2.namespace_id, " + r"namespace_role_mapping_v2.other_namespace_id" + ), + ): # Runs in a nested transaction since a constraint violation will cause a rollback with db.begin_nested(): - api.create_namespace_role(db, name=namespace_name, other=other_namespace_name2, role=editor_role) + api.create_namespace_role( + db, name=namespace_name, other=other_namespace_name2, role=editor_role + ) db.commit() # Updates a role mapping - api.update_namespace_role(db, name=namespace_name, other=other_namespace_name2, role=editor_role) + api.update_namespace_role( + db, name=namespace_name, other=other_namespace_name2, role=editor_role + ) db.commit() # Gets all role mappings @@ -131,17 +146,17 @@ def test_namespace_role_mapping_v2(db, editor_role): assert roles[0].id == 1 assert roles[0].namespace == namespace_name assert roles[0].other_namespace == other_namespace_name1 - assert roles[0].role == 'admin' + assert roles[0].role == "admin" assert roles[1].id == 2 assert roles[1].namespace == namespace_name assert roles[1].other_namespace == other_namespace_name2 - assert roles[1].role == 'developer' # always developer in the DB + assert roles[1].role == "developer" # always developer in the DB assert roles[2].id == 3 assert roles[2].namespace == namespace_name assert roles[2].other_namespace == other_namespace_name3 - assert roles[2].role == 'viewer' + assert roles[2].role == "viewer" # Gets other role mappings roles = api.get_other_namespace_roles(db, other_namespace_name1) @@ -163,12 +178,12 @@ def test_namespace_role_mapping_v2(db, editor_role): assert roles[0].id == 1 assert roles[0].namespace == namespace_name assert roles[0].other_namespace == other_namespace_name1 - assert roles[0].role == 'admin' + assert roles[0].role == "admin" assert roles[1].id == 3 assert roles[1].namespace == namespace_name assert roles[1].other_namespace == other_namespace_name3 - assert roles[1].role == 'viewer' + assert roles[1].role == "viewer" # Deletes all role mappings api.delete_namespace_roles(db, name=namespace_name) @@ -247,10 +262,12 @@ def test_get_set_keyvaluestore(db): def test_build_path_too_long(db, conda_store, simple_specification): - conda_store.store_directory = 'A' * 800 + conda_store.store_directory = "A" * 800 build_id = conda_store.register_environment( db, specification=simple_specification, namespace="pytest" ) build = api.get_build(db, build_id=build_id) - with pytest.raises(BuildPathError, match=r"build_path too long: must be <= 255 characters"): + with pytest.raises( + BuildPathError, match=r"build_path too long: must be <= 255 characters" + ): build.build_path(conda_store) diff --git a/conda-store-server/tests/test_server.py b/conda-store-server/tests/test_server.py index fb262fc75..4624f3650 100644 --- a/conda-store-server/tests/test_server.py +++ b/conda-store-server/tests/test_server.py @@ -26,11 +26,13 @@ def test_api_permissions_unauth(testclient): assert r.data.authenticated is False assert r.data.primary_namespace == "default" assert r.data.entity_permissions == { - "default/*": sorted([ - schema.Permissions.ENVIRONMENT_READ.value, - schema.Permissions.NAMESPACE_READ.value, - schema.Permissions.NAMESPACE_ROLE_MAPPING_READ.value, - ]) + "default/*": sorted( + [ + schema.Permissions.ENVIRONMENT_READ.value, + schema.Permissions.NAMESPACE_READ.value, + schema.Permissions.NAMESPACE_ROLE_MAPPING_READ.value, + ] + ) } @@ -357,9 +359,11 @@ def test_create_specification_unauth(testclient): 256, ], ) -def test_create_specification_auth_env_name_too_long(testclient, celery_worker, authenticate, size): +def test_create_specification_auth_env_name_too_long( + testclient, celery_worker, authenticate, size +): namespace = "default" - environment_name = 'A' * size + environment_name = "A" * size response = testclient.post( "api/v1/specification", @@ -395,13 +399,14 @@ def test_create_specification_auth_env_name_too_long(testclient, celery_worker, # If we're here, the task didn't update the status on failure if not is_updated: - assert False, f"failed to update status" + assert False, "failed to update status" @pytest.fixture def win_extended_length_prefix(request): # Overrides the attribute before other fixtures are called from conda_store_server.app import CondaStore + assert type(CondaStore.win_extended_length_prefix) is traitlets.Bool old_prefix = CondaStore.win_extended_length_prefix CondaStore.win_extended_length_prefix = request.param @@ -410,11 +415,13 @@ def win_extended_length_prefix(request): @pytest.mark.skipif(sys.platform != "win32", reason="tests a Windows issue") -@pytest.mark.parametrize('win_extended_length_prefix', [True, False], indirect=True) +@pytest.mark.parametrize("win_extended_length_prefix", [True, False], indirect=True) @pytest.mark.extended_prefix -def test_create_specification_auth_extended_prefix(win_extended_length_prefix, testclient, celery_worker, authenticate): +def test_create_specification_auth_extended_prefix( + win_extended_length_prefix, testclient, celery_worker, authenticate +): # Adds padding to cause an error if the extended prefix is not enabled - namespace = "default" + 'A' * 10 + namespace = "default" + "A" * 10 environment_name = "pytest" # The debugpy 1.8.0 package was deliberately chosen because it has long @@ -424,14 +431,16 @@ def test_create_specification_auth_extended_prefix(win_extended_length_prefix, t "api/v1/specification", json={ "namespace": namespace, - "specification": json.dumps({ - "name": environment_name, - "channels": ["conda-forge"], - "dependencies": ["debugpy==1.8.0"], - "variables": None, - "prefix": None, - "description": "test" - }), + "specification": json.dumps( + { + "name": environment_name, + "channels": ["conda-forge"], + "dependencies": ["debugpy==1.8.0"], + "variables": None, + "prefix": None, + "description": "test", + } + ), }, timeout=30, ) @@ -462,7 +471,9 @@ def test_create_specification_auth_extended_prefix(win_extended_length_prefix, t assert r.data.status == "FAILED" response = testclient.get(f"api/v1/build/{build_id}/logs", timeout=30) response.raise_for_status() - assert "[WinError 206] The filename or extension is too long" in response.text + assert ( + "[WinError 206] The filename or extension is too long" in response.text + ) is_updated = True break diff --git a/conda-store-server/tests/test_usage.py b/conda-store-server/tests/test_usage.py index fcf3990a8..98833938c 100644 --- a/conda-store-server/tests/test_usage.py +++ b/conda-store-server/tests/test_usage.py @@ -2,6 +2,7 @@ # TODO: Add tests for the other functions in utils.py + def test_disk_usage(tmp_path): test_dir = tmp_path / "test_dir" test_dir.mkdir() @@ -11,9 +12,9 @@ def test_disk_usage(tmp_path): assert abs(dir_size - int(disk_usage(test_dir))) <= 1000 test_file = test_dir / "test_file" - test_file.write_text("a"*1000) + test_file.write_text("a" * 1000) test_file2 = test_dir / "test_file2" - test_file2.write_text("b"*1000) + test_file2.write_text("b" * 1000) # Test hard links test_file_hardlink = test_dir / "test_file_hardlink" test_file_hardlink.hardlink_to(test_file) diff --git a/conda-store-server/tests/user_journeys/utils/api_utils.py b/conda-store-server/tests/user_journeys/utils/api_utils.py index 3968e0c41..8608d766a 100644 --- a/conda-store-server/tests/user_journeys/utils/api_utils.py +++ b/conda-store-server/tests/user_journeys/utils/api_utils.py @@ -11,6 +11,7 @@ class BuildStatus(Enum): """Enum for API build status.""" + QUEUED = "QUEUED" BUILDING = "BUILDING" COMPLETED = "COMPLETED" @@ -19,14 +20,17 @@ class BuildStatus(Enum): class API: """ - Helper class for making requests to the API. - These methods are used to build tests for user journeys + Helper class for making requests to the API. + These methods are used to build tests for user journeys """ - def __init__(self, - base_url: str, token: str = "", - username: str = "username", password: str = "password" - ) -> None: + def __init__( + self, + base_url: str, + token: str = "", + username: str = "username", + password: str = "password", + ) -> None: self.base_url = base_url self.token = token if not token: @@ -34,79 +38,78 @@ def __init__(self, self._login(username, password) def _make_request( - self, endpoint: str, method: str = "GET", json_data: dict = None, - headers: dict = None, timeout: int = TIMEOUT) -> requests.Response: - """ Make a request to the API. """ + self, + endpoint: str, + method: str = "GET", + json_data: dict = None, + headers: dict = None, + timeout: int = TIMEOUT, + ) -> requests.Response: + """Make a request to the API.""" url = f"{self.base_url}/{endpoint}" headers = headers or {} headers["Authorization"] = f"Bearer {self.token}" response = requests.request( - method, url, json=json_data, headers=headers, timeout=timeout) + method, url, json=json_data, headers=headers, timeout=timeout + ) response.raise_for_status() return response def _login(self, username: str, password: str) -> None: - """ Log in to the API and set an access token.""" + """Log in to the API and set an access token.""" json_data = {"username": username, "password": password} response = requests.post( - f"{self.base_url}/login", json=json_data, timeout=TIMEOUT) + f"{self.base_url}/login", json=json_data, timeout=TIMEOUT + ) cookies = response.cookies.get_dict() token_response = requests.post( - f"{self.base_url}/api/v1/token", cookies=cookies, timeout=TIMEOUT) + f"{self.base_url}/api/v1/token", cookies=cookies, timeout=TIMEOUT + ) data = token_response.json() self.token = data["data"]["token"] def create_namespace(self, namespace: str) -> requests.Response: - """ Create a namespace.""" - return self._make_request( - f"api/v1/namespace/{namespace}", method="POST") + """Create a namespace.""" + return self._make_request(f"api/v1/namespace/{namespace}", method="POST") def create_token( - self, namespace: str, role: str, - default_namespace: str = "default" - ) -> requests.Response: - """ Create a token with a specified role in a specified namespace. """ + self, namespace: str, role: str, default_namespace: str = "default" + ) -> requests.Response: + """Create a token with a specified role in a specified namespace.""" json_data = { "primary_namespace": default_namespace, "expiration": time_utils.get_iso8601_time(1), - "role_bindings": { - f"{namespace}/*": [role] - } + "role_bindings": {f"{namespace}/*": [role]}, } - return self._make_request( - "api/v1/token", method="POST", json_data=json_data) + return self._make_request("api/v1/token", method="POST", json_data=json_data) def create_environment( - self, namespace: str, specification_path: str - ) -> requests.Response: + self, namespace: str, specification_path: str + ) -> requests.Response: """ - Create an environment. - The environment specification is read - from a conda environment.yaml file. + Create an environment. + The environment specification is read + from a conda environment.yaml file. """ with open(specification_path, "r", encoding="utf-8") as file: specification_content = file.read() - json_data = { - "namespace": namespace, - "specification": specification_content - } + json_data = {"namespace": namespace, "specification": specification_content} return self._make_request( - "api/v1/specification", method="POST", json_data=json_data) + "api/v1/specification", method="POST", json_data=json_data + ) def wait_for_successful_build( - self, build_id: str, - max_iterations: int = 100, sleep_time: int = 5 - ) -> requests.Response: - """ Wait for a build to complete.""" + self, build_id: str, max_iterations: int = 100, sleep_time: int = 5 + ) -> requests.Response: + """Wait for a build to complete.""" status = BuildStatus.QUEUED.value iterations = 0 while status != BuildStatus.COMPLETED.value: if iterations > max_iterations: raise TimeoutError("Timed out waiting for build") - response = self._make_request( - f"api/v1/build/{build_id}", method="GET") + response = self._make_request(f"api/v1/build/{build_id}", method="GET") status = response.json()["data"]["status"] assert status != BuildStatus.FAILED.value, "Build failed" iterations += 1 @@ -114,20 +117,18 @@ def wait_for_successful_build( return response def delete_environment( - self, namespace: str, environment_name: str - ) -> requests.Response: - """ Delete an environment.""" + self, namespace: str, environment_name: str + ) -> requests.Response: + """Delete an environment.""" return self._make_request( - f"api/v1/environment/{namespace}/{environment_name}", - method="DELETE") + f"api/v1/environment/{namespace}/{environment_name}", method="DELETE" + ) def delete_namespace(self, namespace: str) -> requests.Response: - """ Delete a namespace.""" - return self._make_request( - f"api/v1/namespace/{namespace}", method="DELETE" - ) + """Delete a namespace.""" + return self._make_request(f"api/v1/namespace/{namespace}", method="DELETE") @staticmethod def gen_random_namespace() -> str: - """ Generate a random namespace.""" + """Generate a random namespace.""" return uuid.uuid4().hex diff --git a/conda-store-server/tests/user_journeys/utils/time_utils.py b/conda-store-server/tests/user_journeys/utils/time_utils.py index d61c203c8..4b94b2302 100644 --- a/conda-store-server/tests/user_journeys/utils/time_utils.py +++ b/conda-store-server/tests/user_journeys/utils/time_utils.py @@ -2,19 +2,19 @@ def get_current_time() -> datetime: - """ Get the current time. """ + """Get the current time.""" return datetime.now(timezone.utc) def get_time_in_future(hours: int) -> datetime: - """ Get the time in the future.""" + """Get the time in the future.""" current_time = get_current_time() future_time = current_time + timedelta(hours=hours) return future_time def get_iso8601_time(hours: int) -> str: - """ Get the time in the future in ISO 8601 format. """ + """Get the time in the future in ISO 8601 format.""" future_time = get_time_in_future(hours) iso_format = future_time.isoformat() return iso_format diff --git a/tests/conftest.py b/tests/conftest.py index 5fbb76aa0..aa3135329 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,16 +1,14 @@ import os import sys - -sys.path.append(os.path.join(os.getcwd(), "conda-store-server")) - from urllib.parse import urljoin import pytest from requests import Session -CONDA_STORE_SERVER_PORT = os.environ.get( - "CONDA_STORE_SERVER_PORT", f"8080" -) +sys.path.append(os.path.join(os.getcwd(), "conda-store-server")) + + +CONDA_STORE_SERVER_PORT = os.environ.get("CONDA_STORE_SERVER_PORT", "8080") CONDA_STORE_BASE_URL = os.environ.get( "CONDA_STORE_BASE_URL", f"http://localhost:{CONDA_STORE_SERVER_PORT}/conda-store/" ) @@ -49,6 +47,7 @@ def testclient(): session = CondaStoreSession(CONDA_STORE_BASE_URL) yield session + @pytest.fixture def server_port(): return CONDA_STORE_SERVER_PORT diff --git a/tests/test_api.py b/tests/test_api.py index e936a0632..c5af61af3 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -16,14 +16,12 @@ import time import uuid from functools import partial -from typing import List import aiohttp import conda_store_server import pytest import requests from conda_store_server import schema -from pydantic import parse_obj_as from .conftest import CONDA_STORE_BASE_URL @@ -80,14 +78,16 @@ def test_api_permissions_unauth(testclient): r = schema.APIGetPermission.parse_obj(response.json()) assert r.status == schema.APIStatus.OK - assert r.data.authenticated == False + assert r.data.authenticated is False assert r.data.primary_namespace == "default" assert r.data.entity_permissions == { - "default/*": sorted([ - schema.Permissions.ENVIRONMENT_READ.value, - schema.Permissions.NAMESPACE_READ.value, - schema.Permissions.NAMESPACE_ROLE_MAPPING_READ.value, - ]) + "default/*": sorted( + [ + schema.Permissions.ENVIRONMENT_READ.value, + schema.Permissions.NAMESPACE_READ.value, + schema.Permissions.NAMESPACE_ROLE_MAPPING_READ.value, + ] + ) } @@ -98,7 +98,7 @@ def test_api_permissions_auth(testclient): r = schema.APIGetPermission.parse_obj(response.json()) assert r.status == schema.APIStatus.OK - assert r.data.authenticated == True + assert r.data.authenticated is True assert r.data.primary_namespace == "username" assert r.data.entity_permissions == { "*/*": sorted( @@ -475,7 +475,18 @@ def test_create_specification_parallel_auth(testclient): environment_name = f"pytest-{uuid.uuid4()}" # Builds different versions to avoid caching - versions = ["6.2.0", "6.2.1", "6.2.2", "6.2.3", "6.2.4", "6.2.5", "7.1.1", "7.1.2", "7.3.1", "7.4.0"] + versions = [ + "6.2.0", + "6.2.1", + "6.2.2", + "6.2.3", + "6.2.4", + "6.2.5", + "7.1.1", + "7.1.2", + "7.3.1", + "7.4.0", + ] num_builds = len(versions) limit_seconds = 60 * 15 build_ids = collections.deque([]) @@ -487,11 +498,13 @@ def test_create_specification_parallel_auth(testclient): "api/v1/specification", json={ "namespace": namespace, - "specification": json.dumps({ - "name": environment_name, - "channels": ["main"], - "dependencies": [f"pytest={version}"], - }), + "specification": json.dumps( + { + "name": environment_name, + "channels": ["main"], + "dependencies": [f"pytest={version}"], + } + ), }, timeout=10, ) @@ -550,10 +563,10 @@ def get_logs(): return response.text # Exits immediately on failure - assert r.data.status != 'FAILED', get_logs() + assert r.data.status != "FAILED", get_logs() # If not done, adds the id back to the end of the queue - if r.data.status != 'COMPLETED': + if r.data.status != "COMPLETED": build_ids.append(build_id) # Adds a small delay to avoid making too many requests too fast. The @@ -622,7 +635,7 @@ def get_logs(): # be reached based on how long it takes a single build to run # non-concurrently on average: threshold = 10 - quartiles = statistics.quantiles(build_deltas, method='inclusive') + quartiles = statistics.quantiles(build_deltas, method="inclusive") print("build_deltas", build_deltas) print("stats", min(build_deltas), quartiles) assert quartiles[0] < threshold @@ -646,7 +659,7 @@ def get_logs(): ) def test_create_specification_auth_env_name_too_long(testclient, size): namespace = "default" - environment_name = 'A' * size + environment_name = "A" * size testclient.login() response = testclient.post( @@ -686,7 +699,7 @@ def test_create_specification_auth_env_name_too_long(testclient, size): # If we're here, the task didn't update the status on failure if not is_updated: - assert False, f"failed to update status" + assert False, "failed to update status" def test_create_specification_auth_no_namespace_specified(testclient): @@ -773,7 +786,7 @@ def test_create_namespace_auth(testclient): def test_update_namespace_noauth(testclient): - namespace = f"filesystem" + namespace = "filesystem" # namespace = f"pytest-{uuid.uuid4()}" test_role_mappings = { @@ -825,7 +838,7 @@ def test_update_namespace_noauth(testclient): ], ) def test_update_namespace_auth(testclient, editor_role): - namespace = f"filesystem" + namespace = "filesystem" testclient.login() @@ -898,7 +911,9 @@ def test_create_get_delete_namespace_auth(testclient): assert r.status == schema.APIStatus.ERROR -def _crud_common(testclient, auth, method, route, params=None, json=None, data_pred=None): +def _crud_common( + testclient, auth, method, route, params=None, json=None, data_pred=None +): if auth: testclient.login() @@ -927,7 +942,7 @@ def _crud_common(testclient, auth, method, route, params=None, json=None, data_p @pytest.mark.parametrize("auth", [True, False]) def test_update_namespace_metadata_v2(testclient, auth): - namespace = f"filesystem" + namespace = "filesystem" make_request = partial(_crud_common, testclient=testclient, auth=auth) make_request( @@ -947,7 +962,7 @@ def test_update_namespace_metadata_v2(testclient, auth): ) def test_crud_namespace_roles_v2(testclient, auth, editor_role): other_namespace = f"pytest-{uuid.uuid4()}" - namespace = f"filesystem" + namespace = "filesystem" make_request = partial(_crud_common, testclient=testclient, auth=auth) # Deletes roles to start with a clean state @@ -980,9 +995,9 @@ def test_crud_namespace_roles_v2(testclient, auth, editor_role): "other_namespace": other_namespace, }, data_pred=lambda data: ( - data['namespace'] == 'filesystem' and - data['other_namespace'] == other_namespace and - data['role'] == 'developer' # always developer in the DB + data["namespace"] == "filesystem" + and data["other_namespace"] == other_namespace + and data["role"] == "developer" # always developer in the DB ), ) @@ -990,10 +1005,7 @@ def test_crud_namespace_roles_v2(testclient, auth, editor_role): make_request( method=testclient.put, route=f"api/v1/namespace/{namespace}/role", - json={ - "other_namespace": other_namespace, - "role": "admin" - }, + json={"other_namespace": other_namespace, "role": "admin"}, ) # Reads updated roles @@ -1001,10 +1013,10 @@ def test_crud_namespace_roles_v2(testclient, auth, editor_role): method=testclient.get, route=f"api/v1/namespace/{namespace}/roles", data_pred=lambda data: ( - data[0]['namespace'] == 'filesystem' and - data[0]['other_namespace'] == other_namespace and - data[0]['role'] == 'admin' and - len(data) == 1 + data[0]["namespace"] == "filesystem" + and data[0]["other_namespace"] == other_namespace + and data[0]["role"] == "admin" + and len(data) == 1 ), ) diff --git a/tests/test_playwright.py b/tests/test_playwright.py index f8f6878d6..f9fd0f7be 100644 --- a/tests/test_playwright.py +++ b/tests/test_playwright.py @@ -7,7 +7,10 @@ @pytest.mark.playwright def test_integration(page: Page, server_port): # Go to http://localhost:{server_port}/conda-store/admin/ - page.goto(f"http://localhost:{server_port}/conda-store/admin/", wait_until="domcontentloaded") + page.goto( + f"http://localhost:{server_port}/conda-store/admin/", + wait_until="domcontentloaded", + ) page.screenshot(path="test-results/conda-store-unauthenticated.png") # Click text=Login From bf36f67942f7648172a29d244fbe3d817844a214 Mon Sep 17 00:00:00 2001 From: Pavithra Eswaramoorthy Date: Tue, 27 Feb 2024 17:59:17 +0530 Subject: [PATCH 45/60] [DOC] Remove duplicate image (#762) --- .../conda-store-ui/images/versions-view.png | Bin 239259 -> 0 bytes .../tutorials/version-control.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 docusaurus-docs/conda-store-ui/images/versions-view.png diff --git a/docusaurus-docs/conda-store-ui/images/versions-view.png b/docusaurus-docs/conda-store-ui/images/versions-view.png deleted file mode 100644 index f2610a4ce4a134498fff116e075eab7e355e3a93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 239259 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≪_VYaI@h(OOr53^%YRrCnocA zpI;6nyQHHeb(XDIO-_HW_r}HX4u*MJZ|6DR3CP#YvsosudRs!9rJpz#;H^v78V1h1 za!>i#R)WjdfK%HJWWkXIAs@u7f)vLT7z7Ko^*)O_e^vxDi-P%97xs%NusfG5@-bk$ zDF%j;2Aj9Xg5EkF7=x50R?}jn=u=~BYR=?H8Zi4Bb>H{(jj++wFE%z%*)t9-qJTsv#(n41 z)k8;BpI7QNh9REc= zDNh_Z`B&lOtOKdrX;Vh>d$W8SPX-5cYkBCCQWB$f&iBmPL5(*2XsGO0Zu$j843wby%2O4YxWQwJf6)RDdk$dlZ_h0+_|dz@gMPSqw`kLgNRqxoQkY zF4Lz-GK`8(I%#gG(%W)zLlnpc+K}bCEUCp9gb>VN^w*E~`^u4*X#(k z19HK^-$<&(sREPEQhHX?>YdiczFJQ|+N?$mi}c}QZk4$AdUXmOVbD-`S{Bl7ioYfT z6Fc(D=KU;$+%SpwB-O3_?S=2O9N#&NoUfbQ$g(7*^}Nb>^S<{ukAwE}c@AA?7gBVI~ymb;;FWmM`BlMI(NiJGBw?1bYycx?cC#5F8U?{mv zLeqP^xv`3J(ogk#=`S}d_w7)g+H1l6EWX+dUl3dOuq7-ktn)+wG$EL8q*Gca5hJ)9 zld3~?ypCxYAKlkpKiksMvK*46F!?kR(@D$MknNbmIhF$>3>-sGX;;`sdsUcv;8o)B zh86Mm?^Eh~hjZ1xlQU@2y4?X17v|g|{TSPw6XmHSwb{eE*ooxiWZMCnMH_JQQ8O_y zS+Y)^k9FRXCP7}C6JW@sLxmqpxNMfe*;$BMwtf0D9;!%?b7rBADA?e6I)|h}~vgk71N|@q9Rr|0^4A_5NT$d7Y z$In$rMb|I`kYDV+l+9uxS!#306%O15vp+Ru`l|0$Om2{ zx1PNY`V8e?KUy8&iGm3nAn|xdJ~USqDrl4fLX+vcn_0bPmmOkG?=;M0rOasPy9tja3zjXm0T0v^jK`^_*4n!`S|(R zKSEmeGBF|Q-_9UrI^q@fY-{o|H3JpC%N{3q?c#IMOg*H_cVt@JOY>#81R0iYC&yRD zrCiO0f8t3T{$2(BeuENej9Xq#+VRbkqMoGqtJHrIj~Z)(B|3YhJ4mteuD-tC8#o4D zBe}5e&+coK=OYZ2io*Qz#zjAo1mdNnA{A)RINo172@d`Yc1??S!|>0!_U&E?sGo)q z+)aKqm)ZQCj$_h|hNoUlq`A^~p^GvvcW>D|D!@iautAw3^@Hdr5m{zec0vn3Z_U|% zkr-+MK9u`GE1rUsQ&7o9YgNJdDW=Yk%>Ce5KkK0JlEr>!H?Tmo0L*bUslL*+JDjpj z97e$G#1>V0oFJ#rbs1(azxa&HDXBV=;nOs6Mu#p1yo0^+%_boN9wgItIk?ZWfuB6C z5}N{IV#>ovjNF~(>W__+w?aZDlvqTi*F7S8pyNy5ZjzjJ^@LuWa}f|eN+x!>P*YU3 zQxTEv{LZF5&#p%){cOU_Ky8)L|?bXL$ zwR^6SsA|lk@ls(?QTFxTy9?@D+;vu+7BQ>LIGmfVNoOBD4G}s-#f8e)dcDS>UeJ|2 zSlWMOkMiiDn`voOsz?xu6%jnv3t(rY+jcI_U8gjTz?wnKG~JX@4Css+H~vT(=Ml>4qlgK+24<9Y`j`fKdqh;Ut($eeOt=D zBrc5{dK!k;yYW{r6;kqwiVwAIUenbVJs4FhnCSwW5)gL$9L+2pr$;<^StU8IRW5VdAtdH?comX*a_c?c^eZ^4u4)qrTojB2=JhPc16t{(U}X6CU^DoX?m*+~G@;ztZPzF_uDxrztWp6l3_4;q1~p~-Up@?Eb;0En~!zjfu+xc9@A-3I1%hX@SMH0@g= zLmg#5CJ(llg=jm3CesX$JKFJ-jP^TI*M<*XvTf0!x^sr^rSgtOvp#-ZK(L@KJXy*- zKM%t6Y(R?^Bk=~+Y@L6PE}h?=d@%ywM6P+loPmbTWyt4Et4^e`Vl z4?19r`&+<=k;c8FW6Nd|?UhKFa=WOK+>&ADIx5!cc;<{ZRz-#A7H`@WSAAh42VqEo zg-LOfr*wJfv8Gv{9OZgh$62oLr5$)TgkSf~{1*^dU>CetjX_=}FI)X))wmAe(&+d1W%dZok&M+W3{y{TqJHTWdH*Brz`h} znYeVhF^r7zul>-RYgsXbVn24EOj8t+xmz=yW`n&k-VYO<;tnW%AzKb zTZ!JPV$0Is({INkyFqd{4Y*c~@=8ZHCXEm5F_(DzFZ~Pne8=09C~hbPkAqbu9%xT_ z5W3p{raVCA4imw$m@Es}obXgw&d&{P4q%3>36<%=BE{SaG>*Yq_fMwrvD;pTCnX|x zC}(t7vzIQGEtbMY$>`-|?v_84^SB1o6Sm!*!B3vFLK)QWa^3r^`?Rm_(hw@P$y-Y{ zGDlEN7CGs>nx(H5s;}8fF|mu$RveT!%Z}yC5yj@nC;@Ot<8 zS>B1>vsq-S(0)av?H-AT{BM}%I)DT3%(kP8Zx0u=_RRilFN2=pF<_{(s9GP4E~(;W@`9QspQQR$fB{j3lG>=f!S43V9qe&ylzJ>Oqq@ma}3aO>dfWlo?XYk^~a- zprTfCD}J#!>GmdJ*60iuk=;#W7+4$Y!M$I}`o5{9S_F;_uY4I-PNT#&0qh^@?u%yV z0#ZtHBj2zH!181dB(TK$X%C-k|8)Gqyu88+=g=C(>+a7 zQX;QJ@r@3=CRjc4~W}IcX&6WZHaU_pw(?iN%g>H z=JPvYqndsBEZZ7P$8|yAx$4~K59hKzTKBI*d5MM?_bsBC-HFy++WjX^O>Z%>G#+C* zOw(d`g}^sPTPq2MtWQn=W9WAmal@uZFx4ad3_tAT`T2Q7V;U?1(tjGIPRVW-!1(P) zl%d5M?EV$57z^-NT3tPs-`Ria%BsgE;YyGFT}s%#;|s=ewMZQK)r$Qf!4b7&V4@F6 zl1^DkFIDi^!#eqsP>B_mqVZ<{Jeq{)ftAz(_;)Q2Y2&_r5e6S+cTA*InEMS1k;++m zopk208q8mNuu%^AA_L#RZc!=mibtD|&*pFK5&jS1BDLPyq>a!2W2L4RE5WyY=Ptg{ zx3j~pvb96G3uVRaBVt&zJzcJQ;<`V~?v-WTGsn%%Y(RV*;P4{uQBQmDeVpm=zH>&A zr3nWvtXhx++ zE6vcPAPD^~#q<-4c^#I5A8iFRoVqEgsK&uwV9c)No?qcX>2e@;v`*p{#oV94jOQ%4 zry&iGa8D^^)etu@=S~^yIg+t+LQvg|^JK;jLjh{7L5U%8Y~S#_g(K@QH~T z&~seiv7MxN#d^{jps02<)X9wQwvrR*Q10S;>63|(HRxJ_1KSRkb1BfKB;V*2)bCkw zdVVo-vE{Zaz^uJ#K?5O-gr zl9{EP>m!n3S*XlYQ8WW@)KXR3KB(u8aAjZSwauhYYryrl0O!+oI}31=u#A8zMF-$p z#CYQW7~0$kK>fi}5r3_;^<{JDiXE0sWz5aW5-0|wFP6FsLtnc`42-Iq_MZQo9r#Wf zBX^3cSsVZCc=yy|2efUv2N(0wp3jSW*s0Es(lo8)mVn6^l;RD%g`XeYjefEisq@N%**lE3X0|=c@I6A6EmaM$ zX9_^I>1=hb#D|-5q_ik#cS@z>IC-}Q`Y z-0UNvFho<+j!`kXZ?PkCYO!g#dr=a}Qs?QZJ+^r0VawA}5*fCAHIW*|AKBya@MXZN%|^h8 ztN$db^J@bVS@gM(_qTI?DFv`YHfm0e+uPh`Cr5N)I|(bZ0)PEJa) zq5J4*c4^iDdJB1!H>==gvH<^+4g0?7U=T$ae3lq@s=UuRlgQVdT`S47)0i?qu`dBA zI27B@n1?`83N2Ooomy0p(No=!^m^Xu%sX`v?-vi|IhVdfoPf4!xPamP{862G*=b4| zpITK`#R27|xYW$Uf`o)jaX_7T)b{Yd4URxoZ-v_*C=DfD?T3kL2Y4<(@;eb7wO?vO z6*O4b`_*$|3!82dzP_4$J^*mRvbOP&oBHA&Ia5N+T~=n@GqbbWj+D*b;gQ2&m2E}W zE_dw%Gd0oVXgyhBz2}`i8#7EK|6xEsdfw@vVk`^1p}wv5$D}GE3^dIlj*Upp#<*FZ!%tKjf-x{2BU8d9UtlcTCI%DnaWrQQLNEx5SfZ2ExFEW{|~U`Dc4_ zuY}_GabSr-pT@N(br$O6Pdb42t)-&iZK9;~7+kqNKvT1~=U}rBnI+}vfPJD_O`P{` zJ0O6PV)$(*S70F+L?p6d=IInz<%aC@OuCgbQ-H~omIhZTnq{Z>CgSbW-CK!>5{;M} zcc;1dk}jLL9)m^PJJ(qHxN!5XuSc)R)G5eArvr+I6HNd>7;7Z-MW=P6v7`Qb_)mo& z-}FrC#8?8CtBl4e_ji^Rfl)1v!m4vhL7!o^Z1|}`U@y+@PkN8K(xRdwr++15{*Szd zfK*PfU6l$3*RcVX*quNZp&B9Uall)=-4qg6p1TQuD3{0uZERWu8EcHsIojLYJUqhK z8DKSB4)4PEe}WkGSI@Aa068VL)Nb4^-n&S5jKGj~zPa8M`|;yP#HRr9s_ciU=lK0x zJCdR`a}G1XyqtD4dD%OkWgKh!;-a4pxY~_gU6_n$Y)-z!a&t<0?Fn219bg8rLdp|9 zzADn~^on%Pp&Q21Yui^%54_38-(?Ul&juhoxzr-nF*OQeTb0&i7%w*HQ-SOJ2wm>` z(^VuBLVoCRvlMxXdeUR`ho)D3TeB|H?86{p{AP#R8Pp;xQUC4D>a8CDT|WY z@mf%Iu!x&eUR4M?&e^|5Y|CvKCqYfFWp?f{#}{ecw1-p7oUuym7%R=?w!I6Dm|fBM zHBA@9GbX$Wi_-s4N*uv)Kgx* z97}l>s8sSxHsE+-K$&zz`9K5{4$kdO9`ed0S=;1WfqCCmJ}xF55U8GqWzPQ zkQU3h>L=7ke8p;-g#u}2%+(!Gu%vnEAve&lv4OS7!3tlS!_R1wf`?cUky{&sBxNjdO~KSFQx3s9BD@F_{B=zrw) zgc`Cwy~xPiSPzW%l5(bk8z4^~LdNyW0b6K?wAli>G7~K1TO`d?EqBk2UbL84XU_18 z0A8IEAlh-FgoJ7pQ0E`V3cy4|9fh_hdX(S20JQIji~OA;%&~4DpQ3G0QseEUB{DV4 zoRjM?t(dLslXN`wUbinW%27Q06%MRz_^=HYWuL2jh-8BnrW*s&@R%@0PG;oO$~6iX=H}SA6$zN`!!fw|H!&7ObK3)x?6-U;a0sj2L5b@tHZrJ^X=j+me;{vE$Ab*giOaC1YqrY2v9+B)B*SpR_i`sF!M0QrC5AA28O}A8s zogr6+N;YJH9}=-;TE0L2#Tv8z^=&oG&}c@`erPy57eb{yf9uPegFhB|nC^0`!iMs! z2L55@w^0+gzZvQ%qlRlh;40`T1+MPhv+4I!co7B=s7gzh1L>(9#>iVh(gO|N6w9(X zC-EQO#v|h;7(e z`!efH!S@$@OZ6k~U&mH(GiWrcE|G8i{T>~m3Vq5^AnAe;PQa(gd+D|MoFey!KIF*1)rZu{p(6r}7&E!@%cbsXxc47F<^rf;S+wGG^yf|iA2D?U zgIJiiPz~gua2wWtD}j*`K?YOLr7@|w$jJ$M_$i5rZ>!(GFYcQec=-JldPE+}fEhZ7 zt%=mdP{%!dxCcJ+hQ=P+e{mR)biYCxO=IY+leNr<1jD<4*XDX z*Ize9G}AHZ{Xe8U1a&fddwXz(y}nrYU66Jh{;FT({>}d)yvhM<*iahw4kCTTzXRYY z1c(~m8M-20|CrhH_j#+!21zTjjfs$OZ9W^Y&jh&A@16dDJ&rlE?e{_VXeMOl@hCc~ zxgGk{CRXL7JWU61oXY~o4tp>5w+DYvbR=Y=flmWVJh%%R+dtntijBYUvAs3 z|L4X1w?$`tj}D3=eA$o5V+rE`mq88EeE3H;C&lVT4hl*%AlmYF+m&~<1BVDh7p4%w z|Hl*k+D}p=Xj#?k6lC=Qu>v$mGM@_w?kOcD;`ZFPBY!66C)vLrl2HS&D;9=hB0t~K z!~n_S|HpNyU#CEo8y{ZWul9!o@7fC~5aTEJ1OJy$C8A%s ziTmi$4{tN`gpPRPEQY7{|MqwOgX|qL8;b+5aSdX^CAzC~c$F(x`nmr6{P}Z%+v-nw zBf$mHZ&f`CA>4M|grS;gWZC~xS^VvFw4rRs*9+HuwUJkM63nT`7e-rZL3e8b+^O!4 zb@~LmhfwuVpvkeQM=@~%ng)m!%+Mtvln-#kUR9Uhzci&-!9OD%B>%^sQ&UsN>sF?v zr#sGy>Am~|cGux42Cea*R|w}Wkjw*_TK?IIzKKBSWRu~5k?q2Meag4eg*S?1d77{R9QUsC?~9-X*#@yK8FmQBxxTnG z)@rh|vribM%R5BzhwT2h622}qxM@YGh|Xr%a=Wx@0k9ICrzwo?31pK=fqYll@c;5E zP)PgB6k=&D!_#GtxKl>*ixhXQ=fyyb?Xo!Qy#L45^{WTn;}TpJ&JmCj?Uby|&I0q{({>n-&Mtkl!ZmtdrXx%kH)4FUVa{G+)T}!(!95G1cjT|d+N~5s z%i*#?F7d9=u~<1*29qwvWmt%ToaYsOOIpHr5PDP(YB#Jq%lvTjynX@ZiQH%e%{V0^0`72*uB`n+|+nH9;}wq9^u`uQi!w@#dRFJ z>=!a0xP73cq&(T0+y?J)tb&3ihlNBqmzp+S9o+*Pow@okkKqk-<%YE?9ncI%yH_+s=7 z3>~WFL@W}xTCaV#0ZemA!1Q7)?67qTwRs@XS%#^@mY|bd<;y7t^wcly$kQ6s-VVcT z_|@^#-@eENXpTS=@q+9<%b#baC>D~a1*6vIgR(u&z<(uye)Gn-)_Rx@icC+O z``V(r6--QE#*VjheD>RaBrq*HDsl2}D?=@|PRIvbN%Jk^X1dE^$vTV{c%)^!Cl2hY z2Cv_NDpu(RTC5e()I$I+KL68RShbfBZrtZ-D1D`J|CF?<$ zZJGmpYGkLByy=9?obe(K?oD_9{Am!b6!#7Sx4T3}7)03NeEObH_tawOyDzhX)=E1b ze_iMOyLXvDZwFh2Os1%+Bs@+BYViV~!W7z!v;q*=mF+Y%2X+>a1ZrWC#II;Erx`UZ zVKO+HnyC&dnPmK}EXf<6?}8CUmwy6CBO@L|hJfD!$wGZjmx^p&vLmlV?BFZcvy8)x zUk<$jOYsgUS~~@4_VB%Ia~=q~(?WZ+aqV(Q;h>6K4N(xN1j0krqpw_pLM}=0`e%a4 zW({P7neomI8uy!y)B7)j4N`pBC%e+BRG~qM?5gC#I(~mevx{azOIk%LZm6Du%W@p5 zkUY~SE+k!?3+|Ep`c9!JH+JLR@G4PIU96E7gzX+6Cj`EzA%?QEO~RPf=?BHR^f=y< z9*~y7kn`Gc4$RjJOUHno5gm0DfT7M3Q)<^J_0$K*c?K+>xOpcxXEn7y3gt4wO}h5X zcHceKYg}g zNS1UC=Fco>G_DNWMlX+I-RHFN&a-8cF{xjtW;1@r1T6;!+&=*Os!^}SpF7Z{00kEY zsT4g$BW@z+>G@YIt9AQb$N1fMICk3H(FVTG0(jiDudURsExqw#0$rmcTRaT6I<350 z`Q?c2+PtpCm+GL^VHk!0E+w{6wrm1xdd4#P(UUia#1=YjYrjrjiWF~Kon~39N7`mc zZF13#=$%}$TXOoILMserF$Ul305RI}XSKM6Y@W+=11xK;2-4Ob(SfP-I>m$--vgc( zXOP*h+|a0cjF7~#?&=raut;tdrz34?-6vusV*O}86JPV<`l==(Moa1G>DzlqT|I4} z!O*f7ou5wDC2?J?DYczH>Are%dFl>ktWs{AJd>8j6?he6CSgd+@Tz6JL_UH5 zT>9^2Ren1H^OG~I+?P966rO5#JX;NVPBQ*HB2c?WaKpqll|(63jD)8gZ*o-g#! zlapedY!p9LmN(etcUlT;N5yYiXNvmlHrwe|oMGDiC?eYQOXa4a(ovkl>ps&S&6gMB zdc^0t`3?{%qKcg^j}12&6c49m1C6d=bVHileYuVeuiqPJ{-HU0U$H-_d%tze`PmY{L`h08_PmMa2`dZRU>iVY|J9pV}{ zc5Vh`Zyo+o(gK@g*P3Nf@&1ZE3Fr*jBvuN^DQvJ(;`h}oY!zkjXw)mv3Nk7Q(Vl&Ph#j*>Ni_6H+w8~Tit^#$!rw#F1>$F?7b$d~8T zhZwzzXF7oWUbnqU9WAB(dm#w104msD29QD7L~pv}#x=3HspsOk(C&2ivtPog3H}!@ zsxe=r@bqtcpW-h=IG$8%{&t1^IFvCtH}eD3NgzSi4xrS|f)hR_rUdUVNGJ(3>W{!8 zd+YD>vqVm|u#=E8tiz+Vpw= zVCX)Tzd5J#myDj{A#a=)8^%jm|8jKe{ru;{+HtHsI09^|+{C)L{y(2z+wL`dqM z6B3G5ADwqZ4unPBLG@y_G(ygq4?uX?(skgAPcKB|JAN6v4EUciD`?4<vB;4Hkmzc`vOk#NU}aHK!i9n@m6XxiJY zC0>kN&_F=&2+ryLeFW>SRQ+tEv~ej#k-iKCzmT=lL(;H9Pbm{qstbT1UNS>}E*&+FepYqMWHk4`oUPv|Zs=v7kZ6R$` zp^4s$?$ktDXSxCxut30nT=&%7X%AcZjPRcXDy`RgG>RTMWo`^g6|1ZAF!fRfO&8I7 zNt|Jac3^FZ4K>?&O-jn3>((fi5;Kt?^rj1(+ZeZK_B2JUBD&r55HYH&sd4n((DeQQ zucJKeC}-f}aNp4dk1q;|vP1A%jb$&;rjh#@^VW9X76=7Zvf-cAyP*wJ_091q@3&DS zM}aV=nE#7cDzK4ijBJjJNWY>cAH9#UIkC>*ga%&ve!oH70p7^mJ-O?YvzuLfd5xa) zT29A2Ys`x39esF8?Dpm$*A1GWQ|(h{{b!nV_uo_Yf8N30Om!2O-{5$0ah|`$_*W3& z)-ye%pC$EPgmnHVRv&M?pZoM!1HcT=Q$Cd((@s4Wb^MsX=)QYM!dj#wl8Q7^Et`xP z{f!5^;lZUb3rLgFX`rXFsfn5I%U1N=EAR)R2ae!KzQb=iyH;bC z-P?HMF1R5tUvQ(2v${<;;N+;esj2(889_JctTh7VFWpgy;jNzUb(pV0zzPrwM9f z9L~MbbdkzXHWs%<{umR9D%}^F1a0G!v104$uQ#YoJr&gV@hPL-Y{0C);JkHB<#k_Z z*Ya~&(cWedclIq~uh|!$?C5^J(FM`gP9bue1$5;Vj);FD)-}p2gcR24>^->qmun## z=;OPdtw)(3e|hhcfXMHAEn;idM>xpCa)_lA*p(+=qA@|r>F^>b^rz@t86NHhz>%3F97&+AH`Lrz2)Yot#D z_t4ZT-Ks~k&%khDYu`?B=4lbnb74&^obt!lCE@F!VX2t;Bd@!sCZn9!++PL_m{zUd ze(aBbzu@q%V)3)46_48PT>K~y68(LCy7&i=sn2wZDO#i%0GGG@+^!8gEJqT33ZY1M zTgiZA2q1`y!va?c^VsB-5o~vdJSJS@FQOYj6)Q9eYE%`urQousJ7V ztj2VC;2oh=Da354ZiR?=(-EcR8J4SvY8S)pD160|G4kxBEE@DCpJa4JQPwK9U3;Dd z!9N~qRmBXoU(%IsM4#~YDy-HyoKpHu@p4sQ+y&L?4@ZV`K=Y!|dn`bF=s@0h+Geg4&kl$4BhTxRsVFsXSiDI>oRuEVIt$84(Uq z2cccP6#s(!ETT4|F>j-bPYsUlrbk-E>OWBGjqpT^5k+~#A7ALb{jSGcO?&|AVu?eCuxQs@4RwX%9BUfxz5PB%SOny zo_#eN%f}J&zuyXIEt8m?Y>NjHviNQXHzc_u3|`Z{SywXGQmv+tS{Pm!8HeLfH-Xdy zZBfmWauf<5VUSG~un!WblaAHfsLv;^ttj(Qtu|bG>;Jw|%FTFrI6Rz{DwwIIZ$$io ztP?>`|NYvM{dj9veo?8smMf*w`>F5?ZaO0Gq9|oWl12&sEx@re1a4(V!vU=XTPP(+ z5k2Z{lm~@AoV{5BY^RKDY4j_tW zpE;J2w)-`HStqpcZf-4s-UN8uOl#8qvM-+~MS1bG(*=1-yK?ZyOx(6hUrsIn)ABTC z@ZR;~q5QnfluE{Z<||e7=IF3yA;Q@k`#0udR_NDfHcsk1O-Y%jRHxwi`4auxMKDH} zh=fXmX-BpIi*BH6XqRiEp1JkKuz_%MZHs(L^!ed4PGZvCz|Mf`K6wom!vX6>HWF9TH5@f2C0A(~<1706+d@*Pu2h zUw%O#j?Z?~JF-^uS(@bB6c_%je(oH3S|K2dQd_g&#Yj#fpc0dZF!?;;!b97V3pYNt ziuFi50=Q)g+q~Uo0DAiEw|e4u5?!BF_8{=-WLI(1@zs=+{rvyCwLmP2Z2TYbaq+Z2 z9{0+G^wzIfCg-8Gen(tPYydq8OHy6!wVQ=!Ey;2?ln-5$_3-Vi<4By;%*l0~NZJrDHO zIwSedA1sR}_CaSUzr?qbV?*G5)V+dsbGdCiz9 zqL!u?(GtQ92pBz7*sp>|=f<2sG2}z1hFpBJD~;bY;hCP{-W;{lgkwlQNLAFs(%I5c zsW{G%6?yuhyxgb|rjPCPCdToM&(Qabx~R+n{F+{gHpxFG2L9-;79GH%zA=o#XPVBl|o!d%E#9 z_3Tcdb<>iYB5)HXP-SnQe!2hfJ7=lc$4ysp+n7PQ*$v7mo{gzQm8T6Q7qou9w6x9t z?}p*HI2Z1RJ=Ow}fg{#>#&p!+8LuaE#}tqu{o&{ywcCKKv@2UPaQt6xk8s zG2jE=Y57U2rvW>vAY0Whj0Y1AY0`!5DAD#6_MOWUDy9aWT$>2sX%c#(1l`1yQ?&0( zRgm_xt+(9OSuvDuOHH+I;Vz4(9X+S{IG?bf52s6Eu(Gxcu`IFGbphaS-EO`f=}EkD zwF>ES!?2C|g}at&GP-a*2Hd2j(g5Mdj5Msma|)Q%fBRn_-^VcBS`& zfMlvBZ!>E(Yxf~Mn*_v-!OkT2$g=^->4&esd%@UTnEz2{id?$;C7$8OU(*Q$T-B{@ z(m5*q_}K>?zH;YE>J{2`cD!k|j7}R~59aHS?qg8qe@n*pl1h@~Q|``SpS_K+@z{2? ztHXDJ6V?f8FzwHsEZcK|AfK#v>VI+SUmbt>F|f~Iq3eHcU)tJxGX99Cl?Tu;883?; zb(NoDw3?s&>3ZjmCwWkezXV4SM^O?nE?RvrY>1_<=;iih%w9=;n{Cc@2heX)M#lUE zz2ga?6yq>k+2#2aQ^}2wEeQ8wJQ>O8o7tWmAat1`#HQtvSD{5X4!yfF(20@v{(?%F zPMAiBO^#8JQ}E7hQ#O-?QQS*%VC$!nvC7r?x>KT!>LmGQz2_w`kEN&)`97cCQ-s&M z59{X#F4NvE?R?ZPdmhZnJ-63Cp9+;EmSlC|?%+l3axR@gzbO*>6uOclq@%HHW+=N+ z8gJ^u3?WL)W1ZyGoh{Ho0;8Fo+LAo*z=hm|XneU6EauK6ws=DcKR&+`TfOEgUv-!(? z4J#KdrSa#$A3v?zE16z!r(Auez|2#+yE% zWB6E2N7LqdAJuxVmdeqzGHwshSvb2sS>Mq%Xq)Rxg2)A!HhZE%bVaJhvFmZTnSFPq zi~3!*Ox>-3%9t)X>D+W_(Khgc7P%DNl-~2bJ}4^1j>MUf9Cfc&sa+#iCu`OuQ<1~d zlU~MRc+T!KvCpVjX#p%-8RvR=4EEkWeil79^sGCup1y1_K9&89MmCwrVw65r>8Ebo zGosdN=I>KTD)B+IRKkI!oXcJx;HjpiP6}Lm@$kn$C%_P|VU%>nz(MQpM*opWlr9E6?O zHY&m~0i`*7QmiMzd$yP+1sS?yUzwKi$a8VdHT1Z2SRW(&j{!K4G(kVAoBQSEGt&n+ zh|^|X8s(R-SRJaf_K96yE92EVYb7W(;6XoVc(?hZ@JYz;Na?+^&x6(4luq_~Rqa=Q zZK)AI+Z*kWe*8YLz`dQd`^JC0yI;$=9l}Fh^nPz>tXm86Nofr;mQ6CQ{KtTY7gS^i zG0AQpDDMJDUAO)@r0`{3X>EOv;L@Ll!^P2XBoyXV1`|J5Xa@K+f?9|Yw7VfAL5f%s`$Li% zx>U?b6_oX?B(_IT5pJ-K(7iN6Xn-q~@inOBoBYMjrf>urVCKIr*57yFW4UraUJZf| zD<=G+QPhqqps-JY@oD+@8LboR*G~ygiB`J24O`3-&7MI*xc0~={+DwQ zi4sei}OqTN8~XShno( zD@N91%rQ;ez5RT`pAegNz}XH?g*4E=XXxLyg_6WRQQHjlNJxvKI#q&&O%U-NGLn|) z?~ zI0|t`uT7I7B4t&&or9O*U;+JbWbt^6W7QVJ^GQ@lG*w6KBB}W_AFtTFypcj-q91V1 zr@~NwZXp&)Wxf_d%h!TrX%@Am8F%p3mb-PsWnFSk^G|iAY*M5nG`k?5Y&RHy5{JNawB8`M-VPhUfN5Z`-J)K14T2HWh3wu*Grh@S{L zI(OL$)J%gae_D*S|NYJ0+P=MCqF*s^;F;v&2g`pGUOy%3{<_+xNyZJ)!U;#(^q~uq zh$=V`9yFSPzNv&~x`uHTJRM*)*)9CPAh{DAY|e=7SuD{=1q?xBJ0QOjX? z`bmV>(*#T@q0Pc`4U1mov%scdOpt=8$|2+^DCMjZe`;>7PMk(=J~UrV-W8Dp{uqxK z>C~KeXD!~nID@wME3kQ;%1}nb2tRfh*l`vh#xt5~qqg}%Sgw(kzj0xBk%7TvOMpiQ zb#NRx;Lta}FMrl}?ZKx_I-N)%mfCk24AQGCh@@OleeXzJ|IvthKS`wL8>%z}WaJp=0 zCPlojv(xt0m!be4JIYSy1|-;~!-3z8;5%N^xw}H})^1SYvnVAPCDi z@VMKol4!TQ^6&!e;?z8r{w^y-_jD_l0k21jRu7@DUR` zn3nIV3#Tr7kNm4sT(Y!gtGL$mM1*va(Be1F9YVb0sS={uTRmbwUPWU%6C?=Yz2V5E zfAS;}e52wIwcBRr&TKjNr-Ll6NN^s+yTafoo#+j3e<<@hu$d&0su~$|foc2!yQdyI z@5;eK5IoyYf&y~bY)PMLOvZ<)U#9L-p%i1ts~EjZocZPyq1G4Ml-Y%xed?l{*B$Qu z!xNo*F03397w-?CiZK!1jX7>zAHKcMi^lG176_>K!Ksc6PPc5EB0nhvwyh`nldae+ z1VSjAv|Qb!D5D3enoTln33pcz55N%9ddDYXLzvNHM%lZ!dO)}CQ0U_Qw6Lfsj|V+y z?W(i@Wgq>6mXV`ajT;VkrSkfDga)S($r}Ro;tCfmnF;%yZ6d-E@-6`;JOLGirFnWl z1gW#5DR7tbTBMqz1lfoekM(;+^T-?5vp-qx*Rqhi{B8siGYd5%;O(I;$}LGCK$(Ib z%T#!iXY>jrZ*?AkIYw=69il;sQENobg!9)f!ZR8qD=kVcrs`F|KrVXXJyAnZbydFT z1BjasV-btib?H_9(}iD!08)!v>zdxb6yVD)hq}v3u_>MtS{0Hz@RqzpKM+pc`7sX3 zjR=Y1$Lg=~zKgP|&&}rzVjwbP7tyUKkuJvBYtwn2N)hX6W~>j@Hqyd5XPP9+I;D0z z)xm3+Ns1I8{H_sc3nbNvV|VnO&3@L&HMrWPh_;&)|09YNT0pzhHaQ`j!LxbLE)oM# zpgXK!8wT0}f{T*^=`GOFVsns&!$OTNxA1Z|`l3k@QB>AIAmgL(YdaG!+Z`bEwTzAYR)V@j-s{LFPw zG6Zi2rqsF>qS;!A3nG%x-~P`CR0VL2ix&@LLi*Ey?< zfU%6F<30iQV}bfRhrJNllgtyrECubhvjNE1)3B)7mP>t6qRHw1a+uzZ-l_uLHh}TwfuC(i(ks;o# z^Q#LACBPY7u*UC<9I<6>gUA` zycFmD(}k?ga`mI?q^AvAINwsZ5N-V(?~tQK&^J+Ye(G4%<~ z!#VFSSU~bNn44yDAmWeq|15()5q_m7nrS^VFgRW^*Cx|YbO!7wR8d(LR%Exr1_$IJdfjT=|K%|-* zHpx8AL_D9sXRNr~)Aak$utS(pJ5#Ze^KKT*hNM*tRu?Lw<`XV&q$)lJuCR)_o+!rN zb2vi4>FAdVLC;hNwd7IxK_4dBRCgAy*%O)rDd~g9cRZ7MqB43z^B}sdKHWWr($q;?ZTC7$ghy_u2XTXrru-ewU{dHaymJvB}{IX+?+XqNhAY0{hCD z&=F+|Y+PFcM5y1Zm6V`Tv5IYwaSkIH zHX-Y>sd`Rejg~Qyc;}JoO1Fu}WhTMcFKO(?b{r{|>dj8zNC><+JD8poojU`ND}~M^!E5<6L>H`5v+8C|SzZ z_X_zsYUg&7bb_P70C>4VK}<5GR3{M?%JRH;ZDEZ40H(V5uWV z2OqUOM#1`A=nDb#l4H#g#>|_{o=8pGUYE{w%KhuynmWuLM{;-fv0d9%BJ zi^>Bp7}H$?9n;$>V%fecqWHC&)8N!1PY|2_7qoSieu;{0u68g4n~)A-P)rRb5>~bD z@25T;`zHsrxKF-$Grt|HxGw8*QUBS+Z^*ud7r0XK6Tlg+dO%C}ls_olGyR=5x3*Zm zJgxxR^QI!22&SkOH`|Fp^QL$KO*(mh?guT4W__zP%(UM8z(rqzzaoFm2PyS*YS~O~ z+Kv}R7b{OIVJcBr4f#yOhJSv-$>r(GazNEkhABiJ>=|BShjtrSIrdEcxs1KdQ0%^l zZO}>A_^V9E`>saiQKI`_k_JEdOgo+;WB1NRuup5koQtLSpD)bosD}MuV(g1co1Oqm zl`^8}msV5{#}yX0cZcug@OEpdO6fWlK_ZeO;ET9eeTy(O!5TYWdl<{t#&G_uEJrkt zU8ZZ$fS>H0vy*b(7uMxekBvqW?ePK0l5q2*pzB6gBr(s6D5v!~pxz}s>VWtEKINb} zZYa-UcJKgtZX_bWg1oj%ACMt;^*|YS8rV{|5;tP`ao-@yHpE>zE*9++E2@m)dQHz5Ycwc?EX!Um* zF|OnG%ar0~UeG2lWvO%?k%-(XlveZ2y8V-vUwVkI7kmEX9G{+h|DTodw-JAP;-$~X zYQeWqtot8~oVG4bqu+quu_gy)F{AvGi*Ie|^Z#-oTMuamXnV~o6`r4bcw_mu>8Oe? zM}V$6nBDz~EBUH-e_tH?pPxFX2qT0YQGeBS=h7{A%D~y-MB;C%xG$9Y z>W2ok{u(y|kxvW=`ZfYjNTjEy#_lbS@;hey*dOIz?{vSa#Od79Obz^oY@aQs^VPrW z9{>44*+ec{qH2l<>$o(k_quSaHkOLY;sbxr=*nMnOSI9O~|tdhVtGPxYYr<4bp*2ujV^0%#^vVF8V=I z{x{6d=5(0z8@$8ccNnwyD`sZgee7p-TWo2ngptKrxvo@n$s_-RgZUp0BjOHs%C#Mf zltdQirQ97UH#_X?^;}vSOSAYd7gf)>7Qy{VetP86Kv7^3&hNgK^h16yqKP{wVHR(9 z@6r%hn_wl-s&yAP_Tp$4f9juC+Rh!^NJVJ!&4Hz%Cu$=r!Nsg|lBwqK-2{* zL0kCUU*G(N6x=FPUM&6AzYk;SEpac*kW~%4Pt7W?_}9f(viWDY)z6n**8kW8a<~=8 zX%%(brO-Q~1u+R;bDc|Nk?$E?`G>D2L0PU|e`%_!63X?!q-z}B^kX~OOn|31&Ub!a zi_KM6W?ayM+tqc4$}gFC&o`oj6#*-&>RC1v_t-F4K6Ys;*KgN^wGz4N|2O=n=cL(wh_vbAl0BC3Mtgtg9T#l;LR;Wqwd_`9Wz_TH6zOt|EyoSldwLRkza$$ynf7#7<@G&Ht9-& ziw{_S7*@GxP(bgar9Hf~uKwksDu!!D3ZiqQmx}IY48)A6+E^{^mj6CK-zGDn;Hw#N zn<;i)TpoVhYW%Kue&4etmOfLS!S!*sRBvxxT99Te2ozcf=3h{1I}@yY*R=H-9>De-GGH*mwCc=yutJp?Nyy6;T&4;kF;P5H>Ao6@ph zZCtt&KN3NIOj@>V@qZSFyEHBTaN(Ey_4raGF;aVJI__b>w2dn}rAPn6H~#Bg&5poJ z=V#qdVloVtR#0LV>_MHCZpuG2D1Hb>;7oOgL|ODw;I#P(+^PG?)DJDG?Xp|BukT}> zIQv^L*9YtDJ#$&H>JJU-z*pC_XV0^{OUq?*G~CME*~n|MsnaTvdCXaZSr_Pi9BHMJOR~ zr+~nBKV;MYD3|>ji66iXeXE+5LLzm3bm!3g4-KlypF1eMh@kW(qhxjuy!!pOevbM- zFj31_ybt5v%0>QZ=bFU{D1U?8DmCf(4_T@8*SKM?P4uSbZ{K?f&wP&ON8a7?70f~`(4@)-!5byaHqWLtqV4JX;!qMhTvd*S4DjB zaldQ3{Ps2c`g5{NRe13S)@Z?X1^R=;Rx^1Bcet5j&u@(=fZpDv{ z?jKf{xlVVgO2a;@i}OT#QzNUsqbIO{ou{N+UH*;YY!~G+n(dFNV+cz zO^LRgUaG}>-_m;p(E?--BU`hM{^tv39k4Uk9&rJDujs%J25w&?n^!R>W){P=dxr3S z{emtuIhgHKUD|5jb+HBmPxT8Q2N?0=6M!99>$WwhbOAus2ZziLFD_vg-iEku8^T?7QcrqcrFfLBFknz%Y&i4_gQpW)`6^78(C}jwlr4|rMZCP-p z77jmHNaHNb^&Q0nRxoJ}7!ZR~~pwwqxC*HV9*C!C;30d&*FB8FY7ikx85fo@`ht@~@EVQSIBh@&@9X=^#NG}BJQ zDrss6yB=p1JB3|a&rTK>bn9UB=pkWA`4=cPh`P_gDW1Z+fW=q%9Yz+udINFNpy(Cg zUYl$cN=`3uD(F%A%sHCP$%Du3I`o-y(ldysqydhmCV(B7=G>2PqlN&ggAPf}&Klod zkue1Tgh3UX{5I>9EEu=W@~np18bP6R#e?a^wSoZfae}s2@`}^$PeN#LAERc#CLXNb z>O9rb%-Oa46`)2FfbM^EF9P?Q?PYk3T!#Ku`%_Z0&PKv1uOa*Mt4Ra_8f=an0HVK) zed!670U?J|!5jpRmIV-(aKO40PL>emj*H#lm}B0u=7+~mr*7RG92kbgkGOns21E_<(ih;i~?{p7VB_o8&<~y2qndC zlF--aGV`^B+NF(pW`5ReIMT)-bg>CU%mqVBZ2`Cm!RF288R|*v4wg!g9pbWNeZYmS z6a(E^zxz|)<6gdfM%D=N1VBDJ7VF81q$lBx4|E*ZHZ`x}ZCO0gtC)7UfBt!Erqr77 zkPhTbo%dZ^gKEoH5O8|Ir#lk~GPvYn*|b3gK;EF@@dBQ9Qj~!UaJuX?Q6rIWH zODa5gnS=mNVewdAPaioVR^vFh^#d0pxtR)1=O;m6D3zb|T=IqAb-^vCZ5Ciz$X+}; zIpkE6fM4ro#Mj}Qv`x)e*XS~HIs1ItI=jmm4?)xU5lxCT4tu3`lr)0ihXr7FhcgWgn~0}^(j_>&&=7rw?E;HfDh|v98L=E< zoGY$yGLG3nM$lu`d4917pE0%4hv-R3Q8sR`%FA{P6yFuWL=X@qfG8XhG21%eDEd*u z?J(k(B2FR?{c1I9t?i3%HG=Q)Eq))72ro0` z!MkVBF#lpo*w@Ea1@0n!&~(8Q9h2_iki2&`Zh;wR0U$8~f!YZGUOI3v>|OUk2d1lN zURmlT%u5YyIl_&jgS?1!aM|=@n6>OWHTDo4cp`Rbf%(9IL5nbYp(~Il{ve=?bXxD) zRX(t@ONwztA<&9oS#!gL*Pb*Z-@11`oIz8(T*Kk#T_5ztoZjr9ZyR+#6?ysA76R7J zynSG9ly6`G>|kLBv_c{g#c7WA~6FKm(EBf7SY%hA^oP1CDcnPV@c)+ltu(-3Uz zqh+*Bu!bi9!c^uQ4IOrhQwYw8{SRJ718A%+I&;wF$($L&ctLbPR+JGx@0gaGMtRJ{f5zqvRv@*)tD-qFDfwbWN8CV{&;8A1i|aiP823F{E{J zdH3ybLjWc!E&($9euPj`GmgMso#w`?@z58qVC0>?eZj|DyLXg956Fz7TL?`%>wHMI zzQ+v&m57MApt6UK81yt3%KrcJwhr~<{ZvP*H-%41EGP=uL1-YYGCVer)saQM_xj_> zxy`V~fovzV7107CgEMIA@f2QT2;fv@^~?O$}ACY$KHL~y?i-eXg;7VO4Iug?J@)~SBsE|#h>j5ZY^3*b~ci5lb?lv zHPFX^GHMUqcTk555U%NLTkAMYQ0u15M1&V3t{bE01w&OE!>Q z(ggI>fVL20o}N)Du={DbkwZ3sU7YC%*qk{(OQPw=oV^Fg57VTH?rZlA`vSSydwGQL zX?KT=;dF@+X*V?=t0Z<1t>9AMxS5#u)jv6W&UKm1rrZb|0kY^ z5JMt{8%;98Bh{NsbQ)Ag{V&&->O{7CIPE4S;0>Jgs<+g~v{#z#{CNuLSf`J6v(fc# z2h_C&>O=j3>aD8w`zlH{nYceCbm7fR-N<+8(1%P4fXjAP?}0X`k0>aGo_V|niS$0Z zBjh%bj1;OH=B>@@Av=KbacLdsLSh%uOX&kg=u`3MM;e~cM?rQhpjmzFD8h$8i*UtmpFS7a2yAU61Pf&e;~ z88eK*thOC_+}(w+nq51w)CR_shI?k(3bQ2b9`&uzkFRwCfjKSE*Y1KYmW3Ji$@T@H zB@hmReK`gcg}hGmECZMA4cPFM_1%aJRs#dEaNa}a;*y9FJ5)cwo!HUY9naUBRI=iHTHURRn-buOwV()tkmGrGoM z{4W!#11U2D!Y2iw=OAW`aCjC(1oLU_MnE^Hg@!t&TKDb22=|NZwOL&=?N#ohu;))# zn0r@LLoV-(YlmZXR?VL1P<_kDgvOm~v80X@mCmqjorBGxkMBc3o9SL(t88iH6bk_n zQDD*-%qbnhA6zh}IBw0Jek!eHPrn;L?J&!0SS{MnJ4`lHr9)5L`6YS8QIs*yLh2OU z6BACa*5vg5ykeO41*L=z*yq#x_;R;u0E%|<+Wq!;Sr??_>UaIukPMnx zd#l?M!x8Vf168)9O^G?u@B9c4^ zr>wRP%y~b@w95I=YlCHcbzz4xfDnV>T z*0+kG(4?jc?NWI;Cv7@dJQ>`wUR7ARzS|S+hS)jN&a#4rF5zEGFtky#a40@rdKa|J zuVNLF#L6IYA%(z4Cy;^w$vMk!x_0G-xE=hE`}*z!7h#LQ{%R4TLhCHTlVU=G+2%0< zWHEEYZV0SV2=lInRD%N~KMFJsUbmE)VV0-}|9yvSUJmJn<3`XPHg%*^=r6P{*le4} zltG4@wC$+&Mw4q5&BNwc3&72CX4;8}U_%<7)Bij;oQNB?p~LX2k_)JDSfnrf85*Qa7_Avprs#gY2ga_tcrUA5eriLSKE?ZSli02${ zIlgqrLbZ8z_A0vhbmU9s-HNyQt`>qMReogoAsuZPu9@5+|V^)-D~;`;v0? zhu*(dzmc?5(fn^0<$>JOG<8zlhm`cfyt)3@+MegyFr|zzi@us2B;HMW@ztzx2v+;^ z!Tsrquc*&TqX&5Lujmc1#SY5!V`h`_$$Q3;$_0`e3(W6Mop~*M5i|?vpbuG-1?wst z;j+&G{b@gJV5{I8-9Mk&dI`7p3z8`Zb8bysed$-qYB|yA0Q;b1$5vpNp%164HA?OY zIt4YBzAOYA4F)B<4t#oynRykLx9j+_T@QpfqF^g9*j#w1-#B88$G!kh##U!`^98*j zSV-wUG(xb2Qqc;~T3n#;uh{J#9+IfQS`oC>=`?dZ*tbLJEuXX&&tIy&tVt*ebU|ot!ryNm1cHBxPuKuLp`kV zwWl|00W?b~Y6os@v*aotC^*e9EzHvRG9Uf#wG!ENNVs#oNyd3N%7!Q-G1vdXx($JZ zTO>956xAJFu&MfZXg?j$?uLaBP>|L6wtXm^=c!wFWUM?3;n0n2H$HBIP%m2n{_hz$ z2@(Ve2F7Ylp@PL&OgCBaCJmAw3KQ}Nnp^q~5w_M^Gnic|LA5VtSk-#7k4aFons^)R zlOwRg)s^8nhEM^F94h@g`_5AlEBp$qFz{ay9suFaM+BlBmpFuwNqX)pN`pp(jg>KH zyNJyNAb3xJ$sw7b(a&0e5=n-NpFx_{;O%Jn>mXbKXLyEXV$@NNp_>R|L~v${dLJQkmgqGsV`8r^xR5$Seo?Kle2aP#Z?DEgd#d89nOP- zhelj6LfN2@C()^l(NPYqeZ~9LS1$8SmDxkfkWiEp|X<3ypiuCA!%)@^PRC@dtC# z5hmndTo#%WRP$W((tuzV#$9{b-mBsf5XPl}MTnxj`5^=gQ;`Otp3acW!QX_)UjEhn!g zof7kSL8vVQc2O8Y#^335j&F(L!nv%7eoG8gHaTc*Whj{Tf(0TUwwVqLKc5U2TG=5vr9UC9cp0I&3Kp zn;dk9$hnDE{sNC{5G67p6x9yfj0_xnBI7Rd7W%d^pc8YSLG&<`q`8GlwA~nlMf(sb zD$mWI=O?8GlTWwUGH>XWo#(uGe8DQAzl$S&sin&^g5>wXuEYs(nKdPLQzlF{ysVrzU5$U9z7~ zvqXpfS_4qu7IVgT1OgGUkbfiw0M6y$4soxcOw3hWENl=1dXCl9mR7Ak%0%DeGzsVz5RL5X9;tVRZMq)A`!bx6*(U9 z8%NNHV+H?lI1cKhIg?qinlr59K`ftJMRlXmpy*`Ehj#3Py@f)oORx-WGlR*PM~rX^ z?GnOt^Q8Pi{>a0*5>66KwB)VQkv}V1t2z@T`_7z3D?Dj!n9hVOe;-rD0ZKHZbE*{D zxG6BZtko1qAqk}UfL4V^)mv)|9(?dPC`st}X0Nk6SIoW_ip6!n zY^8e=>q*P6jy(8{%(lbD)XNKYsK7bfDkm}<}eHDfTl0@XA?Tts4y zscm-w6+5&DJ#p$8)bzIYfJX3M8ml=Uq1hk&e4;m?jh`+BWs(@&is(>zF+WQj)sIW0PTUf;-oI;O0U;p7NJp$21W?}*4kvD}ki zBjZ5?d#%Gu!lk~|hgmi!prjP>aMIL!{4YB2yhe@KK>RZdH*%`s zCW?sN5qWt$ z0txU7fug5u9+ug#iI56j*aA}VVG)xrkg;OW#>C*s!bV&=%jq%8qy3;SmEJLpfBC^Y zysUoktmY?#Xc*kB?d-+sE1C@6n7k{Nq|FFd0t#a^^`Z(H5kt8OZBxQB35Hb?vynE} z=W!SZ2kGLzFK{qujQD)ip>Sq+cwv;x@tPxE)Z#8QazNL+Lpiv8Nx<81x;YU=b25f;1rU1#8Z%GVxUm$!<1;BpRfQcjAEA zIVB$(tj4-#<#QExWNiV!2rF>33%c;#+C8Ss&EoVIZ}rgpp53XIzqA`4fINOErcKFTfa<^=iVG4 zvk~)6-GaY&s;h_VnK+j-mgkps6+5Mt9POj6?gFK_86~%9BFV4IVZwR)(V%zW$aQf0 zw5LDQ7Hp!6O?(#&6Q`!}W;E!=f#$H=5SIBii4Pu}-rR7dh|C!9I8m=ORp%itFnMrf z;tj*mm-rsxE_-+XB;G$nn`6e!md*{0q)?n?Mk5jGTVJ1Lm+2-$+f0T=$pl-+*N@rD zWW*VJ1lQOUGK-O{)1Y*rPabb>^WQRtZ=tN;LoVP!w1#rJy5OHBtwm22#g=4hiWr4#NQnh?_eV;C?@PSsxF&;BAyvfLHK_Sj)+Cif@09j zmu`#F1LDdmU%CUxFavr-6xe*@>8DgA`^c6SoKVm9?$4XQVcINJ(CtqL!KIgK$6B@) zKNQPgO8@#x4{_uivSyQ0PHx~T2%%>$XeOmy zZ073V2*-VS{in{UbNze!>Nee*qIS*X<%j7EBBXgQ>|lC??n1+pF*8#s_3Q}wSLVro zhEC^VQID(3Na3=t(~C#i1|Hq%$Bt#Oi-c;uK*Y0ipuJSpIn^!|xTE~B6hC6aaznAt zqi=?2C5@K(y&Z3u4iKFqG*pG+DfcJqKe~a2?IY{)&NQ}<(B|{24(ft*MT@1%DIHcBqsC1ANME^YDkhWDRGOD_L$r)(8>Boc_$Jk|PG{<%L#?SV zKZEY}XCR4fQy<5!_d%*6t8>W9C&uup#6xc5A6-f0nyp9OBzZgk18=0rN0B@#id{Ya84e7a_BI#d0L0{CjU!* zE^^vW;WL=D8ynEZ%$}x!zCrWu?l1?@qay`9o>TRCc{7lumasBh)Ruj~X#627?%8;N zPg_b>L~r70mG`RrEo$WPY6I9Vp@OgJC-RwQHoj8g$L8UXK?|3WJEEu?;8R%kaBDux zM+!lkn0a##wh-|*QHtuxgZ+5I2}H6(dHKCCU4vxSv)e4A>4Tct@aprz z?t7t{S_Zus|9I@nbav57s0z$8tglg)FK|dOohCndNV(*f^`7jtugsao%Fo&~+|Oe+ z4;dc4kekJW=KCdxN0SHvsAqn&1&XIvL;Jze`7h1G^=u++eOd5 z<+y_)r?>h!6lv{;gPSIK57jFeOePXN6cZr!W1sHgD>iKRm{1G7<9odo5s`aZkk_Qs ze!w}ECvVau#5ef5%?aFSZR4#H%ZcAr>zkkK#FZ*iIYGZnO|sa;X4+5Ijh?L>mWG-9 z@_~`?!bh_uBXJmyG2IiQv z+E`?d8|K)+llVb(TukykMlOG>MGdtqdRjLOO4p;9#GlLHa)N7Ms=E6Aeu6Y4QF_oAQOY1TEfOs~FaIqK_GJ zMXexAjj3tcIib$bc@_cXyA(9Pff@+7sH)A$_uP}y?Fkheam`2aUqWSs;0vmh_?~;_ zGd6i!*&q&{JfA|X-Y=eEt-CTgpF)1PIiIB$&W#((xN#$hY;fboEmPzJ#gg4Fm^SjL z8ME7~Q=!YfhoG#@DKX)mI3qgooSa>Uz5qwPXxEd_a9XlZ3weA{Fh?Tr;f*tLV_{|W z>rcie?Y=fO;s)I9QUFNArJ^3?-mDcW(a6{(()4aZ#S-N}gVR^a2%`BK z^S!6(geNjPis#CV^7G6o?JEs>-KILUR~TT~<$P+Iy!$6qysGwy)KC5{ah{epcEas& zikivD$213=4O(d&Nm!6{2+d0ro1M~#kU$F$Zfx}4a<)@zZL$(|a9Y3D?MvWwaPPkj zmZkfF(4ON#6DE77@7>R%p><^C8!iB?nR;0eN;;tRcE4|X0?No|_Tugt@7rgxD-C+D zOisT)f|;e=;T~s$@HMEj_ccT$33z1FlGLV?ACdM&gM%5gLxKV_RDgBs6Xv8M&Ia0r zy%Z`rm+)o|2SokTvbgA;u-V%qwbynk`Lc_G_C;z;bKJz`MlTjtf6OZhrck;j#asb= zG41gt`GA8^k%sKS1qE3|hf~9;4RvG2f=Drx7nEBOIC(3i9kJVMgPoKZ<9jpb4!@pPsZP;W1pCPL<9sGv^NYj<}-iDEwX&9 zzk`OiIh>0>&DawXvxrgTNpyf(enqf@++5CVmlx(^e#KCyuA?-2_Yp3%$vgSC|A(_b zkEeS5-pBEiD3PK*;*b`MqDS*E#k3=R6#=J%{^V_gdGw)^%x?-95n^9|c8P^4K1Zck?$&Q)ezu z?RhtHnP^`XshS~68;8{2-RWyMz~AFt2Px6xDMWxARo%LzjH=+`sb$*FOlL#JCQ!Us zG;1zjKtNj0={%O#czI8Rh!nhh&<-?$?&13u%q57RmbS7*YH=>vErg17<7~<-vKOp% zE6kIwKyy+nd9kmX; zOVM(C#y+)vZfakhIh0bTC1VW1ys@l?G?a*~Zxegm2uhq;Act(f5AUY)5QNd~`z{zu z9oysEwjernfVDO>-!8v|?w-|>cHOe-vFXkE=cB#XqP(6f^QEo(X(7EUc21P~^V*~0 zP&j1ECD;#G37*>f;^wvNI8P(b^Glgosp)^nJCYRxc$`-P`G(c8H2*)pSrk7mtG>%? z%SPYrR=?>fBW7v-;LJlRqC$XE&|KYAzDxgJYsyl=Gta^&UF^+VaJ6}ePA-1k))uou zyC|0^O7OFv;#lmai@7fB0`!S=$JCHMf=<_)$#@I2_RAI0N&$@=|VGYoIUC|`; zhz*3_m}nJi%4(cS&zExt@uYX>4VOx4o2Hl?I#0YT)x_jqi{|FC^Kc+pl{rRnBrh)n2(_+w3j{A> zca3vakg~A{+Fm7=;tuSkbMG@qSrA$Yd)P}~I}5J2PgSF_T3RD{kjCKe9k^(2)=;jH z=cs2Pj!1HEah>k#JPf!^mt0`u<9GrJXm++ydJe>6`Dv<55&2C zaePy_TQMO&9s9Zq=kjKSo&mav_9E|Je}04OTjA|3eCaZC$EZ*mUVTH2e8@Q8&qw>+fvUEMJ@*P@(&zC3SOeMEkhSzwvC2RDY>Q`Ac>YR!LVItb z-4UQHIZ6Ss0ZE=rYgj38=FBRAE?+VQZmB5GG`6eVHJuUQD%@^7{@XkzlILO=gpf(O z@l5%V0gwU9Td+O9vC!Z8?LtpU-gLx(&3y34r zcy-B_z6X|y?kIC6eiz2XeE3M0CF7sO8+^FO|L*>-Z2xI?Ift&AkX(L-%X!9<-p}L@ z`^Z<{kcu&1GdkkKSJ6bbflH6I9qnD%=xrLa#r5^9Uep)y@3mz zHs=Sv)^{{>O%29|k3Viq2N&8htBW*`++UQmp$D&ee{4HcrjmHV+~({A=%ff)bm#0T zZlk)3b?wd<1xZ=dp?Xs=9nY4$z9dH><*k1?_(Y{|_*~g=w5)5{oLjRS>NsDL zH-3kx{p3rL*^1+4M;AF(Rs+)C9@S^-AM@QuL^=2Vx##HV0FV1!iAu5ln~Ah8+`|@G z{h`iLh|ah}c8Ww&&q~AvTYv1QBJdf%A1jfe`E8b>qi{3M^-&Z0o8{9L71QRxF@pH} zdt&Zb;X>ojELuxS;!)#~U`9&f{`ydf|!l;gp{!#Z!G2f&QUpMAC{WO+YD!6l*- z7Tl06+cD{IGTy;lx|sitaAClv>x!^$-BNE&&Fy19EvEkAtMI5TOR5H|HruM7c0`5X zEjto_4otuCUsX+b6wN-zNE_xC+Kz>*D2nCk;!=)ZDe%A)qVW@ls8vp%^ZwSNsn*Oh z(-{ZiAo5|i!2s-z;%ScYj$AW!aF|(n7n!G%z#HxPB&`&fo0ct@N4N-Pbj{bnN{jnh zw;vub6znlSzkp0SWO(yop9P7XVQOWhA!+l-#$s}0bXs&pnB%s9ujbz1GAO=xDK zU%6xxWplbZB^urBb{6_a#?8kUfauZF`s5*Ii+O3TnG2=crttZjd1=?vqs<>vV=O->hKLn5n;Ne&THI={bK5)B(k|; zIghyzE-hkN+>Mv>-+~zyhu78#mi&urDzta{2+-MWt_MHLf=OJ}tWDfQ%-l*zznBKC zj(u7}#692dj}NLo7f7jP*aa*0Nq1aTH!g|a-d7tLH=Oom{N&$LDm`pE2`Z*_+=M6txCF zdy7`^9oyZ1NU#TJ#6v2ag)!)Mr^ijbVBTGiD3BAv=15TK)8LkgKKi)6rTWo;an}{s zK0GzD&r(0!@?AMH@UZg&q2!I04lbPi%;c+UrU7D>FB;iQG*%ls3J~j%pdwmM7vKQ+ zvnydwlWkPYJ$H=i?tqr{@IxJt;eFTzBMUj5UIcE`nu;`T#3b&bJO1z@r5ocAwY&R= zrt9sZd`-m{`;FQc5^Cd!PG5k9bavteJGcRicdC%<;CJJPc$p<}FD&^YX(ax|u@>27 z(NbHkwJ2lbOs#FxE)Pv=aIyc+{b=wxFKMv*{wkMA@m}KMgi3lP1PY5H`zbZ6LandB z&gnrnyF>G_m*smQUQO#2G*NnUtOoiq;IaDoY&n?An!?rh!2Cx&q#dK_Z#%t6o>P1( zh@V26#0B@J$WjJ<`~nP&B{$R(_GKQ>5olEp)@RQEF-IRLxiSN}ZThB{$>iBJJ}cIc zCwt5QTSw+Cxd55;p&S*Jo6-xi!NEfhWRGfo(zM!7{S6N`whLf8&~+NI>p~2lJaaz< zfmdT0B6YcOo$OL~j>+&?YxN^Sj(H5jk1ceaG(c}fZ^7vy2Huu$qe^A2p)Bz2CrP>Ac07;yt%O?1A z8LM*PQ8%cpLfNM$#~v#Pc73oLZ+^ddltv8|DQpwd1+uG^3e$FMcZzb#<~mipaTgU( zH;o!2Op1}z(C|yS2PQjbcc~DRb?s z?r7>4XNYXCz|_1;hqaW4P&jsM0d`0ClGDlsaAG~Ut3W33n1zVrtoaI!8g&~9N~6a( zHekBw(Kl4>Lc+?Q-~+sIXUM4VWoEKz1lZeWuh&8nuaoMS7cQlbz^#$(=s~f%?!bYw z1~D(qnk{QLzKH>BHVU+mC8I?5TN3Yu`@XBCYOalh-^^7{Bysnr3kfG3zv)HY zv9EDA%{T!!)?8?J)F9T&n^|S;>iW{+k)ha`R5;qg zIlI$}CfOID58u6nR7r)<9HJn`d&@#=?Jh@0A(bxB{$eDrcNjT|gKLAWEi|lF`}_~{ zRhR<_5mVOYbKUE*-9E4t6rgA;oV6P{KbjV`RQN?Li>D;e8)(c+SoG_LxXkVyx1 zn%T$Fy_bz;CB;#nkAkcrO#KcurqNR&U74$bPS*EE3SFW5%wK4dmE6Rb|@m6YzXz^*9DWBg-Fjm>_oD| z@+?0s5dZgA5?y~CaXq*7uKqmYa?c<~oLK6WW~;fOs!;$kzYZ3s=@c`d%!|Hnu3O+m z938N?J2LgJ3QdUv^nU$3*nx_sQzLUz!yQ!zncWtjPYmF2Xl>B(P5Jt`+Wgj?2_Twr zkTZ{yv{qe_K&{7$IO;x-#1-~r8Q9(^Xtec z>B7=+o85^O%G!~c2L`DfwaM00Y2_7$Z9tt{km710+m|t{cNf`bw8mI)?xpU?SArIr z@59s*QOBynH+MfF3C3QGLq<5lcI@1B$WD=wk&J1|5X(2NK0Bv1DQ0r5IsMl0v&)U) zO^l0X(>6i&OqQi@Yyz%!AhUm-wa%7>>yXg%D1d`!Uka6d@$nR`0_#Dk^)#CW5ZpDC z{MgCm7zL1T{slm)UE@05eaYz$<^GmTkVMsU((PJ@6R81GFrz~S%A&9#6atTl^$waF zsHk(L8#TQd1u6dJsUe@Q1)AzqTfw?w(h;0)oFuPtmoY<^@ZU5|h^WfG9=LH-tm- z)Qgn7VSjUh|KA6(N7f$1##lY?{fDPNy|PUeL49%4MbiiTQ1_|C%g>$Rv+Nbl26|{X z^jj)-Noez?K#&#umJeLU^|J88g63gP0FlpR0f)C?7cYef=j+d#i*nzr%GTo1I;oQ4 zkzd9?$1039Ms5@q?eSJVH2qr0>W$Xjh*#*FLeFVOCp*%k_U9$Chj?04VcRCevFnQy z!;b0fj(5C8aV+{$HRPA?VXq@&HK#0d?|wR93t%d-R=w0^g*&kTeQ zJzPNWTebZ`NEuq!f^ENrI4jPRZVau*uS7`c)b-5o+87#6AGcAdwSOeZVxi92w$Xuh z?~2Y#ew0uqOS_Sy{k|0VnG!*Hv@TJmBOjliq?8)L9zI+~XuC?QQsaLlR<`Fw1>Uj` ze6pt<1753-o5D1gZFZhsL~=orx9p&fSRKXPBxl`D?1}M&g`~JKp1mpv6iN2JwOMz7 zl47tpN;W6$Rp7+W{6v93!07a{|gq77$^j#jrw&6q)< zn1fe_J!MuM$ZW;mIHbk}QN?+)0$a|YNOY14)~;N~T2BDmW^CucY}p}7VwHHYu`9D( zvt&Ft8_S@G6>dgk1pZ~!_Z{91_T~WwWRG*jI}VxS#SN*KTZJj2PA)Zwyy=NrKQ%sf zYW3IR$vv>!g?Q39_o3|PqA`_6;t3;^X7g4{=q8|4d^~FNfd+FP)hP<&5&_sh6O)G8 zzzoJ5RK5iUt{xoZ88o}JS>^0KpsK^YH!x`c$PlLOxAduEn?HDxQRXD-CRayC9j)+{ z6rITl0*_FIdI*8$(Bup7oKih2`L>w%>j?Kb#W1<|M=Y3yy4dQM!7gzX>z<}~wA!w2 z0aJ}n>vf=zN}7+Tt^`}-KE3rqC|SFywGKZ`R28mcy0s%3xTB2?-A%{DoI5kr-d7m2 zPL*V}UoFngrZ|-(^-3!APU}JeROda<27u?U!z*~Gw{0N@pq59kJ0~vcS6ZkdVGmtc z4LQXrt7e|G4{^5b=aU5iS@C@KR3~gWUD*>8aKZAP_H{{mvX>RN)7227vf;*3hYFf4J)aHa#|^N77>_>qi1=%4T|k1h72h$1@-GtP+J0< zT!!{qP?;I{UJ6&+>B}a6KLb3MC`F>RW7)uo2eJw;XHkl+BWulij_YN z2A8ffmZ*La-P`27F%)&Rw+PyreP_L*+PF&>m2Jo0J|?1^JRLy+9m{kc6I-;0Opmty zJ95?m=@dmM*1WS+=C;0d+clIzEaUX1yaB)D+B|Xqv0hKt*7w;szMZ$UMI!-5C^rHo zoK9X1&(yCWK1}w#(XvU;rK$Q=ZVvnG-2=(PL?YOnSd8F!kB>PXl|Re>ws`!QIJJ>_P_(New1?rLOXZu%~&vz6b}ge9DB7#d=4|@G;_5{ zer2|A7~x&HzxOp}HFKwkkAlwmX7SnX-p0ubeNfEEf8X5Lm^7sz;#(GkAVXUZq_{uM za*(3Qo0g&V%`i~<*!d1)0Ai;*%3<}D+m&$Wf*9@=e)p1FWZV3)CJVXdCfTn(zxLJ+ zZN0UKVIQ}j&`Mj_)>~73`>^1iZhva!%|6IyZiBrSHO{2zjaUOz=F?-tK2XTikB7GE zARg^X_PH5ss)DMD_a64`OMWsyBOWvJuKUv=XeX{t`uOGD!k^X1suyR#7{&wX?rCwO>-bumrdw|H5d49GraT9Go^c$E>cl zEF`c$Cw2`%8v(Fsud={H!i3(!>@EN+`}7#+QJwj>T02#EZ?1gKU0ojouu2dq&vjCx zas7^vnZG4L(X^Gyn-bTgnjVp4R%x=;v|g%+9{7Q5nRjqr{ogt2-_h86P^cv)_$*C@7kQg$4xcST+5FtTg91*-rKHgBD3T7qS=q;S2U#Goj+?5qa6$1#- zC4mnkd1fEShN}DmHg{@D)WT7wrMJE#sSLN07E&K= zaO)JcxdLR+E-%Tfku>zy6CGTlj<=pM8EZZ~EHvX?V}06Y1=6A98YT?>YaodL%!78<1~4Y6h+2T;m;`W7TuNgckkn zuY25smlqoY=j4HP$?fkaO78H)2;5hB-Fcd3(M(Uszg2qsXTJg~{^7A}tFGyY_NbKk zdASXjArDz7Xu`V&lWt(wX00uM{_K~&y2%aKMY>Td5YoB-CmuotIZyKY*WDkueWK9^ zi|<5mZ~yF>_1(t}rk2#QVhm%6<6*tuIU6F5`9{RYjqo=Mi#Jz_uq zN_Z-q2Zkgd1ln=1i6!Ps{ToD`_C zsk?A;JJCov4%f{=9$xt~_xRPX_|3L%IeckndDYJkdcMy+SN{YE`B>5h-9*Exoo z{bsm;YfnVpCqzaA&U?pxK0_4j$8-uSiUhBJzoh^3OmBkb?glv#-y1#Z`7=1w0_K?R z8Zy1-A0O9$eXJgOTOUZv$oTh+p`3bApZfog2YAzI>wekAYlTU+_n1TwE%;B|wPHH+ z^tbP~*$Xh%BMaaE_~Vc7-#c_=J%4zDO!w_BU_up&e~})K{6m@K%Cl=V%>s7MLT)}x zTB)~9V>*f#E4 zj~w%9ZBNlyXbfj6Rm7~k{?m^q^zPr}TB09rnS0!=LG+klT8vo!eUtEgxvSKL*tPfz zbCrLaj*V81rrn&G{$8^9{riUHNP)B7dhGuGh9ZRjn_Luir8&K}a^E&N#{;Ah)1xzf zPscF22Br;_4=he)PQ>Z|By8xwpsAkw!|LC_&Uc+_f ze)3xm`}e_}AO8lf%#9r(*L5TuY;M@TJ55kOP0t+&{QO&@rLF>7w^zn+zU!w%^CCDF zp#w7}U;cOq+fU=4zK}qqK%>!kuZ(TkVTouEQcYK`6#Uz@|ND)4y!q=vy?Pa={^JRI zqV^7S?xel>2 zKmM|W5InphkI@6ab4fc$bhZYtriNaAj`;C_qa)#yx+g4Le#_Coqijptbt<`VtZe)4 z4tl_Yy7KkK-se9){GVRhkKef+v-O}}kDtlhy}j+nF93P?O*a1gj~o8++yD599@>Zr z7f%#r`}Q@Fioi0rKW_7n|L#BjNIB_W*YqXm`cE?$G)Si7(i@8xPyPIKMEvbvK01|S zYsVC2w|?CIZ6aJ~dK21X$G`vczx}Noc)qaA8RcyQw=IsEb>O0AYYTsYsQ9-Fl~~wX zQVmUXTT?z;chG$Uu4zWMaQC(}`M=&8T-*50E%xK3FbB(Ro;YtNJaYw3t6Jgh5V3vd z|L_H?;nqj_IBha)JKIR?hxwx=-i+Db`XqAKEurcrQf?jk*#$;pt^+MQ+L{e?!mOb9}| zR|&CDY=vp*7l`U<4j5g&L$tj@XJ-X}>-LwnPN2yKs+#S;Lw-sUH_G}RkyL^fvZw(5 zK9EsNK*aYt;`*%#3R*3k+4U`L_6K7=0~z-9jSC|$W0IlQ`wkST*a0x)5rx6~s5tnU zs2d1rb#3yu$_LE?YbVCdQ4j=FBKd&|Gt^Qr2Hk6w>7Kto!+&_ZfBop1EQGfv$DfNV z8f<&eI^o!ZI(vvHoz;;>05D$)g6(K`rC63ba1i2uozK=DoC)S^jj z)!H7KMVIxN{ML*+mA;pZfCF!bAerle|FA#*^G6^9oEFz=h@c`ZBchWC%oHhT2t+@Tyolz3fFl410 zX>GafM8|6bds1Mf%=95fX^$0<%hHNg0i|lVjui*y^#t&FQbp)R)wOM;8HBBGKOR0f zUk>tWZxAqECV(M$*WSa=9_?caN2tO>bek?__!stoMEYM#f6~i#TiV;hM-J9& z;R=dz(XU*Wbfq=~U|UY0RO953*Q?sr7k zIC^4`Ir?s(!1*%1B>MvWp*%b2Qqo)GLAyQ&WB}>ca~Xm3Y|J3)VD2DX;XuQRD5Llp zBJ$U*pRY3OOuK{lSrV3|6CXWrmD%m3C9rRRh$0jRFY*$y(=-NPgiIS06oXrUVABd> zr#*4e1HiM{dd-Nq&myQ!#Q_nXW9mHi6gQ|~n!vwTc({v-{monRlbercVN~%MG9b-=dyk*>LCWY#oCB+jUNRD^)RhV1G6>)0z``mL&h4-JW?1MJ^$;N z25zM;D`4nhO`!*$o`5K}Oah^|vgOWWCN=x|pM~DPZ_H@?R(RL6xWTY(1|&hb6)4UX z9avn4)?^E)h`c}??6Hk-alJVZmDHHbs-n?e^DKX^AR>|h_DK8(tou4hq^TgQlTPm& zuxW?I~)EEh*`9TPPHe`Q&OVps&-+?JjDFW$tDf|jm4V)tv4 zvMlz&NAlq=gMR6gjN6;!-@ovn+e+Nc-kX?Q+y3oTm~-mreVs@ZrU2JAfee5afdsku z4bTpecU~QQEb8^=KIqFJ2M%^Qv(;A;Cpuej5+OsO^g$5Hz7^s&hmwE|jhKBQ875&0x zjkA2!ceYo;FRY{-aL<86mZM2gf7{`9Tz_i;NmxMGOjgzeW=HnzZbW$BS(anh24aP6 z2hYJ(*4b*5L^seKH9;op7!WGxn-Fk< z$La@*BBt9#(7X4&3^$XAxewGkI*VQVk44-7egQvTRfV?Zw~ooO+O2?u9$A~|+CDq0 z!S6WzLU~p+TEn{-m_pGxm9QG)fNXqT0dz}HV_IC}NXy=kcAkCrksYO3Mh!jwM=zXX zf{R|WWTzzRIE@6ekvt5RL&67$C4@qAyD$gF9OEFCvjdJ5ZdGv#W(C2#GLIOFCg+iEl+4pwtz61u6Bp1 zB%%etIRnbFRjaj9E>)ktTV8}y-{diq!`XeIItPPE@#lZROkX%y@D4K;s0y^m~^iyva(P6 zZ*AM#TzH|ROpi9J)(_-K>uCmW%1Xm0NHCh{(r%n|!i25Ig$$xw3yq~wW zm4aPR`-82_T!Ldyuj@j?1jx-?M-)!+87vRZL0HRykQE2g*0GZjOe6<`;C^hal0|7Z zV#Z)uo@Rovjw*#mdyl+8vUEM1;CMb{VegQE>8iEb09aA{si5QluL_ieqYObVeW(){ zKoN+!fSx;SQAJo-=9$cQMnFy{w=-}c3*tz2r#pz{wIG7f(>2v_Xape{6|#eEHV~o8 zqo#)zIT+ju=U|2@A2Ct08#wm~FfMX%uM%WbZy=KW ztzmrnzA8$R0u?W|9ol~EgMWCzshOh@hu&I>QQ zb(zHNESzaaF%};!;nVf_-0g*}$*&Cs0tzi;q05}9zb`>!!x&R|8>-zoN_a!i5B1Ty~;^+qd zvIW}l3w`FLAnW`N;-IJkyz;J1dbI0vWJ16W^uyG1{UYB$@~?Kpe;tuTM#?R}2E8>P zmLePWIZK3kVcV_^gu9UgQgy0=vXC4)FhFg9&+IG&PJZjwy5&h8rzZ@7P>vkD5Y=ud z2U@`+R=?9C27svpls>TSn)qU`>NJunRFEoeqbE}5C_!3#xYT7nc)A`0Q zbAikWri(-o7Oz15=!@{1;7H1!@3}igejJ%w(@5lIU)vZJ!pS1h3rX25nhB5tG_kLi z0?S&u$2c39HVKH79Owt5<&exST^zJX3A z-}T12VvorygOD}c!|#1XKzVKf0mzQQ`!gPZa`3wmClV$=2gAh%d&U#>QvjoD~_gH^s`0C z=?r{&SNc;1MWh0dcpMJ;?#bPy03{%4IaXvDIm$QSGEzkhAOAxP`oBMFHvQ`b37!e~ z>!0Q1NO)-i%d^NbkdK|D@mw8eUs=C5sWZ~uxG)D-C5TMzDhNFNs`YIj2<(1?Qd8~v z+VLNaNL_jn*@W&ooQw;ROGuKNf|FwN|pFC~zAxChS0 zpnf={4-F9EL4Ovtm2vUm1WT`oZif_&Zb`6a6mG;~J_2~1fL~-bEj!%GKcS1n`ugZoU&aH4ycS6t$ z7~qW9a0~}p+XW=YA6^e4~t+vt$6TmVQVX*1ygt%^i z`%xDtO5)Jpi5=3+HVv?%!pS6jAKD9UE<}C5yMlws06#jYrks$wZ&_?rL?2Dgr&$S{ zLxTtmN&)eL$N>w2kf(*%!BA1)yqbyt9&z5U+ctL{!6mb?X92Ug4tkKch{O)u8>E8k zHq`hsm6Y)2I${mw3<-)zgw?1-!BzVg08QR)c+F&_YNi2)UA8I5T>nrL91Xf6jfT}q zU8BIKtBU}ZP#v8Hlc67~y{TGMyNHU*(^&tgC z&}`0uB^U=>bsjKm(mh`NT~pA;8F2L-HN9m(DJlW=D9j^oVvE4P+ks@#x8 z48;U@*S*Q^=Ne{Cef%C)ODKXrEP{#<2NHqJYFP;9XX!ms5=>DZ}ee z*#-E}G2MzMr?OTqxvqYl7PX4kyX8LyS7jywq8(WTu3a=M$Ou>~h%mnFD}M8t#gFsF zQ{kZ}hX%COL&f= zqEYsl)TG-=M+ZJE=J^Nz#!8d8t%uY2g3MTJ-PljGC!QkSM@Yb%yqTNC zdA#-YmO;cNCcuLx03J(q_5!s(R>0RBG{RCQuY9_Zy`$)boQl^0L>1cbPDyRFYH72w z2J_O>5y`ByT^qGTKN}fd4o{z96^nPA<*E)inHmwG=jZbElkb#41viGs!0g_BM0A1f zQ1WyFhh(!e;U3@`wVoMDcyp;;8|*c&tgc3LNZnO3$<97q5dg_0p%vs=J2hITU0nwt zl2~cN*(2&Y;IwB5_R#45M^PCdUvJK7Uh)vbKk zF-n8y*wgLpeSFPn_OT~7s}oB19~I`MRFc8Jm)X&>dJM4md@yCXd*?%%S50*QVgpmUP#6M) zVW8i@QHC$RY6P#u+HugEJ|4-G#`Pc&)k_5`4Q6DY#+phC!>HyOGUxRU@{ltPkk2Pg z>s(Eo{}JEl9&nfit>A-~kklI`m%O)U6iiHrQyRgIaBy&ow~g4VCiDWDDdM$4l=GbqBkGXg64T z8Biwzq3+%1U~`7r_xU|}dmqHQ2|yxJohK93 zj?juuam~HfIHH=?!7YvfY1(%HupS?n$tZ*Em1WcRX59Q}?##@=ux8j!2Q^jQe`Oc_ z{2dVhLUJXxQfx&8wf21=M;bbRntcGYcyE{hvMmQq88@{^&hBctbx7Mn$#yQ}W$J?d zT=q>xT~rFJMH;&`a5Z~_GObHKfO>j9PY5P!a$qhu4slCbR>fl=hk~l;28aYW&^+>6 zFNOML0wfV`z(DFP6Yn^3S_JyjAU_AfuTFliWvp3Af`jDG$>I;!IvSO;Ij=>omzrn#AOMJIGP_0vQC@K>-?r(m|$=O~7P_O)436=6mr$!W;y|k0mh- zhGmE@byq%haCU9KeYPa7dyQH@fUp3~fa@^E;$8%x<-C^RRALEjFuPf0;2e`K#7>Rl znUkJ8j)<)f(j!TLH-1t9(t`;=De6aUR4e`exI1&;M&;xEiYf{MC>!i_Dg7B-?~@hg z0j))Jp4aD9(v6s=MB~FJK0q$^cA5@VqgMhIzoQ7K-X=dC6Kx>fV0lKrBY7hJH8&12 zZ0zOg&Y_-Js&sM@938@<}h zUlEcSxtkkti?9O_{T-t7Gf4=Mrqei;yew?o9PLVOQJO}dR;N_KRu-Fe8go#O`#a(z zBdTLR4WLndMLB9WhXbOUyic;yw^g@LYx|nG&oBRzv*|JlND_o)vYP?K8^Ggihw%41N$jG z`oTAL%ED%4yF2h-lJft^*Nu_JGaWfmdX`RRlEkaiU3 zAB*3$-lYs_dfg6%eq_rRoqq2AF~Huk9;g}IH-<)M{p@q~`y4I=C+0N~_EKqKb7)`hJZp1qIN5>!5Ozc}odhW1YPq37gQi=CH2Xr!sUZ0mWc}RWud)a>q9O!A`?v#Yq{`gj zj*PXDHYkA%D#G%rQ)%sX3<(}U>mWlYUcTLYY+o8Z zfWSPOrR*Y4c;2_^r2N)cg+Wlc4-=^`X)SN@nUqE#&4&O89ZHE zT66yR>;xOuqIyNwijC5~J23CE@en)+wnpi=8VGdhi|gzRx$kANcN9k(gHaq)mSOVRh}%k=@cd@#o|l081Sbh^ z^D+KoZ~nLad%NPVbit~(z-9vU!DPXyRddc^_r9ZH-XkoD0tiW=rZ|hQEI6YGf}l|$ z9D&)_&DL&pKyAeN?NAVx)J2+2-_xF~H1(Q#KBp(u1e58br#O)ZiVc2)}R zM`}aKZ!y`oDY0RN1d3mhl{udrryusaspoa={t%3xi$L`je}!6ZM!3$ucGdQlV6hcl`V1Bqk>ZJz)Vv)-LGSzZt4?Io$ zLj)RwD5m_@TgNK`0g;m#mZ3FzOvM-iY!*$MyA|dXM1{jwOBYk%Tx=pvM*dDE`al99 z2!k|71*3kozwB-%yPImKQ|_6{+@FLD4WpLC!@g{9aAe?tJoNne-m=JVc`Y0^RbBD> z(g=F=QQqv9&~qKqR%P`yN}<2WM)^P*pCJ=GUt94AF~bxA>6mP+t-;&4?yu^6<*Y(Q z=Nn6=4mSZz7(`aia{qvw{}@=(Tt`?Pebjd`Ze`q*yb9y6csV>x=iYEUOhQn5hi^S# zC4u^w+!c*hJ&FwOC$nGT215{y2T7dMw?<15au_7HX;?j-^Ms-lg^NvamWIY?YL*hX zvHLec6jmPkaQd4REzr-3%!Bbc^SuTGXxH6*W3k^2LlIPrl3&J{7mZnrWsZYS<6O6# zEL}+?X9(sxN&Sk4k9la_=Q#QpzyDa&O-UTVjo@MeRk$1i;9=_YHlV0O+G2(a?}Tq< zeWsVhS-*OwdpZzxlmXtSoiYz+At2F43+`a;oT@dGbJ#0DJ{skT(P4V*i($1!!#h~= zE^_&;Xqxl-uQW)n1@KC(31HxVVqXtds7S1!bq4{EGXAj$TSLjW&{0T9F|kSEG|mBi z{CH?b3M)XbS-RMn5#iYR-qbZP=E0_H{JoG*sHnCi(@)omQm#NG z7Hh}47P`QVo(TwOEGRy|?=f`ymSK2&S{suBOQM&9y#paUOu+1YJx|kU zErVH#k^%L?G>`k5oZ8&E3Wv1=_zjk3jkV25u}VNXmAzJLySK9Xi;yEvK$l1{SpNz3 zKGbfOY5}v5=HEbcSm2ScdxM(L4<(GYq@)NOigY zWC5MNkCiZqAJSZ~v2dk?yzO$hP4$s%{kdIU> zfo4{R;i)ACS_xwt5etcWj5=;PH$4u4a#ND@(G*CZNI`T_wLC3Z5=V1VhF{E(YLEO` zNTBwdPGbIzCO$-zCFuBQo&vJN>(#4mh}&2TG`lBpncv^|P+wJD^ewML;6_@@S{`mn z;>cK0@zZB5Peo^MF)aG6>wqKU1dt;+ikRWl>xPMU6)%fg64YnjHy23UgLkBsJVX!^ z0>WI2neT$y0t%IH^PB}QS7@dVDqiG6*o+#vh0eo?<^#qq&@|Ne!vaco?h5$^|7@iw z8VWz^HU*(RRenE;K&3)!U$*$OPX)T+s1`6qyI6v|uH5dU))L_Wo@J>R+<@fiS+BAj zSMVF>$t2DzA?hdm{bFMJK@uXEtXU5S3o0j|T$^;5<-p$u97FWwY>Hd zK)AC+Rs@vVS|9~^Mz+K6VWjy>lqfRY-wmretK6!~rHSK$%-K~KYHpNz9-Qw(Z9;lh z-?D1K5g-GPECd;xfVteJvh|YhtNp|IN$2VhJHTY6gc|Dn{vB5j^RgdABvFxe>PH?$ zsQG-W70j>jUKqW5?Dh;*x=ba*Ks0)h>HSx7_f2S)nb;*yYhPS}QsgzLOa&jp={SAK zY^CgZ_a5LyHCq1El};rMJ3!vj9Gy!7YY6V&Ht<~s1~8--xPBn^zeQ%0h-Uh01%)b> zB~H_whzIRLVevL=rV~%e_amNkOjyAs81qq*#;socg0z58AGtjhp*3-XhTBJ?2lP{e zk+#iHAf&A66LWpAI0uPInnG z8*F8LZQk+%Hq9rbt!4srE)BALy=JuK6@e;pl+5XhG^i$ViOUX9Dbn^yhfM>^>KY|y zhJ+0#CIa92iBY%WhguJqSCf>e~}jivUkEK6QOd>9d?t1#1V=5GJH=hAC$s6|t(==YfS6 zC&x}#B?Vq=;HVtwDX^Zczz*alUMm3Am^h>*&=plO0pO>U zYZmhp)cISM{@=1Vq}eosbnQV}Cd^CeJqP>xmD6eDHT>B}!M;wzm=%PJJD@?Et}Bfr zXY2#Ciw)a6B6qCC_qq`R%s3Qq*3$-;nm&|tVjhORe6>*7bMI$};X{nxZH-k99Qyne z@)Df2&eN{3{BdN*(^^vp=>4FHU`*T5w!}9JFwOazYMVJ7f~sMmE`}84?upOXgBE2f=~?Ld&xtYgI3uyr503X>ZG@;T zPfhrGifxz^qgG+pv2Hq!@A-u{{Bt>IqM=eFdx!~cWyRW5iKk&e2djm7^Xub9I4jlP zq;)@?)TH^N_MrjL*a}C(Ii$~;z;C1#<(V5np09z=sr`Db6q2|FKt=6~03`8EW2vXb z6HSw^iUCJbWBlxX+A1;{Jt7=pJtw}Q=t9^nn?K2TosUue0tbbb*SDb;SD|h2_WTCGA@FBBwfxfrC^f=k-t6` zq3a@S0yd&=hCXSF-Z3|51xx_1tO+YMN)=P2z;+(fCuf*IpvZfqCIi1bLiW7cp&1R6SCXVou+q zG{}o#G=ik1Tg9T38D~I~)f5^gszbF}HSl>YYW$4U=QWXm#(n&&f#gip2MFh|uN;hy zQWZJ+O5_8Tqo`_><@eGC9fT%17mXh2@o!cpearIF7CA*vl+-RL$#PYQAXH6=4de7X zQlM{?oAlu7|10jXo=ESM?u#$G?%IK2-v?JzIsEJ$L!5`%DVqgaze9&EB8Ekm)YCN7 zumYXiVV8#%Y=i)U=+G|$-IyQ2L#k45Km*`V)8}+FZs=BQfFX%|Kw7$3M~aH7iNER7 zxsqTxQbok|%np?N_?e*iEC3ing>-2Cw5Q-_b=7Bn6dnl8S?xWi_`unV9rfp5=6+hA zHD?O`Jfx^`8|s7pIw|wuw^ih2GUcYw=bcHJ8ggAcI9LPVa*D88mtr}`d-C@ zc^(2XEb;E>`KgpTGM4dU?jl+&hmX2*lClay2Iw;raAey#;a4ig@(&RIg=ok&*w5VZ za*vs8g6OL1RDeCFYelvJ&pF7PGnlp%y9@u}hF2kraSo)&@rf0|Ja9~t%cejhN;c0dB3~ZtR4&QB#S(p3 zGKt*k|6}jVG+uy})(2UD3d_ea7Ip->8^yBjon1#h^A(3o4 zd9`rha;07mKN}LOTVI0A61As9m~&a}rCh}OZ{NjFe&n)uOAo@|;Jq}bc&qb)!)KYA zd#3(r8r-ZK{6bc;Xf&%zSphCYT7ig*cjx1<{m_~8q2=*w#{p5pXV>0Akausc)R}!@ z)koUdFf^vfk!8UqGP`~>H(Y?x;wvKRK;XArz}`pi<;)m8^Z{7!@~n6#RvOQ0?@|_Y zkFT4M66DQUP|Hwt-V@A}vNO%jtCq`EHMmO7_Lz^AOQDH~cXRRA3V;zc(5n-3Rs4sV zwJF5u|4zK8CrbR^*62b67Ty ztaw;QgF7#QWiS>RiMOD_O0j;a%n@vP7ZE&aC<)2mQZD$o!!^;T)>3MA;+*;$c|-#CJQNvbvnw{T+wyGL9PfWtobONmw2I?sp4(zxR)C~ zY=VY}2%mLORWy+=_Q?lS(a0+F5YB?G#QmY2$O}~1a12#9;NTHNF5W7fD2)o@%260 zXPGVXgN(`y(Mzc(hFy6Ph-^qt==lF+Z}r7R0sz3VopR*Tp21wGcne+!t4IF;gBmGE=&A5UVZGO!i z5h89$j2|DpYX^SkF-L9XOLebXz~oLEpMtuHDU1_t28?!O_dnE7IhQa!RRaBLNaT*SPEBL$g&^-){odK2iE%#A0C*s3 zXhb%RaMW?DV6Ano>Qyr3VDL8RBQW@6xqUYCoZfazK*KWi=O#>04YSSF5xFMAuV@z( z!<|aSA0GZ6F&)c12rP69M2`wcT}l3=>U|Ym8T0@pjfajR2Wx3Cbr%G@quI?J0GsQ+ z3Nr2iqA2QkjOW}*YbQjUM04z09W)Gxl*<6t?HG)@G}t{s#to<^5r}9=drIN%Gx!m& z!K!gPDk;w00L>FT5q}kPvH>g<^AV_)dAW{%eNHkCm-%SmYPlol=P;wTZUD*S_ZXB0 zXLC~7Aaf{)?iB;}s4x}ti7FVGwK`A>Cv9iPJFTgOyw%=YLH1(+jYc#=-tCv^APOKy zEi>>JpRj*I-%^Ii$Ojz}qS_u?aAuyqA@|d2Q;$L>?$pbD9W6(s^z?KD%R`Z@lJ1;u zULV9Nvl;aPIN!&+OUnkTJH4^9_!Z@1LS3fK$ZZfOCBk3fmTd@)9-%Ay4-W#4oR>Xu zO}spu<~biURow^bW5h$Mh$?ViQLaf=GfLOfP&)O7mdrtpzzqR*1g#Y|X39f5<09vi zP)~U@)lz0bv;YX+7{L2DT5kp6PtYXi(->*>@%D%8zOmLQ2ij#z*`AeK6OGiz(H{ly zZLl#v3rL#xvZ6{y`lcYes+a$x3cMTh zBJ;4tBiG?jc`gn!F>Q!$1mm(Fu-en09(atx#7)EOp(iW1-w7o3G}5+sEXdT14JLg& zMfz%Euc2R(K`kDGIrh+MoSGo=+1URG;rSB#me|R0D&uU&M-<);oP(=TUO0;@{EnqGmcM0cBtK3OZujzFPTJfyjXZ{wE(&w%M zxMQ}Z^-E(ubh6g3M>;sInrc9e-K-Qc|1c zEu8=e6^^^!?ekuK&ync%Jt7IaUvI0_a3m-KOL<%RFrlfaU7FvOiLwIu2k zNlHlLv#sKZFZascV^D^vV4IQj>?y%=AgRRGI^_Ssmk)Tt!V}X!Xfl*MV|BQhmTK1E!&9wiQw>6Sl^fb)4Wmv1|D(-)5oHnJ zBfcsrK(WJ>-U*d(JzLTQI$uL4{N-|bvZuP!+i({Nkm{ej&lnbI8BTMJe&4CL$#+mT zM_=y6R6%f?O@R57OWO~+8f!Z7awyrnJRxaV;=Msx z%E=r8^i=N;qL2en)IxK zv4sXM5d6`bo?Xksy^F}q{=!<$sX1Aq5P|UryZdH%_9@&-KAFZcNI+7-&m?QM_y;3)-)JNAok%hMr@ce`|#I*6E??4T0$@Okm1z6ijK=R@_d%p?}O$ufGLe^L30qKOZ{KyB+#Vb2P|7+kpVfnPmbnC z6kfD;fbtT7J=p_QP!<0 z0PJ1q1UvG_tR?*t7&U`4Eh02>!gj?UF}lkaf7NWN6$tf=2X@zBa7BkEKR=X4Sf0R# z9Te7$bPK%krM)k{4_w%+08^V*R3k==XQ!z5qSlt*)3(fw+ARefY zd6*PvsALTbQ6CwH!bXKr?jfxaGV7s<;>8|yHjjXDpyYDqj>1SMUN#5d&(aLyO%bqZ z$(6Fu)rP>V$e;#RPXu5(=xwscH5rrB46ZRCGt1GMj#kG%bSXuBS*`J|@1@a0(66eo zUrUF&DbczeS70ai)RCJ&=Im;_HcrojXF;=@m%qB%>)s@aFA zQB+}a;1hNha}Za+Zu-!E7zi;Ch&;ZEKJhRU4T6L~Hrvl{vUU2T(F-2mo?Oh9;MobA z<7;O)u#u+c#SVabPxYDH6}IX(T0h^AQAefJU>H>Z{5MP3I`AunCk4B^U60Bw!mUaK zQ(6(aXCq4%B82dJ%|{4J@C%HFn|ExBZ$geKjhq`Yo@O?YZjH&UF7O5k$XIsO=W9o0 z+z=foGf%46?1;)&UT;T86?yFlT%|Dmn*2Kf?0E$g{&UD3%kHa-eF!w=gOKivQF~E) z<~e}_d}v_tCLnwD#lET{*Rw78w;#-Ym}iHIvT6`CH)5M=W%-7ofp9h`8UPm!W}V9Q z#~J%0qO-mq#S31+EkMny6>n~|}H>g!=|Hmdoe zIe&M=Ffu;0aVtFF*JJo^zQ_$io=$4Y+yllfFA)*X81$W2V7Snlneb5-0^{)BLCw{O z0v#!)kL-SC6ordFqN~3O9P`M%?AQh>x|5F+U)`=nM81U*STFg249^EavbVhMmc|hQ z06Hxh_UI^&O9SBaRauQ9N1n3`KB)ae9!Q!3C?8L60*}ZHP=4cTB%+1^WVA={8>$O| zP`PDvFcN6k5(>htj#AODX@hG z>FT);Mo+HxSs4s@g_q6Kq*l4?-!kf%LMgBFu9?ek$QV916kTH{W#`txrgz?9%~^9j zBhcg_PJBx#Wa1>y4172cw89j&&s3#)Q=A8bRs2y`W(`t@mVE3+0$2<$L5NrF20P(; zY7%H$3hWZGM!BfPx~en}#LOcrH`d6M!~C~X3^K)T#EWPDJ;VMl`4OpA5XarBzfAjvElRBE- z;?@6IWaGOCNvDAQ;&q4*kY6>{hWf~3WR~I#WvvX;Ai(h$>s@r$i5Gq+VQ)6DfA@Oz zd;K>fGvn!N&JTat3L!YqZ|whPn=%E+5SYMu=k$R=C5X3sXkS4X@)&FlR^uxkC6PuF zTo}sj9TS@1re?Kv9KvT-MSoiW#fBkt-^vs#+rRqRzu)bPDl#69queOlyllStOpw83 zEjIgMZA2Je_SxS8piFxrmcD$WzlmpQL#o=+^5FXOohZMDEL-?p?ho_>2fSc4|%y1&u*nC z>Kr)sb60;!v(?abwTgtpmpy-kZnit(Y7+R%KJDj={^`jza@!p9$E_V(y0dvf20*}m z1(DIV?e+pXz?T7fs+8FfuyILo-p@!tY?w^X{o@mxoAIt>xbI4+BuQIDN*dqkrK<3s zA6?*4k8VC!5;?ukkOv=mu3)%o46s*K4$m3s#Zf5k(yV47f#)p@LS#>`^oeDCJQDmC z(Z;v+Krw7M7S+FY1Gx{IzHrsI|M3C0Fz{`X*f>dXBgN!wp(W={dz?D;FCOpTO8|tK z9Gh2SF=FcV4^PH+8f0dgd;P;x-28Ebz|CI)UBdT!K?{JUV60ut<9#;P$0RpaRL2q; z?ye#TY^xRfvF5$Vy1-EX!&cDzg$g0LHZPC2s=~#8z1#fyb*2t)UY>)edo_OTi;l?F zECf%}-*3%`A*Ee+r?>Q9{zx0n%^#_`x9|P$9|^8yxUBI1`Xk|C$TLs=3zy5-vP}<$ zFILqax<9Y2FF&%~e%}L$Ps1mFZhBHyAaMupkVSf7v7aye`MrO9GIPT>?NU4%TvdN< zZq1xX1*88@_t)l!$K2~TfAPWpd0*MQ@Wse9)Jq64&kD`?bC`zx^f=S zZ-j3rD9~X8n%ZaS`0IuL@(T-r9}h_6?Cakj@d~ooi~ea7{IYq0(l5w9`U|thsUM5d z@N{3>??s9D-wFNiqTJlD|94UT@1p$qr2fMe^1nOf|HqxuU2_09LsiHe$G0KF|C`h> zNQ)C;9N(?fmJIw&0*DcWCL9p)?yPzCr{fdOF&sW?x7=Y|epAta7zmsJ^PnV<4!;GZ zbZL*5H;`#JCqHL#zxF}q0<4Tybr&bWr_Z2Z)p2lR$A^uq85rSGhQvH?5!`nG+RxOT z%vm$?x3#W1|LEM~rE`bBTb>OZ+(Q-d4hoX64B!nAv?U1!!UPd9p$*?rq<87$xO4A^ z6V1Qe)sgBe_{UlC`T3axKNp*?CL&r{69Kxr0ECEd5dvw@cpFSCP~S#S24rqO5fSwv zMg==@x3XQf^7Qw^e0DVAqtsV)E4Y`lQU{rBMMnDD5ahtx5k^?9fW4W2P)BOthFcQ5BguJu8jv8XnBDW!_LxU2?=py2< zSMqHRAbW&kIqh*4&`kg(!}P<}FDXu}>klgnB=x z{xmlnoo)oQpd%19*NMktWW&1X*+fnPEN*)f(W#$5-(qJ3Z17l;B<1Gpfmt>mXfT;p z_g)(Y?(_2O1sHn9=rrf#HzCd{oBDB>vq`ytaoGS>k7~G~`%RHfux%`tEddph7!qB7 z6G3wAGsL$u8+pz$IHc#t(44G3kEl^2I*oiNq-dh&3B>s(uOWzn2p-1}qai2DhePmDRs&34Aqw)B__cH?4my7%%YQ?OLWLL5}+R>>9XDwYUljn;*Tj z*EbvP-DzY}Q`mfJF;Qw{RUC#Lf(3Q#Lf+tr3t-jq7A6A^T>yd$Bin7A(|{PfXgQ@> zj{+-q1voZ4cknR9ssNZarlXxSZd|2;POQU4xb(Rp1E{=>nsx|pK=+`kg@y`|RCest zy-q~N%$S`gtVJ6F72}5t?+zmzUt_Tnpc;z%L0z3RHADc%TXmg&5KWhl;?e`MzwU#C@d0BGIQZL!k+in#QhItt+V zfiFQZTzts-1hKWldMKw!;}LAq0IV;naz6MFmtmOei}{F5X)JaN652$RN_5Hk1Pk}&*!*M01sH~2Pw z$9NW~v(JOSfJ2ze-Qgn-wK~n$m_ICiNiH_Tm~)E50K!PG9CcIjHyxOaq98sXCmnTr z@^dJN)5}iia{U)Rfbq#8bM*DJ^t62ycw|=TbkM5)qtp+1q@xm%>((5ruBiinHRi5R`kbvba`zMw0dcNL#1apuTz z?itdwD1LASt@wPdZ6=;;=he%UCXe1#gN?kUZjY(5rtZ;KWj#3J@U6+bE^3f)&NgDDOFk9%iRWnJ^ zZM$-H2N~J+RwTqLx_8q*xPDDuGGNesVSHrQ3TVbN;UW9LWWkNWKIf(pwd*_I=1HfC zy(&2KVXrD_iH`r_!3$qXF4{(C=q1uV<+e{Sh3C5!UB&nLMnj|-azNOJ6{O9CRytL9 zbQahdiNi6Qc6+4OFa{i_d%zxb5)Mz&lGZy#=~QiH%)WSkSz2pDFn-g_x+ZVO60OxF zO5N;dGyS!wZ(g8~Kk7xl2l2v0Yr>WC!%4h^q}WkF5KOMTI62#N3)CTQ4wV>u`+=7i zJo23g&4-7|Lm=HXL~NYP2R~4GXUm_H;9I$081~bmTEZtF zVR?)gJ}x6jNK$-*jNyJbEMooH7|A8OX~?xz#Jur?HFPHfkxDNKIouokrJ z14ypBuhSyT=I=d+jB&9)+dzD5+?#g$$P4kmoultM(C%P8)|vpZBd2jx#zG9j!z%KS z(qvSvthnv3b78(^{vj>t=Y8%xrO5GjQjHvBxl{%y9wE2@VW11nKr-7CoBLraH)%^! zU0UU~5CR7Bx&My!E!td}QsA4y)Oht$T^`twxuTu}g)W4b=?1mR7$(KR>uYMIBqAI3 zm?#5YJ;G3p2-V4K*Qg@oL8gL+sA>rACcd7QozrP4d<^u4o_%IcT!C7@>EKpslFitJGbKTFIc9#2NVv|7_59VtI zBd)^uxd>^pyDfE^rDY6nN76w|94WEs`Xg+;j(q?$eI+K*I)fsll%tk@vCE|cbW{UP z(q5|E+Pclm^yC!XD8Jd+FNw-grRUh!UbnlXmYe$XX*R3&yr;^op!nB8#(iFjM$$Gd zx4Ldt(nNd8GnR+-E<$d+P=xWOLfT!LvV1YWXv}G$Gy^dD`RKnFNecVO{bBld?$5vd z>gHw^l)$652eQ8tASW%5yGgW2+(tQL8Ndg}pRGm}e;2@05+xw9?(KPN2&IxLuTi-t z^x|x1ZR@p!xKn&T5S}-hj?IV}CMbrLj#~(+jN+cA%UsQfe2G8YC=(YOgME^63XSb} zGxVylB!qxP&ytVp;;anH-1E%iXpK@H3bB$jmL~w|n)}`gdbw~|`5QShVT0}HDk9{I zFJddlyaem_G9S}0!r(n&q2*cxq!Uii_3}168M2k$73~2dx`v$juEVSlO36XV;M=4d33H4iG7ane&Y+sPH_`chn)+U`@{P>=@rQ<8)p)K$EGuMW{(^;$*53zPv!9#pfFPm5SY%E#gD>kEVI8 zE+z`{5$WT`Hy%-R5T;-Jo)a$QU>~F1dzl*g;Oenu=x&?xsFi5N7k~Fih8`iEX z!@ve)Q*)~d0v~9}4N+&C=#(2Gh3lAJUTvF=6tS^{T;44IT@s>X!CU<-o$@{dxW{3U z)0ZI%-sXFK#3FPt7tsOjuSDlQ9vu!j>h3S2Lwi61{=nGvOtBefR`;T_#KkUEJwDK9 z+4$uN&HEN#a&H<6s)-k#8+B&bR);p20Cz`+s8Y4aO0;Fh{&tv)8Ny71vsIdrlhVA= z(q&1ky=JlF%?(!hyjZtAX-Y|}8gNkxlQiV{sK4pAc?L6ZITQPOS6`)uN<0(0q5W8+ zMvbdhm^zN>A#J{KRW?j%R+qR`OtsWitVfHx~(x^gLJPfiy*?o zTQ|o_uY^R5@k(s0IaqaI!z7h1lys)+nHQT|KJfD<>l147pjW9$btd$cXkjedzg`sw z$#?0vyRBZD)`dS8+|a=+wQc4qF?wCcm0lXd#17sgiYUz5*jf$A-1h=^&a)rNJOWGf zI3dYkVN#%E| z3bo9Ma015RVudv%PF03SmWcI%W6$>sK3L84{kK>H5c!*K55$QR<_&9L%xU|Ymw&&9 zd*XIU0VH*Fqc^EZ{#4U$FUjKFx4W~=%A~A|ZjY?Z7X?&$8cn6=pKuBug{hVYRW<^`feQZxE_ks2|vfJ_dHPa+? zU7(53O;U-T!^{UwbFgo_-#6VS3H7-1_XzXi0wE*tEZd2bF{yvFVUY4z0cTiNdPgH| z29sGFMFkOaj$^Hy(}%b3Iyi}WqF3sg6%9U2-UOje2HGm_T-H?i?Gz^KQh1b1off*d zk0p#-m{jmOsoc9FXyGuKRpWk`{hUGp+TWY@>zHec$vx!!vGIf%J7M8;@qH;xF(ohLq$R1t zn2eqI`VQFlJoCbYXOEhzFtbAe(_o0O11#!l{CZ`>JxGMXd$0W1{iX2$6Lix_ zZ!4(L)x4v+TEyd>MYf2}uo`bq5#puY28$+W>z~xN{o2Q|%qMDG4`(|TZT)lC>~v^# zgk`hp>IchD;K?#I+DZNXd=?K`9+r=H3$EpGbgVzmS8k;^9+wA^JePU?2)k?p1Hp*? z#a;I5=i4A9V|=fdbuB>Hs;<-WG{TzGHovdI=a`8N+s9c-RnH8yr#ul|` z6ikvUa|u}gPaub^Ki5`fN6OLIf<>%(Ayxv3mPXn;$y`8Ydc~Y433!2zm0+xv?l%A3 zd-odX_3}W$UUeQVC^znBoF%pnB%a|*O=)zVTt9DC2%|oWr^qo6VtfpXiL_(@KW0$5 z$x!&h2}hr}_w^M*H_wTAp1=L=QVEC(DYo5AB#oI1+EbmF#rCrH90h6BJYb+3R=00i z3x1e00~Q6{aI6!(d=y+BEP1`Hb>p8rKsgyK5$J(C$L-SP*W^J>aQE&M18T5Bmm?k=$>y^fv1&4omE3|hmu<%0k`-h^P(#i~3iAe|8 zviKu_SAD7;_d9g<-!34j%bO{aT2JRg9e$^1UN3|!(HXYqJEbKJd}R)1xIa#1K$V`N zsILO2%*6Pv1f0B-w>%o}NbRW& zHBuXgI_~;OOS+tQ++|>ld@&`OeLD8a`5yfJU7Oo(?Pzg64sEEEetTB@f;oyn2 z))0f7gCr7|pe`2pe6~{GPVpOM=ySmIx2?MJz4o7eCUazAykj2Qf%w?2_<7ONv4efv zEQ3MX#DWj$6&L`opEC{cz_6=fs`MR9PAy6FS!K3{@m%u;)7;tOannqk| z%Sq79vs_$?f+!+2X#C|)@Xg%LW%s;e0$PF;FJ5X!=|-CHuqq;Oxod+!>2q)I8IFySA}~at{%tr z-kW;o%1!;Pl($mu>Qk4gP|qO6U7IUu5|ej< zTC6t*Q!~~4#y_Rl6bk18Y_4*QL@zOpHo~2s_fCRG;yKo|=leBRnj|mz1q`^zom})I16MW^}wKK>4ysyUo?l}+r zQ{3#YKVHw?#2X^cOYUR)b>>R#fPiK?{1*PljFYIewnSZ4M_H7;dSM z!MfCis&8$nf|~l4{s5<8<{{UOT)Q|m?U|S38?fejpchP*$0hV|vW7J8K&OvEFao9N zQ_ujudk>&r*-)|9pqfk7szimUO;L~e#+VIs6gz5}4hGx8AlU1(yYeK$-WfW+Q?F>3 zLzToem05Q12z(o@l)=78X-4yeErCtZ{-txtmR4A@8&uH>F=7czSycF0hS(X?rRm;b z8x>0mxZRcADrv+rW5;h~ptPD*VQ=!~*VI5=#N>*@B;qxj5bdPhylreyx`95Yj_%b;h#8xAP;2fp7QAgC8=)IunUvxv_*@;(2-?&v7lrF+iV%k%(u4G zg5hqXcumD{HPFeUNNs{``xbevKdKHNCY+$&|7)fE`@{7(ETN->@K5v4vVJea=YVd( z;kui?zUXxXD=7*h)w!*|e*w6%H;yG!-RVfz_of&-6tAYEooV@a=n`sKJvhTK-u`*2 zWjJBiR33ia)dzwSbV<6IVgs_fE?h^t*#Zg8goKUhA$)QW8obHZmj2vEp9zw$k3Jmq zWN!OGo>MFARCE>Z?pNNgcxMtD#iB&NEyXWC!w8<%iaP*L7BvVN`J?zx1VsL z2`C3+geT~J?P#W0O{v;|AaJ6(fdm5`DZU}otcuyTpwyunXPn5>J+_h_>lT+dVbhlQ zr~{Bi^{(crmZqtJc_VSl^)YdkdDW&1T8}V2Orjkw34XW=W>`2xz5twoHfGB_WQ>Uy zMirzH^TFx8-iYW&YzdpB-B03eJckWd`#6t)Eg2{JTnK=P*pR0&&l1X2hbStHYp>gu zlKnoJC~pY*dbgt=6ex!AF!uWQn>~1K^@+4|U1BfM-G51{KkBr3<3rc)iL0w(Lw;?` ziCq5P`)`OE&I_q80OulvC!e}DT>XgB z+yLc?9imfXc!Fzy?v1~6M#jer@H8Gr?#E%tP7|FCV8}7-6wGig5q22Y`>#13RO>vR z>fTM#kQxCEbqI~MoRQUxgVV>u_g-T4!qCAPZ&TiVf@a??&$1)v+%d04yP^?hyC`zC z`-`eCv(Qxt^-6Xz^XY!m8gQH#D&skjAQfJT&ZtU#I+9=Gfsq%c4uQic1r z>AXWnx8F9H7fm+r;F@1JifO_<^iJ#TPWGWun# zQSI8~uY>E~o@j^E>Y)RqA*1G>4?F%w+`5k;h?{K{Z4Gc9c;f|L%=Oz_P8k>w`^u&O z8sC9$ew^Ndv1Kx}mo7mF>A0uHA5E^=UJXbk2wtiWxuprmP$xo^*zMd#L5Hawj&*K5G^&hVa3m?OyG6}UW$9vHuY3dw35U4Fmv`{LO9p4xH_$bQ`8;pil+!k zv`?_2W7vwD=V|AtWzXrdSi>PY&y!r_l=VHmhuL#yU;~rw)!oM<5~UtesbU2QkrH&+ zH4GNbU4VUuOVz@O1k@F~5DzkvOTay|%!&G3rr(2}e48tlU2rhm3zT`!u!sHifxndy zE6zK7iO#{01mWkQKP8#PJ4N z_I0LesYcD^!LZi5)X3zWx-FLgFRWlB=4joWVi?!2O51jIh5t&v-OQ>;aqA7o zSj^$?NczOk9>3L#)|cj#NkpN`(=Top4AGEps!{J)O*s86NoY6e8P(2T$kM;eyhpx! z=o$4UI{R(q)92k%KVPP(4OHs#PMW}j`b|o}3S>v8igae1UD1%2=px;l;P764;}JDy zr4f|4cR!=WR}^HOzo8 zgv*A@N%}>%Z85rrS{eE`K_l@j<#wbMZ6*K_2d8Zm%6Q~lK=H7Gs$lv&*^l?B7kYti zZrZ8Pu@^MG0J-&jT%7hFg#u7g5%y*>K`&MKxMZ79gV#1uk3u~hWB(L)`+Y6pP6)2T= z6=37yWmYU(miqM~mSV1de>=f8AxvI+sqNL8l~L&Dz2Az*1n+on%@I_Xe^sGgauSXI z=>@R)y#L2)6*lCaY_nit>(9;j-2v;xj$=c3BGi6z0g?d+uiorP)e22kOQOBV?}$Y7 zbt4YTwSz~<+d>m|ri3V)oCw&f54Sl6tIK`hadJv+oQ$v~Eer5l@AW!;TBn{`4^(hS z9cJh*bY z4j&eHA827dArtaz8qf{YJMDe_x&5)EWH9Ps{=XGzVD6mFa>rIr_8=y zyNat=Nu~9o%`902lk!ME?F{Poo)yGS!fx$+YD!!xv1mehQMi*xMW)OFPPMpe7L45V zHj6E4ol7J8Uc)k|GY(MJa#QPI!uYmQ%7K^UumZTF+$0Igg8m(?xpAUU7Ti8%f{&wF z8TsHe%ji(NE zLSSa3T6a>m^b#b6aT#|4va$;4jHScS0?sq~-W~W9#~aUQSSniBtyex6$YwqC;X&VR z$AP0pqZ=88V5quQtD_uwMkv5j|4P9X`;bW($rEv#yY7NL;>JnPa_t0^!1c{jyYiK4 z$(J(5w1%8Lep{Xct)&eou#C$i2E@&pFEJ)_5y(CTk~%66JU!nt?dHcO-a;H+)<@4q z++yy~-ot*DRq0CmS?dys9P(swqSR|1BKPyRt7%QIDt*T+km9raqGKAtT*EO_zh3}* z^<^^mF+{K9+RLQWwS<<|ZJq};*8mXqpAR~-=!BW9z?$kCUm5 z0ZCwB?~kU-#ALm;f-_sC>S$Pb6+5Zz^9upP8+&WkPTSq|D1XC@`KWs02q8Q)3ibOC z{Bv0^Ow9Ya}vffwi@?eq4`EEh!>&Z7h-(lRl#bVtK8=x1GbqW&^S4y`QPU~Sw+RQ_c;+TSE&Fhc zW=F|<(yB145^sGL>R2E6fvm}LaV3r3wQ6bFoeqPSWgHo8t00i-db%(4`xltSOEH+c zFbt={80Or($ae}7!MW&XKkZejg^6=)62-W`5_hpJ&3H56KP(Uu8?!B5D#5QigL{oV ztvq>R1+3lD0sqSg)(SYS{bms=(SmHVI&#lx8|kop3pxjIwrpQk}ggIl$rw#%t<5B!^_)e$8l00AuVjoxnW7n=F1s;oG~Z{tO?C; zyg35=AQ;klqs&hMAaqiHjjq}|c@sbQk^RuXpjtRJAm&+ey^sa_fK`9sk<8(A-MqOj zx$T;;$t%Jpk3P(-$U{JqH6l+A=b_pCmfmE(>2x&>GQ?RrK?CtrAT%X1d*6L~|9gX^}*;#k1T8;>T}SU>(-09|wS~>W5T?(c)v|$>e@; zSTcv@XkVh8Hu@T$b(h6YI;oQ0to7|>#=~W{y~S>Q6{hRpQ^(l!Am@ ztf7RM<5D?!4kUXFMdojRum9`IUZLY9m~`-fVDvqk z9=zVy@GUlm%9}R3=taG`npZsUVlM<5^%0Lvi@JA6o{IIcmQrrIzhm$54{1T8P9ImB z0MfRi-JQw~K~bFvS6J-2DJPccyJoLZLYPzS;uQ0d?p7w<)(8_b`L!s=NSFkLMh+qz zu?cbA>8)xZYvL3704e03qnlO32LdSQZULOk4Rtv4CI0%%|Lb!;YyJNDv`Wl!%a9!g z#f!1NS^`$%$Wt^cK5CuYO_+(w-!99Hm3V6@hn+#Cj(rw;VFjXs zET{}h3i54x#l%71NLeDAdIS)!QInWx)zv;=Xj?@=E=wS?vCFl*>;nQaRW}dYf1+>( zgc6aJTP?+ipk_K!{ghi;a$ntbpjl}d`t>@6dEb(tn_TTF2SVijjBK%i-{5^Y-|aqX z0oq)Ru|^<@q#R12x>Vfh%7?jUd?#LT@U83cgS>JroS`?0+eYiL;=EYrs95KEg%jknVNC)mLq<>y^to?M+_8C;C}lxy6f=5=C%V+WT;;zN9D zrJ0tISKfvpUXSb8zR6(`1xY&c;*=F=jU##{%T!*U??&J*TpQ6s%r5Q4_`G4q4pg9K zn~*`mb)9-pA*|Nyx)yz+x4$c^@kxji=RQ)~GHS6+wM`>Lxn|_7FtzmwhQ>$M%x+axI>4(@#WxkNti_ zI+Eumb!t*HNW%Kek7{xqCohlhO^uP!hOtFlzrmJM`w;4fpg82)?oDr8=z57~W zmT>kuBYpj0)~VA#1>(r&_8YEs6-}uVdkvyhnh?pn>lUdc&#M-<`bymr%R#<(tK<=>fr|o#+~*+PIWOs|bTKZ-k=C+UFr2EJY%l^nXMKpLvJ5#Vj;QN4L)Yi1WGAQb0v^7d1hnn&bHXwoG@#vC+ z)HhA3|HuKd!~^#@nJ!QM-fXW^wa253kLMty?fyW<>k8x$KO6d02xb8G<614?tm)+; z=A@5%RWv!Ly)$u4R*{wp5aiWNvnYNsa9Xfq)XOo!;H)S$9YPoPReLIrE;-fjeQRQN zc{`=me#d1FzgVMIw4>b52K1kn-{1X_>+mMQqi8XHOZPXy10mW#@Pvcpk3}MGO)pe* zh{gkH!2{R69;7{520ZSKMR15 z3X>74Fn-jdWg?3idf4IDA#G}-nybQ57p|agUn=z!hLATft zIAY8t0EWxgaTf=Zpi!#9p_#v`I!Kw68=Gkt7&IjUHOqY?K`x+xN!z)j>8pf$$1Kxw zXX))XlLO8H^!}(mzn-T${3$@3FsEH7RAYA!gT!>OkBkH6AWFn0GVf+vHAjK>Mf?7e z_1eeOUaE*o`+Aw?N3rG5KP4U+!{WXt%6#^$`+ zXU1QfmT}Fd#wPUiIYN@8wHAgmU{=2)H8Zt^Ba8Q4ys^?LWsEfvlT5Y9lN5xo zi%0q?KllPx0tC#MMn|-^a?LpGy5)jb)5>+1z~WN}(U{pZLNhGGkYnDIj|5`&FZ=N_ z(V!RNjZq&4ocWUKI6L0Sl0>h18NC8lQ#q`G=|+$(?PIKBgw^KW;sIk0%7dIBsB$7= zlm$DO&ud{J;TII@|!1`28F=ZbHab|CUb@z~0 zhAHM^uu~y;ingYrwezjJwWD%w8>nxk9QXhVZc|MLryhSCZRjp`v39yU+q<@hqYk0X zK57rn8si6U#}%O_+3bpr`FjHyXe+m@^hSaDi)llJ^$s$maodPEPilwiHQ{t}Zg4KF z%FV1N#~IEzu%+&a!EWVMV`Yz{J6OY)N_A7vlh9eBMXiBB35s39t}?Dg^L31th2UyG zK=W`rDH|lVa_Az`*$>*6x|Ws#b`}ecY zu7QA6UOU%Y3Mbqucbq7!$}QvhmWUOaja>i3$mc@_t&-Dt`z#L)0wM|hxotLNu+s3TwZ-0K81qukA7UVn z=`t0n&v0p*of3Qd1S>1(nbZ_ed9{U)l^?^IhW1mfFr{un>Bmgoj#IoDd2zTVwBRC# z*BQ|KMGr&xSuUc|yFpzrz#7Cu7;T)b*v|18@X7sWpC_-zHs$lsO5kO!CC#wWp@6}&c74_mT>9K6nq2A#;5+;AAz?9) zrAP5t+_>6yTDs{{7b*JfV*{-6pi~1}*N|6C1_(-{c)?(FerK-)*|6PQub&% zmz>~02fn_^v)>GYJBv8flo#A^&oPNKs6PrPUe^P;)Uyj(SU|HGNmra2n>qe^ zNyQSjBCqBx?yh?ByAH}WM3xla$qe`}SI~_KI5KSEX6A(PYPq94$2-%lK<5GLUb=&7 z;ocINBbH6XdS<xa0U7PPQRUt}9~o{FD;BUACrlD8`9JD3f!C4!Jox7rb3CX5JlSGEpUM}i$3WsFGWsVLKLjC&C! z6o4R0>s#(zubdw%3i@#IQ!dfU&wDFnPm`|uzu$9z5e`h@n@o9?aF^(Bagh`=Xo%?W zH^pxQxlr33^as@^nytKFAHi4z>Gh8WSL*nV-K61?bI-mXHI@FiJHgLy+&0*(OZ7MC z?J2*z=EJZ}$k@+`Fp}t&On9k^PG;U{*l*SP_QAzW@cHY8l6nLaTW_{EgB@8Qhvqkz zKwYWMr~7|bX#DG<*!L^y5aD98fQz}LR>-f8{IMFW7g0utf#JwM^03(qrH0|0@5jsTpTU|tM)JL@+ znjA;VQzw7^>z_C9zg&ewCw^R_PY+_IgEf=wU@eOPV8M35J(tg@{HtUY2tDnPF+)V} z$AeBDz%_jffUPQO_15r7gW@y3^7kruk&pk5;i#a`NC9KlZMAd-MxvXkLbHG;j38|; ziJxml>c$V=z5Dd@mcPD6MVy=0sI$?Px&_1<;uEmh0si?J9|8x$XRuwefsZ}kg&L`R z!KY^p_(Y%RQ1nGp>y|kAtVi5^qEB^j%|lQ_njp$p8+hXT&48LU)K>SE{F_EVJm_7N zLw~lAn`Fkq`Kg0PCr8h$&nq?yv>qk1OXDpV`~FUO_iXb4>po+&Z}ap1S|Pt4!M}S- zGvj}J&OC?ndx3qc@`4`38C)O{QJ$$T^!8m)bei5faQl)b`ok5d{2vb^d!Al_?E=mK zLkBdP?N%zUJlDHc4MoW|3w#9+ufkn=zc!!K<^_VQA!eZDW+aJXUPBgU+cF@8c6C;Z^Lj1H_%tNU_PI zl`)Ak{n3*8D#4=}zT616_$>4R)9*EZEjHhJ5P-o`n`6!u|JNe2+gCO}L|XJLl=Mi` zw=mt?!+cVEKpb%Ba>M3yU?%#C0le0Rqbl8VWzIWB$q?T9lgqsy;*Ga(6^yQ_!!xP? zK%LiT*ysMX;2w8xRxoCZ^k#oYw*Fj=zh3s^7E0N|R>(UT+Yj?P*8pbapViUA_4RQ| z1fsXAwMt7y`v61i*oJS zw%CFw2uLU*f+7vlosyE$jnds6gOoI=bR(TZgXE}`fOIp2NOv~_-!-_^z4x=9_u0qs zyx+ffAN&}Hdk=G8_qx_v=ef?cuIHlQ_M;Ehz+V0DS4JD{DuQ`j%YD`U_#m8;jCI?h z`du9h&rvoumxq8%0-68}9B$qQlp0uIA|Jf65P8&~5+HuPwA2FDM7s-x4mmi^-4g>a zNr~&y|0`B>`{qtx1mWfpMf^ceOB3%g=Y*X%(ZGzq4xc3>xx!a7kUdlY4cIZzZq&8; z0hX}hXm?q?&SkeY&>2*>5aIH0Txx zLI9e1f&j^k98qf5*BU1S(eXHtEAYWVTM20>v0Eu1*DSAtV{@Zn)u4U|BD4RpSAp?mBKom`RM1mysLpy?#BvNAc@ee&R5Vrd+CUV$?gZo;<;W&vQ|h}e z_#Zvax2uB+*k%Eiv>Za%k-`7zDYCx z)EE|M{+q*^QyP(T<5ZJFy@GU^%dScM;}>2x2p->`v$z%xw6gY@U&lemB|a9j#I2oU zKq{MW&9v?T$%Kf)H}s?hFo3BPw6}jjwy65Qj1#m1+L$sr&FU*Pesrb@$krL~B#sfc zHjY7KofJvcZ6qA>+F+Mt4X=!=w zr008B`pk@6xpmb4ot2aAmXWudSzy?k;v>g9q^Zr=?r=DG;fHtVEXU*NWru9k%$<>n zWhAq6RGb8p9;a(R#OAhF;eq}Z-7`RMBh&NY{v3JD{q7-9Vu};AMe_AWiv*y`6ARQW z9Com;%?qF+yAc99J6~PDjlF_bou8rs&}r>ckB_0C;M@0}%WeS(JDjFfM9w2K!MSOm z{O{bL3NVfJ4hG#-1ST)d+gKIQbhJELmBwP>X7%PS?D17adtQ`Hu&b^ z_JM?I1T>GCgXlv^q>j6YWHWtNuBrE|1D{t02K(2z#40T5s^;e?kh*n>FV?JHB z?z1lft3C`yPOgG9C)oKj7}edHtB|7;PA*gN90j(}2{b{Rd!RUF2+C=}YZd@9uoAg~ zKwlj%=bpV6xhrw8iMNN3{Z6=zd_kiRsM20Ak*v}9tU!@30NlpL`NDnC^ zDbTE8okaAq$FrEuX%<4YhxYi+w)v858-=%jJKyM@*8z1FeemdiC2DHF0!^AXT0&jB zCR&fhML|zy?Rf8@0ay032D`g-t1ZAM!s<50fVKt8+RS&O+J4ypbj(%eD(1PsL6)ai zYS2*v0N*E2cO~f2woC;*BQsJ7>^&}fE4O=;2R4Bue%5yxFmm!QH^5BrGBB;%X68Z-&>F(jrmW+7j9Pk_GBXt}DQ!vZCO6OMUzR=9PZQv#JDu@N1^q zi}xTRIDf1I^{NBJt-G~AH&h^<$a%_HnW8Hb%h&{zQEN)hg~;}{L@*x|@{%63!vlc5 zh=K#Sa#P?i_surHH4TEMI8&f;6Z+hh2s+3zw15=!$q*EF>Vj;+McgD3;3IGNJqta< zih*sZyFTyrZff3iSq6~-`j!(3jhz4c;Nt{5%x2v_UlD^POg<&Hs!uv{HA{@@S8y->07kN{mXTPWFbjDB9&h zbUKQ=>{eg-3$atwC!v~Lrtpda;t~m#h567lWnVN9iI{=B0DV+$ zPPf!3Isn2B4kosIy?Pn#=#ZJ_d$Ro%WNZ>=LV%3609pH(U%{``E|&HjJQ#Gziu`K7 z`ny~m`QA(vKzTGk&-FC)%*rbSvRx2iUIYeU-dyF`>1IP1AZJTJX{#Msn>!l@!pAbGX3Z#&XDt;_ zOLlkh89f)!1-04&{0kbT1|iCbbpU3{0YiOM2j(f0E)N%{0XynY7wzuKdhvS!NCxD% zN932lg9Qa*BpCf~vI=qVV7D63aLi}28N@rffkT+{J+Y{8UmB9h^$>R$tuT-80Ma7# z9+Z|I00K-w#uG^?|LFawb}0VM>tJd$;F^HHEdxT~<6~UADR9JW38)N@fM)(FqSPV* z7^p@yASI-TFQ7vg2*9JQj6lr5?)m*@dNi5g?~Y^KiA`TCc$$I8+ck_0Y0gYfN
    pGQo)gwy-aIP}#Ofv~vbQDhibf!W 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⁢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 Date: Mon, 29 Jan 2024 02:55:36 +0100 Subject: [PATCH 46/60] Add status `CANCELED` Fixes #547. --- .../03c839888c82_add_canceled_status.py | 51 +++++++++++++++++++ .../conda_store_server/build.py | 20 +++++++- .../conda_store_server/schema.py | 1 + .../conda_store_server/server/views/api.py | 3 +- .../conda_store_server/worker/tasks.py | 9 +++- 5 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py diff --git a/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py b/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py new file mode 100644 index 000000000..60e3ede64 --- /dev/null +++ b/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py @@ -0,0 +1,51 @@ +"""add canceled status + +Revision ID: 03c839888c82 +Revises: 57cd11b949d5 +Create Date: 2024-01-29 03:56:36.889909 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "03c839888c82" +down_revision = "57cd11b949d5" +branch_labels = None +depends_on = None + + +# Migrating from/to VARCHAR having the same length might look strange, but it +# serves a purpose. This will be a no-op in SQLite because it represents Python +# enums as VARCHAR, but it will convert the enum in PostgreSQL to VARCHAR. The +# old type is set to VARCHAR here because you can cast an enum to VARCHAR, which +# is needed for the migration to work. In the end, both DBs will use VARCHAR to +# represent the Python enum, which makes it easier to support both DBs at the +# same time. +def upgrade(): + with op.batch_alter_table( + "build", + schema=None, + ) as batch_op: + batch_op.alter_column( + "status", + existing_type=sa.VARCHAR(length=9), + type_=sa.VARCHAR(length=9), + existing_nullable=False, + ) + if not str(op.get_bind().engine.url).startswith("sqlite"): + op.execute("DROP TYPE IF EXISTS buildstatus") + + +def downgrade(): + op.execute("DELETE FROM build WHERE status = 'CANCELED'") + with op.batch_alter_table( + "build", + schema=None, + ) as batch_op: + batch_op.alter_column( + "status", + existing_type=sa.VARCHAR(length=9), + type_=sa.VARCHAR(length=9), + existing_nullable=False, + ) diff --git a/conda-store-server/conda_store_server/build.py b/conda-store-server/conda_store_server/build.py index d0257be88..3b0d30a79 100644 --- a/conda-store-server/conda_store_server/build.py +++ b/conda-store-server/conda_store_server/build.py @@ -48,6 +48,15 @@ def set_build_failed( db.commit() +def set_build_canceled( + db: Session, build: orm.Build, status_info: typing.Optional[str] = None +): + build.status = schema.BuildStatus.CANCELED + build.status_info = status_info + build.ended_on = datetime.datetime.utcnow() + db.commit() + + def set_build_completed(db: Session, conda_store, build: orm.Build): build.status = schema.BuildStatus.COMPLETED build.ended_on = datetime.datetime.utcnow() @@ -65,7 +74,11 @@ def set_build_completed(db: Session, conda_store, build: orm.Build): def build_cleanup( - db: Session, conda_store, build_ids: typing.List[str] = None, reason: str = None + db: Session, + conda_store, + build_ids: typing.List[str] = None, + reason: str = None, + is_canceled: bool = False, ): """Walk through all builds in BUILDING state and check that they are actively running @@ -121,7 +134,10 @@ def build_cleanup( build, reason, ) - set_build_failed(db, build) + if is_canceled: + set_build_canceled(db, build) + else: + set_build_failed(db, build) def build_conda_environment(db: Session, conda_store, build): diff --git a/conda-store-server/conda_store_server/schema.py b/conda-store-server/conda_store_server/schema.py index 0db0e535a..52142b4a9 100644 --- a/conda-store-server/conda_store_server/schema.py +++ b/conda-store-server/conda_store_server/schema.py @@ -156,6 +156,7 @@ class BuildStatus(enum.Enum): BUILDING = "BUILDING" COMPLETED = "COMPLETED" FAILED = "FAILED" + CANCELED = "CANCELED" class BuildArtifact(BaseModel): diff --git a/conda-store-server/conda_store_server/server/views/api.py b/conda-store-server/conda_store_server/server/views/api.py index 6cd3ca7a9..72c6826cf 100644 --- a/conda-store-server/conda_store_server/server/views/api.py +++ b/conda-store-server/conda_store_server/server/views/api.py @@ -986,8 +986,9 @@ async def api_put_build_cancel( tasks.task_cleanup_builds.si( build_ids=[build_id], reason=f""" - build {build_id} marked as FAILED due to being canceled from the REST API + build {build_id} marked as CANCELED due to being canceled from the REST API """, + is_canceled=True, ).apply_async(countdown=5) return { diff --git a/conda-store-server/conda_store_server/worker/tasks.py b/conda-store-server/conda_store_server/worker/tasks.py index 9d33e5e64..e87cc6a99 100644 --- a/conda-store-server/conda_store_server/worker/tasks.py +++ b/conda-store-server/conda_store_server/worker/tasks.py @@ -106,10 +106,15 @@ def task_update_storage_metrics(self): @shared_task(base=WorkerTask, name="task_cleanup_builds", bind=True) -def task_cleanup_builds(self, build_ids: typing.List[str] = None, reason: str = None): +def task_cleanup_builds( + self, + build_ids: typing.List[str] = None, + reason: str = None, + is_canceled: bool = False, +): conda_store = self.worker.conda_store with conda_store.session_factory() as db: - build_cleanup(db, conda_store, build_ids, reason) + build_cleanup(db, conda_store, build_ids, reason, is_canceled) """ From 8e2027ce93fae3da4a26de237fe6c51ca302127f Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Sun, 25 Feb 2024 23:27:42 +0100 Subject: [PATCH 47/60] Remove a no-op block --- .../versions/03c839888c82_add_canceled_status.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py b/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py index 60e3ede64..1fc93f4b4 100644 --- a/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py +++ b/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py @@ -39,13 +39,3 @@ def upgrade(): def downgrade(): op.execute("DELETE FROM build WHERE status = 'CANCELED'") - with op.batch_alter_table( - "build", - schema=None, - ) as batch_op: - batch_op.alter_column( - "status", - existing_type=sa.VARCHAR(length=9), - type_=sa.VARCHAR(length=9), - existing_nullable=False, - ) From 809874d11ed93a256601cdfaf22114be7cf3d0e9 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Sun, 25 Feb 2024 23:53:05 +0100 Subject: [PATCH 48/60] Update test --- tests/test_api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index c5af61af3..cf6086d88 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1198,7 +1198,7 @@ def test_api_cancel_build_auth(testclient): assert r.status == schema.APIStatus.OK assert r.message == f"build {new_build_id} canceled" - failed = False + canceled = False for _ in range(10): # Delay to ensure the build is marked as failed time.sleep(5) @@ -1214,8 +1214,8 @@ def test_api_cancel_build_auth(testclient): r = schema.APIGetBuild.parse_obj(response.json()) assert r.status == schema.APIStatus.OK assert r.data.id == new_build_id - if r.data.status == schema.BuildStatus.FAILED.value: - failed = True + if r.data.status == schema.BuildStatus.CANCELED.value: + canceled = True break - assert failed is True + assert canceled is True From 52eeeaed2d79a88af3c03e1dba1a3423cb022f29 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 26 Feb 2024 00:31:22 +0100 Subject: [PATCH 49/60] Check the logs --- tests/test_api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_api.py b/tests/test_api.py index cf6086d88..7ad7e34dc 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1216,6 +1216,10 @@ def test_api_cancel_build_auth(testclient): assert r.data.id == new_build_id if r.data.status == schema.BuildStatus.CANCELED.value: canceled = True + response = testclient.get(f"api/v1/build/{new_build_id}/logs", timeout=10) + response.raise_for_status() + assert (f"build {new_build_id} marked as CANCELED " + f"due to being canceled from the REST API") in response.text break assert canceled is True From 4d4392ec3cc8d99b4bebe3c0c52e1b6eec5bb5e0 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 26 Feb 2024 01:27:08 +0100 Subject: [PATCH 50/60] Update migration --- .../alembic/versions/03c839888c82_add_canceled_status.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py b/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py index 1fc93f4b4..7fea59d8f 100644 --- a/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py +++ b/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py @@ -38,4 +38,10 @@ def upgrade(): def downgrade(): + # There is a foreign key constraint linking these two tables, so need to + # remove matching build artifacts first + op.execute( + "DELETE FROM build_artifact BA WHERE BA.build_id IN " + "(SELECT B.id FROM build B WHERE B.status = 'CANCELED')" + ) op.execute("DELETE FROM build WHERE status = 'CANCELED'") From 6f113b6ddd423d960233d3c22d41b17ef889c15c Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 26 Feb 2024 02:06:02 +0100 Subject: [PATCH 51/60] Update migration for SQLite compat --- .../alembic/versions/03c839888c82_add_canceled_status.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py b/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py index 7fea59d8f..a64bb8c03 100644 --- a/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py +++ b/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py @@ -41,7 +41,7 @@ def downgrade(): # There is a foreign key constraint linking these two tables, so need to # remove matching build artifacts first op.execute( - "DELETE FROM build_artifact BA WHERE BA.build_id IN " - "(SELECT B.id FROM build B WHERE B.status = 'CANCELED')" + "DELETE FROM build_artifact WHERE build_artifact.build_id IN " + "(SELECT id FROM build WHERE status = 'CANCELED')" ) op.execute("DELETE FROM build WHERE status = 'CANCELED'") From 6655060034c6b67d7b457d5d9e76dce97eb5d202 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 26 Feb 2024 04:06:05 +0100 Subject: [PATCH 52/60] Simplify migration --- .../versions/03c839888c82_add_canceled_status.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py b/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py index a64bb8c03..8c30d3b50 100644 --- a/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py +++ b/conda-store-server/conda_store_server/alembic/versions/03c839888c82_add_canceled_status.py @@ -38,10 +38,7 @@ def upgrade(): def downgrade(): - # There is a foreign key constraint linking these two tables, so need to - # remove matching build artifacts first - op.execute( - "DELETE FROM build_artifact WHERE build_artifact.build_id IN " - "(SELECT id FROM build WHERE status = 'CANCELED')" - ) - op.execute("DELETE FROM build WHERE status = 'CANCELED'") + # There are foreign key constraints linking build ids to other tables. So + # just mark the builds as failed, which was the status previously used for + # canceled builds + op.execute("UPDATE build SET status = 'FAILED' WHERE status = 'CANCELED'") From 6ca3be846fa004d207d3b07f2e649d370648f2a4 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 26 Feb 2024 04:07:21 +0100 Subject: [PATCH 53/60] Add missing status to the test --- conda-store-server/tests/user_journeys/utils/api_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conda-store-server/tests/user_journeys/utils/api_utils.py b/conda-store-server/tests/user_journeys/utils/api_utils.py index 8608d766a..4fcef9035 100644 --- a/conda-store-server/tests/user_journeys/utils/api_utils.py +++ b/conda-store-server/tests/user_journeys/utils/api_utils.py @@ -16,6 +16,7 @@ class BuildStatus(Enum): BUILDING = "BUILDING" COMPLETED = "COMPLETED" FAILED = "FAILED" + CANCELED = "CANCELED" class API: From d93dcc7e8e7a3686e9b85d8b44d51deb5ef9190c Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 26 Feb 2024 04:12:49 +0100 Subject: [PATCH 54/60] Update log messages --- conda-store-server/conda_store_server/build.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/conda-store-server/conda_store_server/build.py b/conda-store-server/conda_store_server/build.py index 3b0d30a79..f2b349b08 100644 --- a/conda-store-server/conda_store_server/build.py +++ b/conda-store-server/conda_store_server/build.py @@ -85,10 +85,11 @@ def build_cleanup( Build can get stuck in the building state due to worker spontaineously dying due to memory errors, killing container, etc. """ + status = "CANCELED" if is_canceled else "FAILED" reason = ( reason - or """ -Build marked as FAILED on cleanup due to being stuck in BUILDING state + or f""" +Build marked as {status} on cleanup due to being stuck in BUILDING state and not present on workers. This happens for several reasons: build is canceled, a worker crash from out of memory errors, worker was killed, or error in conda-store @@ -126,7 +127,7 @@ def build_cleanup( ) ): conda_store.log.warning( - f"marking build {build.id} as FAILED since stuck in BUILDING state and not present on workers" + f"marking build {build.id} as {status} since stuck in BUILDING state and not present on workers" ) append_to_logs( db, From cc7726ad6f267a3d65936fb6ec635aa904c731b2 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Mon, 26 Feb 2024 04:21:44 +0100 Subject: [PATCH 55/60] Show artifacts for canceled builds --- .../conda_store_server/server/templates/build.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conda-store-server/conda_store_server/server/templates/build.html b/conda-store-server/conda_store_server/server/templates/build.html index bd36351e6..35dd76fdb 100644 --- a/conda-store-server/conda_store_server/server/templates/build.html +++ b/conda-store-server/conda_store_server/server/templates/build.html @@ -61,7 +61,7 @@

    Conda Packages {% endif %} -{% if build.status.value in ['BUILDING', 'COMPLETED', 'FAILED'] %} +{% if build.status.value in ['BUILDING', 'COMPLETED', 'FAILED', 'CANCELED'] %}

    Conda Environment Artifacts

    @@ -89,7 +89,7 @@

    Conda Environment Artifacts

    {% endif %} -{% if build.status.value in ['BUILDING', 'COMPLETED', 'FAILED'] %} +{% if build.status.value in ['BUILDING', 'COMPLETED', 'FAILED', 'CANCELED'] %}
    Full Logs From 61be69f1e09f29c0562621d4fa4e604bffdccdf1 Mon Sep 17 00:00:00 2001 From: Nikita Karetnikov Date: Thu, 29 Feb 2024 11:27:18 +0100 Subject: [PATCH 56/60] Fix linter failure --- tests/test_api.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 7ad7e34dc..9583249b1 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1218,8 +1218,10 @@ def test_api_cancel_build_auth(testclient): canceled = True response = testclient.get(f"api/v1/build/{new_build_id}/logs", timeout=10) response.raise_for_status() - assert (f"build {new_build_id} marked as CANCELED " - f"due to being canceled from the REST API") in response.text + assert ( + f"build {new_build_id} marked as CANCELED " + f"due to being canceled from the REST API" + ) in response.text break assert canceled is True From e652f03193e882a7ae7e537f5504f713f4e706e8 Mon Sep 17 00:00:00 2001 From: Pavithra Eswaramoorthy Date: Thu, 29 Feb 2024 23:34:13 +0530 Subject: [PATCH 57/60] [DOC] Document setting environment variable (#765) Co-authored-by: Nikita Karetnikov Co-authored-by: Tania Allard --- .../conda-store-ui/tutorials/create-envs.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docusaurus-docs/conda-store-ui/tutorials/create-envs.md b/docusaurus-docs/conda-store-ui/tutorials/create-envs.md index 0636ebb06..d2abf07a1 100644 --- a/docusaurus-docs/conda-store-ui/tutorials/create-envs.md +++ b/docusaurus-docs/conda-store-ui/tutorials/create-envs.md @@ -83,6 +83,22 @@ To install packages published only on [PyPI][pypi] using [`pip`][pip], include a --> +### Set environment variables + +:::note +This feature is available *after* conda-store-ui version 2024.1.1. + +Currently, only the `CONDA_OVERRIDE_CUDA` environment variable can be specified, which allows setting the CUDA version for building packages with GPU support. +Learn more in the [conda documentation][conda-docs-override-packages] +::: + +You can set environment variables in the YAML editor with the following syntax: + +```yaml +variables: + CONDA_OVERRIDE_CUDA: '12.0' +``` + ## Trigger environment creation Once the name, description, required packages, and channels are specified, click on the "Create" button at the bottom of the screen to trigger environment creation: @@ -100,3 +116,4 @@ The "Status" will change to "Status: Completed in ... min" once the environment [pypi]: https://pypi.org [pip]: https://pip.pypa.io/en/stable/installation/ +[conda-docs-override-packages]: https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-virtual.html#overriding-detected-packages From 6e99219c87a35edf189eb5f79e842b00bcce9727 Mon Sep 17 00:00:00 2001 From: Pavithra Eswaramoorthy Date: Tue, 12 Mar 2024 18:16:48 +0530 Subject: [PATCH 58/60] REL - 2024.3.1 (#778) --- conda-store-server/conda_store_server/__init__.py | 2 +- conda-store-server/hatch_build.py | 2 +- conda-store/conda_store/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/conda-store-server/conda_store_server/__init__.py b/conda-store-server/conda_store_server/__init__.py index 59b68eb7d..5737a0e60 100644 --- a/conda-store-server/conda_store_server/__init__.py +++ b/conda-store-server/conda_store_server/__init__.py @@ -2,7 +2,7 @@ import typing from pathlib import Path -__version__ = "2024.1.1" +__version__ = "2024.3.1" CONDA_STORE_DIR = Path.home() / ".conda-store" diff --git a/conda-store-server/hatch_build.py b/conda-store-server/hatch_build.py index 48254bbfe..b4e55c75d 100644 --- a/conda-store-server/hatch_build.py +++ b/conda-store-server/hatch_build.py @@ -8,7 +8,7 @@ from hatchling.builders.hooks.plugin.interface import BuildHookInterface -CONDA_STORE_UI_VERSION = "2024.1.1" +CONDA_STORE_UI_VERSION = "2024.3.1" CONDA_STORE_UI_URL = f"https://registry.npmjs.org/@conda-store/conda-store-ui/-/conda-store-ui-{CONDA_STORE_UI_VERSION}.tgz" CONDA_STORE_UI_FILES = [ "main.js", diff --git a/conda-store/conda_store/__init__.py b/conda-store/conda_store/__init__.py index 754adf609..1e9d9afbc 100644 --- a/conda-store/conda_store/__init__.py +++ b/conda-store/conda_store/__init__.py @@ -1 +1 @@ -__version__ = "2024.1.1" +__version__ = "2024.3.1" From 72834c80de8ef6d4993fe7e2271e88d0d3d81d9c Mon Sep 17 00:00:00 2001 From: Pavithra Eswaramoorthy Date: Wed, 13 Mar 2024 00:33:58 +0530 Subject: [PATCH 59/60] Add CHANGELOG entries for v2024.3.1 (#779) Signed-off-by: Pavithra Eswaramoorthy --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f088411e1..e75f2921d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,35 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The project changed to `CalVer` in September 2023. +## [2024.3.1] - 2024-03-12 + +([full changelog](https://github.com/conda-incubator/conda-store/compare/2024.1.1...2024.3.1)) + +## Added + +* Add upstream contribution policy by @pavithraes in https://github.com/conda-incubator/conda-store/pull/722 +* Pass `CONDA_OVERRIDE_CUDA` to `with_cuda` of conda-lock by @nkaretnikov in https://github.com/conda-incubator/conda-store/pull/721 +* Add backwards compatibility policy by @dcmcand in https://github.com/conda-incubator/conda-store/pull/687 +* add how to test section to PR template by @dcmcand in https://github.com/conda-incubator/conda-store/pull/743 +* Add extended-length prefix support by @nkaretnikov in https://github.com/conda-incubator/conda-store/pull/713 +* Generate `constructor` artifacts by @nkaretnikov in https://github.com/conda-incubator/conda-store/pull/714 +* Add support for the `editor` role by @nkaretnikov in https://github.com/conda-incubator/conda-store/pull/738 +* Add a test for parallel builds, fix race conditions due to the shared conda cache by @nkaretnikov in https://github.com/conda-incubator/conda-store/pull/745 +* Add user journey test by @dcmcand in https://github.com/conda-incubator/conda-store/pull/760 +* Add status `CANCELED` by @nkaretnikov in https://github.com/conda-incubator/conda-store/pull/747 +* [DOC] Document setting environment variable by @pavithraes in https://github.com/conda-incubator/conda-store/pull/765 + +## Fixed + +* Log address and port, show exception trace from `uvicorn.run` by @nkaretnikov in https://github.com/conda-incubator/conda-store/pull/708 +* Check if worker is initialized by @nkaretnikov in https://github.com/conda-incubator/conda-store/pull/705 + +## Contributors to this release + +([GitHub contributors page for this release](https://github.com/conda-incubator/conda-store/graphs/contributors?from=2024-01-30&to=2024-03-12&type=c)) + +[@nkaretnikov](https://github.com/search?q=repo%3Aconda-incubator%2Fconda-store+involves%3Ankaretnikov+updated%3A2024-01-30..2024-03-12&type=Issues) | [@dcmcand](https://github.com/search?q=repo%3Aconda-incubator%2Fconda-store+involves%3Adcmcand+updated%3A2024-01-30..2024-03-12&type=Issues) | [@pavithraes](https://github.com/search?q=repo%3Aconda-incubator%2Fconda-store+involves%3Apavithraes+updated%3A2024-01-30..2024-03-12&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Aconda-incubator%2Fconda-store+involves%3Adependabot+updated%3A2024-01-30..2024-03-12&type=Issues)| [@trallard](https://github.com/search?q=repo%3Aconda-incubator%2Fconda-store+involves%3Atrallard+updated%3A2024-01-30..2024-03-12&type=Issues) + ## [2024.1.1] - 2024-01-30 ([full changelog](https://github.com/conda-incubator/conda-store/compare/2023.10.1...ec606641f6d0bb7bde39b2e9f11cf515077feee8)) From d345670c713e476d559fc6ce4c5f1cca63b4f421 Mon Sep 17 00:00:00 2001 From: Pavithra Eswaramoorthy Date: Thu, 14 Mar 2024 22:48:26 +0530 Subject: [PATCH 60/60] [DOC] Add JupyterLab Extension docs page (#752) Co-authored-by: gabalafou --- docusaurus-docs/docusaurus.config.js | 5 ++ .../images/conda-store-menu-item.png | Bin 0 -> 72840 bytes .../install-extension.md | 6 -- .../jupyterlab-conda-store/introduction.md | 7 -- .../jupyterlab-conda-store/introduction.mdx | 73 ++++++++++++++++++ 5 files changed, 78 insertions(+), 13 deletions(-) create mode 100644 docusaurus-docs/jupyterlab-conda-store/images/conda-store-menu-item.png delete mode 100644 docusaurus-docs/jupyterlab-conda-store/install-extension.md delete mode 100644 docusaurus-docs/jupyterlab-conda-store/introduction.md create mode 100644 docusaurus-docs/jupyterlab-conda-store/introduction.mdx diff --git a/docusaurus-docs/docusaurus.config.js b/docusaurus-docs/docusaurus.config.js index e1e8cace0..00800a4e3 100644 --- a/docusaurus-docs/docusaurus.config.js +++ b/docusaurus-docs/docusaurus.config.js @@ -183,6 +183,11 @@ const config = { '⚠️ We are in the process of revamping our docs, some pages may be incomplete or inaccurate. ⚠️', isCloseable: false, }, + docs: { + sidebar: { + hideable: true, + }, + }, }), }; diff --git a/docusaurus-docs/jupyterlab-conda-store/images/conda-store-menu-item.png b/docusaurus-docs/jupyterlab-conda-store/images/conda-store-menu-item.png new file mode 100644 index 0000000000000000000000000000000000000000..4ce4c05aa012e07fa1018f7c7859c4975874c060 GIT binary patch literal 72840 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 literal 0 HcmV?d00001 diff --git a/docusaurus-docs/jupyterlab-conda-store/install-extension.md b/docusaurus-docs/jupyterlab-conda-store/install-extension.md deleted file mode 100644 index 06db94012..000000000 --- a/docusaurus-docs/jupyterlab-conda-store/install-extension.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -sidebar_position: 2 -description: Install jupyterlab-conda-store ---- - -# Install JupyterLab extension diff --git a/docusaurus-docs/jupyterlab-conda-store/introduction.md b/docusaurus-docs/jupyterlab-conda-store/introduction.md deleted file mode 100644 index 709c4f44a..000000000 --- a/docusaurus-docs/jupyterlab-conda-store/introduction.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -sidebar_position: 1 -title: Introduction -description: Introduction to JupyterLab Extension. ---- - -# conda-store JupyterLab extension diff --git a/docusaurus-docs/jupyterlab-conda-store/introduction.mdx b/docusaurus-docs/jupyterlab-conda-store/introduction.mdx new file mode 100644 index 000000000..bcbb26cd2 --- /dev/null +++ b/docusaurus-docs/jupyterlab-conda-store/introduction.mdx @@ -0,0 +1,73 @@ +--- +title: Introduction +description: Introduction to JupyterLab Extension. +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# conda-store JupyterLab extension + +An extension to use the [conda-store UI][conda-store-ui] - a React-based frontend for conda-store, within JupyterLab. + +## Install 📦 + +1. Pre-requisites: `conda-store-server`, JupyterLab >= 3.0 and <= 4.0, and Python >= 3.8 installed. + +2. Install the extension: + + + + + +```bash +conda install -c conda-forge jupyter-lab-conda-store +``` + + + + + +```bash +pip install jupyterlab-conda-store +``` + + + + + +3. Start JupyterLab: + +```bash +jupyter lab +``` + +4. (Optional) Uninstall the extension + + + + +```bash +conda uninstall jupyter-lab-conda-store +``` + + + +```bash +pip uninstall jupyterlab-conda-store +``` + + + +## Usage + +In the JupyterLab window, click on the `conda-store` menu bar item to open the UI in a new window within JupyterLab: + +![JupyterLab window's menu bar with `conda-store` at the end of the list containing the `Conda Store Package Manager` option](./images/conda-store-menu-item.png) + +Learn to use the interface with [conda-store UI tutorials][cs-ui-tutorials]. + + + +[conda-store-ui]: /conda-store-ui/introduction +[cs-ui-tutorials]: /conda-store-ui/tutorials