Skip to content

Commit

Permalink
ADCM-6133: Implement node for getting hosts (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
DanBalalan authored and a-alferov committed Dec 9, 2024
1 parent 32ab331 commit 3ecf9cc
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 27 deletions.
10 changes: 5 additions & 5 deletions adcm_aio_client/core/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from functools import cached_property
from typing import Self

from adcm_aio_client.core.objects.cm import ADCM, ClustersNode, HostProvidersNode, HostsNode
from adcm_aio_client.core.objects.cm import ADCM, ClustersNode, HostProvidersNode, HostsAccessor
from adcm_aio_client.core.requesters import Requester
from adcm_aio_client.core.types import AuthToken, Cert, Credentials, Verify

Expand All @@ -24,15 +24,15 @@ def __init__(self: Self, requester: Requester) -> None:

@cached_property
def clusters(self: Self) -> ClustersNode:
return ClustersNode(path=(), requester=self._requester)
return ClustersNode(path=("clusters",), requester=self._requester)

@cached_property
def hosts(self: Self) -> HostsNode:
return HostsNode(path=(), requester=self._requester)
def hosts(self: Self) -> HostsAccessor:
return HostsAccessor(path=("hosts",), requester=self._requester)

@cached_property
def hostproviders(self: Self) -> HostProvidersNode:
return HostProvidersNode(path=(), requester=self._requester)
return HostProvidersNode(path=("hostproviders",), requester=self._requester)

@cached_property
def adcm(self: Self) -> ADCM:
Expand Down
4 changes: 4 additions & 0 deletions adcm_aio_client/core/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,7 @@ class MultipleObjectsReturnedError(AccessorError):

class ObjectDoesNotExistError(AccessorError):
pass


class OperationError(AccessorError):
pass
20 changes: 14 additions & 6 deletions adcm_aio_client/core/objects/_accessors.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@
from adcm_aio_client.core.objects._base import InteractiveChildObject, InteractiveObject
from adcm_aio_client.core.types import Endpoint, QueryParameters, Requester, RequesterResponse

# filter for narrowing response objects
type AccessorFilter = QueryParameters | None


class Accessor[ReturnObject: InteractiveObject, Filter](ABC):
class_type: type[ReturnObject]

def __init__(self: Self, path: Endpoint, requester: Requester) -> None:
def __init__(self: Self, path: Endpoint, requester: Requester, accessor_filter: AccessorFilter = None) -> None:
self._path = path
self._requester = requester
self._accessor_filter = accessor_filter or {}

@abstractmethod
async def iter(self: Self) -> AsyncGenerator[ReturnObject, None]: ...
Expand Down Expand Up @@ -62,7 +66,7 @@ async def list(self: Self) -> list[ReturnObject]:
return [self._create_object(obj) for obj in results]

async def _request_endpoint(self: Self, query: QueryParameters) -> RequesterResponse:
return await self._requester.get(*self._path, query=query)
return await self._requester.get(*self._path, query={**query, **self._accessor_filter})

def _create_object(self: Self, data: dict[str, Any]) -> ReturnObject:
return self.class_type(requester=self._requester, data=data)
Expand All @@ -88,17 +92,21 @@ def _extract_results_from_response(self: Self, response: RequesterResponse) -> l


class PaginatedChildAccessor[Parent, Child: InteractiveChildObject, Filter](PaginatedAccessor[Child, Filter]):
def __init__(self: Self, parent: Parent, path: Endpoint, requester: Requester) -> None:
super().__init__(path, requester)
def __init__(
self: Self, parent: Parent, path: Endpoint, requester: Requester, accessor_filter: AccessorFilter = None
) -> None:
super().__init__(path, requester, accessor_filter)
self._parent = parent

def _create_object(self: Self, data: dict[str, Any]) -> Child:
return self.class_type(parent=self._parent, requester=self._requester, data=data)


class NonPaginatedChildAccessor[Parent, Child: InteractiveChildObject, Filter](Accessor[Child, Filter]):
def __init__(self: Self, parent: Parent, path: Endpoint, requester: Requester) -> None:
super().__init__(path, requester)
def __init__(
self: Self, parent: Parent, path: Endpoint, requester: Requester, accessor_filter: AccessorFilter = None
) -> None:
super().__init__(path, requester, accessor_filter)
self._parent = parent

async def iter(self: Self) -> AsyncGenerator[Child, None]:
Expand Down
60 changes: 52 additions & 8 deletions adcm_aio_client/core/objects/cm.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from functools import cached_property
from typing import Self
from typing import Iterable, Self
import asyncio

from adcm_aio_client.core.errors import NotFoundError
from adcm_aio_client.core.errors import NotFoundError, OperationError, ResponseError
from adcm_aio_client.core.objects._accessors import (
PaginatedAccessor,
PaginatedChildAccessor,
Expand All @@ -20,6 +21,8 @@
from adcm_aio_client.core.objects._mapping import ClusterMapping
from adcm_aio_client.core.types import ADCMEntityStatus, Endpoint

type Filter = object # TODO: implement


class ADCM(InteractiveObject, WithActions, WithConfig):
@property
Expand Down Expand Up @@ -177,11 +180,11 @@ def cluster(self: Self) -> Cluster:
return self.service.cluster

@cached_property
def hosts(self: Self) -> "HostsInClusterNode":
return HostsInClusterNode( # TODO: new ComponentHostsNode
def hosts(self: Self) -> "HostsAccessor":
return HostsAccessor(
path=(*self.cluster.get_own_path(), "hosts"),
requester=self._requester,
# filter=Filter({"componentId": self.id}),
accessor_filter={"componentId": self.id},
)

def get_own_path(self: Self) -> Endpoint:
Expand All @@ -208,6 +211,12 @@ def description(self: Self) -> str:
def display_name(self: Self) -> str:
return str(self._data["prototype"]["displayName"])

@cached_property
def hosts(self: Self) -> "HostsAccessor":
return HostsAccessor(
path=("hosts",), requester=self._requester, accessor_filter={"hostproviderName": self.name}
)

def get_own_path(self: Self) -> Endpoint:
return self.PATH_PREFIX, self.id

Expand Down Expand Up @@ -244,10 +253,45 @@ async def hostprovider(self: Self) -> HostProvider:
def get_own_path(self: Self) -> Endpoint:
return self.PATH_PREFIX, self.id

def __str__(self: Self) -> str:
return f"<{self.__class__.__name__} #{self.id} {self.name}>"


class HostsNode(PaginatedAccessor[Host, None]):
class HostsAccessor(PaginatedAccessor[Host, dict | None]):
class_type = Host


class HostsInClusterNode(PaginatedAccessor[Host, None]):
class_type = Host
class HostsInClusterNode(HostsAccessor):
async def add(self: Self, host: Host | Iterable[Host] | None = None, filters: Filter | None = None) -> None:
hosts = await self._get_hosts_from_arg_or_filter(host=host, filters=filters)

await self._requester.post(*self._path, data=[{"hostId": host.id} for host in hosts])

async def remove(self: Self, host: Host | Iterable[Host] | None = None, filters: Filter | None = None) -> None:
hosts = await self._get_hosts_from_arg_or_filter(host=host, filters=filters)

results = await asyncio.gather(
*(self._requester.delete(*self._path, host_.id) for host_ in hosts), return_exceptions=True
)

errors = set()
for host_, result in zip(hosts, results):
if isinstance(result, ResponseError):
errors.add(str(host_))

if errors:
errors = ", ".join(errors)
raise OperationError(f"Some hosts can't be deleted from cluster: {errors}")

async def _get_hosts_from_arg_or_filter(
self: Self, host: Host | Iterable[Host] | None = None, filters: Filter | None = None
) -> Iterable[Host]:
if all((host, filters)):
raise ValueError("`host` and `filters` arguments are mutually exclusive.")

if host:
hosts = [host] if isinstance(host, Host) else host
else:
hosts = await self.filter(filters) # type: ignore # TODO

return hosts
8 changes: 4 additions & 4 deletions adcm_aio_client/core/requesters.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,11 @@ async def login(self: Self, credentials: Credentials) -> Self:
async def get(self: Self, *path: PathPart, query: QueryParameters | None = None) -> HTTPXRequesterResponse:
return await self.request(*path, method=self.client.get, params=query or {})

async def post(self: Self, *path: PathPart, data: dict) -> HTTPXRequesterResponse:
return await self.request(*path, method=self.client.post, data=data)
async def post(self: Self, *path: PathPart, data: dict | list) -> HTTPXRequesterResponse:
return await self.request(*path, method=self.client.post, json=data)

async def patch(self: Self, *path: PathPart, data: dict) -> HTTPXRequesterResponse:
return await self.request(*path, method=self.client.patch, data=data)
async def patch(self: Self, *path: PathPart, data: dict | list) -> HTTPXRequesterResponse:
return await self.request(*path, method=self.client.patch, json=data)

async def delete(self: Self, *path: PathPart) -> HTTPXRequesterResponse:
return await self.request(*path, method=self.client.delete)
Expand Down
4 changes: 2 additions & 2 deletions adcm_aio_client/core/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ async def login(self: Self, credentials: Credentials) -> Self: ...

async def get(self: Self, *path: PathPart, query: QueryParameters | None = None) -> RequesterResponse: ...

async def post(self: Self, *path: PathPart, data: dict) -> RequesterResponse: ...
async def post(self: Self, *path: PathPart, data: dict | list) -> RequesterResponse: ...

async def patch(self: Self, *path: PathPart, data: dict) -> RequesterResponse: ...
async def patch(self: Self, *path: PathPart, data: dict | list) -> RequesterResponse: ...

async def delete(self: Self, *path: PathPart) -> RequesterResponse: ...

Expand Down
4 changes: 2 additions & 2 deletions tests/unit/mocks/requesters.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ async def get(self: Self, *path: PathPart, query: QueryParameters | None = None)
_ = path, query
return self._return_next_response()

async def post(self: Self, *path: PathPart, data: dict) -> RequesterResponse:
async def post(self: Self, *path: PathPart, data: dict | list) -> RequesterResponse:
_ = path, data
return self._return_next_response()

async def patch(self: Self, *path: PathPart, data: dict) -> RequesterResponse:
async def patch(self: Self, *path: PathPart, data: dict | list) -> RequesterResponse:
_ = path, data
return self._return_next_response()

Expand Down

0 comments on commit 3ecf9cc

Please sign in to comment.