Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADCM-6278: Prepare test examples of a public API #76

Merged
merged 8 commits into from
Jan 23, 2025
125 changes: 123 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,123 @@
# adcm-aio-client
ADCM Client
# ADCM AIO Client

Asynchronous Client for ADCM (Arenadata Cluster Manager).

> The client supports the minimum version of ADCM `2.5.0`.

## Introduction

Install `adcm-aio-client` using `pip`.

> `adcm-aio-client` requires Python 3.12+.

```shell
pip install adcm-aio-client
```

## QuickStarts

To work with clusters, you need to connect to the backend of an existing ADCM.

First, set up your credentials:

```python
from adcm_aio_client import ADCMSession, Credentials

credentials = Credentials(username="admin", password="admin")
```

Second, you need to get session with ADCM backend:

```python
async with ADCMSession(url="http://127.0.0.1:8000", credentials=credentials) as client:
clusters = await client.clusters.all()
```

The full list of available APIs can be found in the [Guides](#guides) section.

## Guides

- Examples of the available API for the `Bundle` entity can be found [here](/tests/integration/examples/test_bundle.py)
- Examples of the available API for the `Cluster` entity can be
found [here](/tests/integration/examples/test_cluster.py)
- Examples of the available API for the `Service` entity can be
found [here](/tests/integration/examples/test_service.py)
- Examples of the available API for the `Hostprovider` entity can be
found [here](/tests/integration/examples/test_hostprovider.py)
- Examples of the available API for the `Host` entity can be found [here](/tests/integration/examples/test_host.py)
- Examples of the available API for the `Host Group` entity can be
found [here](/tests/integration/examples/test_host_groups.py)

## Contributing

### Development

To start developing ADCM AIO Client create a fork of
the [ADCM AIO Client repository](https://github.com/arenadata/adcm-aio-client) on GitHub.

Then clone your fork with the following command replacing YOUR-USERNAME with your GitHub username:

```shell
git clone https://github.com/<YOUR-USERNAME>/adcm-aio-client.git
```

We use [Poetry](https://python-poetry.org/) to automate testing and linting. You
need [installed](https://python-poetry.org/docs/#installation) Poetry version at least 2.0.0.

You can now install the project and its dependencies using:

```shell
poetry install
```

### Linting

The project uses the [ruff](https://github.com/astral-sh/ruff) formatter to preserve the source code
style. [Pyright](https://github.com/microsoft/pyright) is used for static type checking.

To install the dependencies, run:

```shell
poetry install --with dev
```

To check the code style, run:

```shell
poetry run ruff check
```

To check the types, run:

```shell
poetry run pyright
```

To run the code auto-formatting:

```shell
poetry run ruff format
poetry run ruff check --fix
```

### Testing

For testing, we use [pytest](https://docs.pytest.org/en/stable/index.html).

To install the dependencies, run:

```shell
poetry install --with test
```

To run the unit tests:

```shell
poetry run pytest tests/unit
```

To run the integration tests:

```shell
poetry run pytest/integration
```
435 changes: 219 additions & 216 deletions poetry.lock

Large diffs are not rendered by default.

43 changes: 33 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,39 @@
[tool.poetry]
[project]
name = "adcm-aio-client"
version = "0.1.0"
description = "ADCM Client"
authors = ["Aleksandr Alferov <[email protected]>"]
license = "Apache License Version 2.0"
description = "Asynchronous HTTP Client for ADCM (Arenadata Cluster Manager)"
authors = [
{ name = "Aleksandr Alferov", email = "<[email protected]>" },
{ name = "Egor Araslanov", email = "<[email protected]>" },
{ name = "Daniil Skrynnik", email = "<[email protected]>" },
{ name = "Artem Starovoitov", email = "<[email protected]>" },
{ name = "Vasiliy Chudasov", email = "<[email protected]>" }
]
license = "Apache-2.0"
license-files = ["LICENSE"]
readme = "README.md"
requires-python = ">=3.12"
keywords = ["ADCM"]
dependencies = [
"httpx (>=0.27.2, <1.0.0)",
"asyncstdlib (>=3.13.0, <4.0.0)",
"adcm-version (>=1.0.3, <2.0.0)"
]
dynamic = ["version", "classifiers"]

[project.urls]
homepage = "https://github.com/arenadata/adcm-aio-client"
repository = "https://github.com/arenadata/adcm-aio-client"

[tool.poetry]
version = "0.1.0"
classifiers = [
"Development Status :: 3 - Alpha",
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]

[tool.poetry.dependencies]
python = "^3.12"
httpx = "^0.27.2"
asyncstdlib = "^3.13.0"
adcm-version = "^1.0.3"
python = ">=3.12,<4.0"

[tool.poetry.group.dev]
optional = true
Expand Down Expand Up @@ -77,7 +100,7 @@ include = [

executionEnvironments = [
{ root = "." },
{ root = "tests", extraPaths = [ "." ] },
{ root = "tests", extraPaths = ["."] },
]

typeCheckingMode = "standard"
Expand Down
6 changes: 6 additions & 0 deletions tests/integration/bundles/complex_cluster/config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
- type: cluster
name: Some Cluster
config_group_customization: yes
version: 1
allow_maintenance_mode: true

Expand Down Expand Up @@ -178,6 +179,11 @@
c2:
actions: *actions

- type: service
name: with_license
version: 1
license: ./license.txt

- type: service
name: complex_config
version: 0.3
Expand Down
5 changes: 5 additions & 0 deletions tests/integration/bundles/complex_cluster_prev/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
version: 0.1
allow_maintenance_mode: true

config:
- name: string_field_prev
type: string
default: "string value"

actions: &actions
success: &success_job
display_name: I will survive
Expand Down
9 changes: 9 additions & 0 deletions tests/integration/bundles/simple_hostprovider/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
name: simple_provider
version: 4

upgrade:
- name: simple_provider
display_name: "simple upgrade"
versions:
min: 1
max: 4
states:
available: any

actions: &actions
success: &job
display_name: I will survive
Expand Down
Empty file.
53 changes: 53 additions & 0 deletions tests/integration/examples/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from collections.abc import AsyncGenerator, Generator

import pytest_asyncio

from adcm_aio_client import ADCMSession, Credentials
from adcm_aio_client.client import ADCMClient
from adcm_aio_client.objects import Bundle, Cluster, Host, HostProvider
from tests.integration.setup_environment import ADCMContainer

REQUEST_KWARGS: dict = {"timeout": 10, "retry_interval": 1, "retry_attempts": 1}
CREDENTIALS = Credentials(username="admin", password="admin") # noqa: S106


@pytest_asyncio.fixture()
def adcm(
adcm: ADCMContainer,
simple_cluster_bundle: Bundle,
complex_cluster_bundle: Bundle,
previous_complex_cluster_bundle: Bundle,
simple_hostprovider_bundle: Bundle,
) -> Generator[ADCMContainer, None, None]:
_ = simple_cluster_bundle, complex_cluster_bundle, previous_complex_cluster_bundle, simple_hostprovider_bundle
yield adcm


@pytest_asyncio.fixture()
async def admin_client(adcm: ADCMContainer) -> AsyncGenerator[ADCMClient, None]:
async with ADCMSession(url=adcm.url, credentials=CREDENTIALS, **REQUEST_KWARGS) as client:
yield client


@pytest_asyncio.fixture()
async def example_cluster(admin_client: ADCMClient) -> AsyncGenerator[Cluster, None]:
cluster_bundle = await admin_client.bundles.get(name__eq="Some Cluster", version__eq="1")
cluster = await admin_client.clusters.create(bundle=cluster_bundle, name="Example cluster")

yield cluster

await cluster.delete()


@pytest_asyncio.fixture()
async def example_hostprovider(admin_client: ADCMClient, simple_hostprovider_bundle: Bundle) -> HostProvider:
return await admin_client.hostproviders.create(bundle=simple_hostprovider_bundle, name="Example hostprovider")


@pytest_asyncio.fixture()
async def three_hosts(admin_client: ADCMClient, example_hostprovider: HostProvider) -> list[Host]:
names = {"host-1", "host-2", "host-3"}
for name in names:
await admin_client.hosts.create(hostprovider=example_hostprovider, name=name)

return await admin_client.hosts.filter(name__in=names)
48 changes: 48 additions & 0 deletions tests/integration/examples/test_bundle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from pathlib import Path

import pytest

from adcm_aio_client import ADCMSession
from tests.integration.bundle import pack_bundle
from tests.integration.conftest import BUNDLES
from tests.integration.examples.conftest import CREDENTIALS, REQUEST_KWARGS
from tests.integration.setup_environment import ADCMContainer

pytestmark = [pytest.mark.asyncio]


@pytest.fixture()
def packed_bundle_with_license(tmp_path: Path) -> Path:
# In regular use cases bundle already will be packed.
# Here we do it in runtime for convenience purposes.
return pack_bundle(from_dir=BUNDLES / "cluster_with_license", to=tmp_path)


async def test_bundle(adcm: ADCMContainer, packed_bundle_with_license: Path) -> None:
"""
Bundle (`client.bundles`) API Examples:
- upload from path
- licence acceptance
- bundle removal
- retrieval with filtering / all bundles
"""
async with ADCMSession(url=adcm.url, credentials=CREDENTIALS, **REQUEST_KWARGS) as client:
all_bundles = await client.bundles.all()
assert len(all_bundles) == 4

# It's important for that `source` here is `pathlib.Path`,
# otherwise it'll be treated as URL to upload bundle from.
bundle = await client.bundles.create(source=packed_bundle_with_license, accept_license=False)

# We could have passed `accept_license=True` instead of this.
license_ = await bundle.license
assert license_.state == "unaccepted"
await license_.accept()

same_bundle, *_ = await client.bundles.filter(name__eq="cluster_with_license")
# Objects can be compared on equality,
# but they aren't cached in any way, so it's different instances.
assert bundle == same_bundle
assert bundle is not same_bundle

await same_bundle.delete()
Loading