From f8465e5b99a148a977e5d0cfc9fbbbe734f97d04 Mon Sep 17 00:00:00 2001 From: Ilyas Niyazov Date: Mon, 10 Mar 2025 13:46:17 +0300 Subject: [PATCH] Added create container grpc method Signed-off-by: Ilyas Niyazov --- frostfs_api/client/frostfs_client.py | 23 ------- frostfs_api/client/services/container.py | 20 ------ {frostfs_api => frostfs_sdk}/__init__.py | 0 frostfs_sdk/client/frostfs_client.py | 30 +++++++++ .../client/models/client_environment.py | 8 +++ frostfs_sdk/client/models/client_settings.py | 19 ++++++ frostfs_sdk/client/models/ecdsa.py | 8 +++ .../client/parameters/container_create.py | 18 +++++ frostfs_sdk/client/parameters/wait.py | 21 ++++++ frostfs_sdk/client/services/container.py | 32 +++++++++ .../cryptography/__init__.py | 0 .../cryptography/key_extension.py | 0 .../cryptography/signer.py | 6 +- .../cryptography/verifier.py | 0 frostfs_sdk/models/dto/container.py | 25 +++++++ frostfs_sdk/models/dto/filter.py | 14 ++++ frostfs_sdk/models/dto/placement_policy.py | 9 +++ frostfs_sdk/models/dto/replica.py | 12 ++++ frostfs_sdk/models/dto/selector.py | 15 +++++ frostfs_sdk/models/dto/session_token.py | 5 ++ frostfs_sdk/models/enums/basic_acl.py | 7 ++ frostfs_sdk/models/enums/filter_operation.py | 42 ++++++++++++ frostfs_sdk/models/enums/selector_clause.py | 26 ++++++++ .../models/mappers/container_mapper.py | 65 +++++++++++++++++++ frostfs_sdk/models/mappers/filter_mapper.py | 60 +++++++++++++++++ frostfs_sdk/models/mappers/owner_id_mapper.py | 0 .../models/mappers/placement_policy_mapper.py | 50 ++++++++++++++ frostfs_sdk/models/mappers/replica_mapper.py | 0 frostfs_sdk/models/mappers/selector_mapper.py | 0 generate_proto.sh | 4 -- tests/client/test_create_container.py | 14 ++++ tests/conftest.py | 18 ++++- tests/helpers/models.py | 12 +++- tests/helpers/params_container.py | 22 +++++++ 34 files changed, 532 insertions(+), 53 deletions(-) delete mode 100644 frostfs_api/client/frostfs_client.py delete mode 100644 frostfs_api/client/services/container.py rename {frostfs_api => frostfs_sdk}/__init__.py (100%) create mode 100644 frostfs_sdk/client/frostfs_client.py create mode 100644 frostfs_sdk/client/models/client_environment.py create mode 100644 frostfs_sdk/client/models/client_settings.py create mode 100644 frostfs_sdk/client/models/ecdsa.py create mode 100644 frostfs_sdk/client/parameters/container_create.py create mode 100644 frostfs_sdk/client/parameters/wait.py create mode 100644 frostfs_sdk/client/services/container.py rename {frostfs_api => frostfs_sdk}/cryptography/__init__.py (100%) rename {frostfs_api => frostfs_sdk}/cryptography/key_extension.py (100%) rename {frostfs_api => frostfs_sdk}/cryptography/signer.py (82%) rename {frostfs_api => frostfs_sdk}/cryptography/verifier.py (100%) create mode 100644 frostfs_sdk/models/dto/container.py create mode 100644 frostfs_sdk/models/dto/filter.py create mode 100644 frostfs_sdk/models/dto/placement_policy.py create mode 100644 frostfs_sdk/models/dto/replica.py create mode 100644 frostfs_sdk/models/dto/selector.py create mode 100644 frostfs_sdk/models/dto/session_token.py create mode 100644 frostfs_sdk/models/enums/basic_acl.py create mode 100644 frostfs_sdk/models/enums/filter_operation.py create mode 100644 frostfs_sdk/models/enums/selector_clause.py create mode 100644 frostfs_sdk/models/mappers/container_mapper.py create mode 100644 frostfs_sdk/models/mappers/filter_mapper.py create mode 100644 frostfs_sdk/models/mappers/owner_id_mapper.py create mode 100644 frostfs_sdk/models/mappers/placement_policy_mapper.py create mode 100644 frostfs_sdk/models/mappers/replica_mapper.py create mode 100644 frostfs_sdk/models/mappers/selector_mapper.py create mode 100644 tests/client/test_create_container.py create mode 100644 tests/helpers/params_container.py diff --git a/frostfs_api/client/frostfs_client.py b/frostfs_api/client/frostfs_client.py deleted file mode 100644 index 655f218..0000000 --- a/frostfs_api/client/frostfs_client.py +++ /dev/null @@ -1,23 +0,0 @@ -# Create channel and Stubs -import grpc -from frostfs_api.client.services.container import ContainerClient - - -class FrostfsClient: - def __init__(self, address): - self.channel = grpc.insecure_channel(address) - self.container = ContainerClient(self.channel) - - def close(self): - self.channel.close() - - -if __name__ == "__main__": - client = FrostfsClient("localhost:50051") - create_response = client.container.create_container("my_container") - print("Container created:", create_response) - - get_response = client.container.get_container(create_response) - print("Container details:", get_response) - - client.close() diff --git a/frostfs_api/client/services/container.py b/frostfs_api/client/services/container.py deleted file mode 100644 index b93a336..0000000 --- a/frostfs_api/client/services/container.py +++ /dev/null @@ -1,20 +0,0 @@ -# implementation Conainer methods -import grpc -import protos.models.container.service_pb2_grpc as service_pb2_grpc_container -import protos.models.container.service_pb2 as service_pb2_container - - - -class ContainerClient: - def __init__(self, channel): - self.container_stub = service_pb2_grpc_container.ContainerServiceStub(channel) - - def create_container(self, container_name) -> bytes: - request = service_pb2_container.PutRequest(name=container_name) - response: service_pb2_container.PutResponse = self.container_stub.Put(request) - return response.body.container_id - - def get_container(self, container_id) -> service_pb2_container.GetResponse: - request = service_pb2_container.GetRequest(id=container_id) - response: service_pb2_container.GetResponse = self.container_stub.Get(request) - return response diff --git a/frostfs_api/__init__.py b/frostfs_sdk/__init__.py similarity index 100% rename from frostfs_api/__init__.py rename to frostfs_sdk/__init__.py diff --git a/frostfs_sdk/client/frostfs_client.py b/frostfs_sdk/client/frostfs_client.py new file mode 100644 index 0000000..02f79f4 --- /dev/null +++ b/frostfs_sdk/client/frostfs_client.py @@ -0,0 +1,30 @@ +# Create channel and Stubs +import grpc +from frostfs_sdk.client.models.client_environment import ClientEnvironment +from frostfs_sdk.client.models.client_settings import ClientSettings +from frostfs_sdk.client.models.ecdsa import ECDSA +from frostfs_sdk.client.services.container import ContainerClient + + +class FrostfsClient: + def __init__(self, client_settings: ClientSettings): + self.channel = grpc.insecure_channel(f"{client_settings.host}:{client_settings.port}") + self.ecdsa: ECDSA = ECDSA(wif=client_settings.wif) + + client_environment = ClientEnvironment(self.ecdsa, self.channel) + self.container = ContainerClient(client_environment) + + def close(self): + self.channel.close() + + + +""" +import frostfs_sdk + +WIF = "L5XNVUzPnma6m4mPrWEN6CcTscJERcfX3yvb1cdffdxe1iriAshU" +address = "10.78.128.25:8080" +client = frostfs_sdk.FrostfsClient(ClientSettings(WIF, address)) +params = frostfs_sdk.models.PrmsCreateContainer(name="1234") +client.container.create(params) +""" diff --git a/frostfs_sdk/client/models/client_environment.py b/frostfs_sdk/client/models/client_environment.py new file mode 100644 index 0000000..8d6d33a --- /dev/null +++ b/frostfs_sdk/client/models/client_environment.py @@ -0,0 +1,8 @@ +import grpc +from frostfs_sdk.client.models.ecdsa import ECDSA + + +class ClientEnvironment: + def __init__(self, ecdsa: ECDSA, channel: grpc.Channel): + self.ecdsa = ecdsa + self.channel = channel diff --git a/frostfs_sdk/client/models/client_settings.py b/frostfs_sdk/client/models/client_settings.py new file mode 100644 index 0000000..b7bd1df --- /dev/null +++ b/frostfs_sdk/client/models/client_settings.py @@ -0,0 +1,19 @@ +class ClientSettings: + def __init__(self, wif: str = None, address: str = None): + """ + Initializes client settings with validation. + + Args: + wif: Wallet import format string + address: FrostFS node host address + """ + self.wif = wif + self.address = address + + # Perform validation after initialization + self.validate() + + def validate(self): + """Performs runtime validation of the settings""" + if not (self.address and self.wif): + raise ValueError("The value must be specified ADDRESS and WIF") diff --git a/frostfs_sdk/client/models/ecdsa.py b/frostfs_sdk/client/models/ecdsa.py new file mode 100644 index 0000000..c04d116 --- /dev/null +++ b/frostfs_sdk/client/models/ecdsa.py @@ -0,0 +1,8 @@ +from frostfs_sdk.cryptography.key_extension import KeyExtension + + +class ECDSA: + def __init__(self, wif: str): + self.wif = wif + self.private_key: bytes = KeyExtension().get_private_key_from_wif(wif) + self.public_key: bytes = KeyExtension().get_public_key(self.private_key) diff --git a/frostfs_sdk/client/parameters/container_create.py b/frostfs_sdk/client/parameters/container_create.py new file mode 100644 index 0000000..827c5bd --- /dev/null +++ b/frostfs_sdk/client/parameters/container_create.py @@ -0,0 +1,18 @@ +from dataclasses import dataclass, field +from typing import Optional, Dict + +from frostfs_sdk.models.dto.container import Container +from frostfs_sdk.models.dto.session_token import SessionToken +from frostfs_sdk.client.parameters.wait import PrmWait + + +@dataclass(frozen=True) +class PrmContainerCreate: + container: Container + wait_params: Optional[PrmWait] = None + session_token: Optional[SessionToken] = None + x_headers: Dict[str, str] = field(default_factory=dict) + + def __post_init__(self): + if self.wait_params is None: + object.__setattr__(self, 'wait_params', PrmWait()) diff --git a/frostfs_sdk/client/parameters/wait.py b/frostfs_sdk/client/parameters/wait.py new file mode 100644 index 0000000..a77e561 --- /dev/null +++ b/frostfs_sdk/client/parameters/wait.py @@ -0,0 +1,21 @@ +from dataclasses import dataclass, field +from datetime import datetime, timedelta +from typing import Optional + + +@dataclass(frozen=True) +class PrmWait: + DEFAULT_TIMEOUT: timedelta = field(default=timedelta(seconds=120), init=False) + DEFAULT_POLL_INTERVAL: timedelta = field(default=timedelta(seconds=5), init=False) + + timeout: timedelta = DEFAULT_TIMEOUT + poll_interval: timedelta = DEFAULT_POLL_INTERVAL + + def __post_init__(self): + if self.timeout is None: + object.__setattr__(self, 'timeout', self.DEFAULT_TIMEOUT) + if self.poll_interval is None: + object.__setattr__(self, 'poll_interval', self.DEFAULT_POLL_INTERVAL) + + def get_deadline(self) -> datetime: + return datetime.now() + self.timeout diff --git a/frostfs_sdk/client/services/container.py b/frostfs_sdk/client/services/container.py new file mode 100644 index 0000000..694dc9f --- /dev/null +++ b/frostfs_sdk/client/services/container.py @@ -0,0 +1,32 @@ +# implementation Conainer methods +from frostfs_sdk.client.models.client_environment import ClientEnvironment +from frostfs_sdk.cryptography.signer import Signer +from frostfs_sdk.models.dto.container import ContainerId +import protos.models.container.service_pb2_grpc as service_pb2_grpc_container +import protos.models.container.service_pb2 as service_pb2_container + +from frostfs_sdk.client.parameters.container_create import PrmContainerCreate +from frostfs_sdk.models.mappers.container_mapper import ContainerMapper + + + +class ContainerClient: + def __init__(self, client_environment: ClientEnvironment): + self.container_stub = service_pb2_grpc_container.ContainerServiceStub(client_environment.channel) + self.ecdsa = client_environment.ecdsa + + def create(self, prm_container_create: PrmContainerCreate) -> ContainerId: + request = self.create_put_request(prm_container_create) + response: service_pb2_container.PutResponse = self.container_stub.Put(request) + return ContainerId(value=response.body.container_id) + + def create_put_request(self, prm: PrmContainerCreate): + grpc_container=ContainerMapper().to_grpc_message(prm.container) + body = service_pb2_container.PutRequest.Body( + container=grpc_container, + signature=Signer.sign_rfc6979(self.ecdsa.private_key, grpc_container) + ) + + request = service_pb2_container.PutRequest(body=body) + signed_request = Signer.sign(self.ecdsa.private_key, request) + return signed_request diff --git a/frostfs_api/cryptography/__init__.py b/frostfs_sdk/cryptography/__init__.py similarity index 100% rename from frostfs_api/cryptography/__init__.py rename to frostfs_sdk/cryptography/__init__.py diff --git a/frostfs_api/cryptography/key_extension.py b/frostfs_sdk/cryptography/key_extension.py similarity index 100% rename from frostfs_api/cryptography/key_extension.py rename to frostfs_sdk/cryptography/key_extension.py diff --git a/frostfs_api/cryptography/signer.py b/frostfs_sdk/cryptography/signer.py similarity index 82% rename from frostfs_api/cryptography/signer.py rename to frostfs_sdk/cryptography/signer.py index f8c7ef6..81c3df4 100644 --- a/frostfs_api/cryptography/signer.py +++ b/frostfs_sdk/cryptography/signer.py @@ -3,7 +3,8 @@ from hashlib import sha256, sha512 class Signer: - def sign_rfc6979(self, private_key: bytes, message: bytes) -> bytes: + @staticmethod + def sign_rfc6979(private_key: bytes, message: bytes) -> bytes: if len(private_key) == 0 or private_key is None: raise ValueError(f"Incorrect private_key: {private_key}") @@ -13,7 +14,8 @@ class Signer: return signature - def sign(self, private_key: bytes, message: bytes) -> bytes: + @staticmethod + def sign(private_key: bytes, message: bytes) -> bytes: if len(private_key) == 0 or private_key is None: raise ValueError(f"Incorrect private key: {private_key}") diff --git a/frostfs_api/cryptography/verifier.py b/frostfs_sdk/cryptography/verifier.py similarity index 100% rename from frostfs_api/cryptography/verifier.py rename to frostfs_sdk/cryptography/verifier.py diff --git a/frostfs_sdk/models/dto/container.py b/frostfs_sdk/models/dto/container.py new file mode 100644 index 0000000..a21f8d8 --- /dev/null +++ b/frostfs_sdk/models/dto/container.py @@ -0,0 +1,25 @@ +from dataclasses import dataclass, field +from typing import Dict, Optional +import uuid + +from frostfs_sdk.models.enums.basic_acl import BasicAcl +from frostfs_sdk.models.dto.placement_policy import PlacementPolicy + + + +@dataclass +class Container: + basicAcl: BasicAcl + placementPolicy: PlacementPolicy + nonce: uuid.UUID = field(default_factory=uuid.uuid4) + version: Optional[str] = None + attributes: Dict[str, str] = field(default_factory=dict) + + def __init__(self, basicAcl: BasicAcl, placementPolicy: PlacementPolicy): + self.basicAcl = basicAcl + self.placementPolicy = placementPolicy + + +@dataclass +class ContainerId: + value: str diff --git a/frostfs_sdk/models/dto/filter.py b/frostfs_sdk/models/dto/filter.py new file mode 100644 index 0000000..0819648 --- /dev/null +++ b/frostfs_sdk/models/dto/filter.py @@ -0,0 +1,14 @@ +from dataclasses import dataclass + +from frostfs_sdk.models.enums.filter_operation import FilterOperation + + +@dataclass(frozen=True) +class Filter: + """ + Data Transfer Object for Filter configuration + """ + name: str + key: str + operation: FilterOperation + value: str diff --git a/frostfs_sdk/models/dto/placement_policy.py b/frostfs_sdk/models/dto/placement_policy.py new file mode 100644 index 0000000..a0234e0 --- /dev/null +++ b/frostfs_sdk/models/dto/placement_policy.py @@ -0,0 +1,9 @@ +from dataclasses import dataclass +from typing import List + +from frostfs_sdk.models.dto.replica import Replica + +@dataclass +class PlacementPolicy: + replicas: List[Replica] + unique: bool diff --git a/frostfs_sdk/models/dto/replica.py b/frostfs_sdk/models/dto/replica.py new file mode 100644 index 0000000..f3946c0 --- /dev/null +++ b/frostfs_sdk/models/dto/replica.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass, field + + +EMPTY_STRING = "" + +@dataclass +class Replica: + count: int + selector: str = field(default=EMPTY_STRING) + + def __post_init__(self): + self.selector = self.selector if self.selector else EMPTY_STRING diff --git a/frostfs_sdk/models/dto/selector.py b/frostfs_sdk/models/dto/selector.py new file mode 100644 index 0000000..a03f885 --- /dev/null +++ b/frostfs_sdk/models/dto/selector.py @@ -0,0 +1,15 @@ +from dataclasses import dataclass + +from frostfs_sdk.models.enums.selector_clause import SelectorClause + + +@dataclass(frozen=True) +class Selector: + """ + Data Transfer Object for Selector configuration + """ + name: str + count: int + clause: SelectorClause + attribute: str + filter: str diff --git a/frostfs_sdk/models/dto/session_token.py b/frostfs_sdk/models/dto/session_token.py new file mode 100644 index 0000000..3b4aa76 --- /dev/null +++ b/frostfs_sdk/models/dto/session_token.py @@ -0,0 +1,5 @@ +from dataclasses import dataclass + +@dataclass(frozen=True) +class SessionToken: + token: bytes diff --git a/frostfs_sdk/models/enums/basic_acl.py b/frostfs_sdk/models/enums/basic_acl.py new file mode 100644 index 0000000..0058a25 --- /dev/null +++ b/frostfs_sdk/models/enums/basic_acl.py @@ -0,0 +1,7 @@ +from enum import Enum + +class BasicAcl(Enum): + PRIVATE = 0x1C8C8CCC + PUBLIC_RO = 0x1FBF8CFF + PUBLIC_RW = 0x1FBFBFFF + PUBLIC_APPEND = 0x1FBF9FFF diff --git a/frostfs_sdk/models/enums/filter_operation.py b/frostfs_sdk/models/enums/filter_operation.py new file mode 100644 index 0000000..68a1958 --- /dev/null +++ b/frostfs_sdk/models/enums/filter_operation.py @@ -0,0 +1,42 @@ +class FilterOperation: + """ + Enum for filter operations with integer value mapping + """ + OPERATION_UNSPECIFIED = 0 + EQ = 1 + NE = 2 + GT = 3 + GE = 4 + LT = 5 + LE = 6 + OR = 7 + AND = 8 + NOT = 9 + LIKE = 10 + + _value_map = { + 0: OPERATION_UNSPECIFIED, + 1: EQ, + 2: NE, + 3: GT, + 4: GE, + 5: LT, + 6: LE, + 7: OR, + 8: AND, + 9: NOT, + 10: LIKE + } + + @classmethod + def get(cls, value: int) -> 'FilterOperation': + """ + Get enum instance by integer value + + Args: + value: Integer value of the operation + + Returns: + Corresponding FilterOperation instance + """ + return cls._value_map.get(value) \ No newline at end of file diff --git a/frostfs_sdk/models/enums/selector_clause.py b/frostfs_sdk/models/enums/selector_clause.py new file mode 100644 index 0000000..d294887 --- /dev/null +++ b/frostfs_sdk/models/enums/selector_clause.py @@ -0,0 +1,26 @@ +class SelectorClause: + """ + Enum for selector clauses with integer value mapping + """ + CLAUSE_UNSPECIFIED = 0 + SAME = 1 + DISTINCT = 2 + + _value_map = { + 0: CLAUSE_UNSPECIFIED, + 1: SAME, + 2: DISTINCT + } + + @classmethod + def get(cls, value: int) -> 'SelectorClause': + """ + Get enum instance by integer value + + Args: + value: Integer value of the clause + + Returns: + Corresponding SelectorClause instance + """ + return cls._value_map.get(value) diff --git a/frostfs_sdk/models/mappers/container_mapper.py b/frostfs_sdk/models/mappers/container_mapper.py new file mode 100644 index 0000000..637e493 --- /dev/null +++ b/frostfs_sdk/models/mappers/container_mapper.py @@ -0,0 +1,65 @@ +from typing import ByteString, Optional + +from frostfs_sdk.models.mappers.placement_policy_mapper import PlacementPolicyMapper +import protos.models.container.types_pb2 as types_pb2_container +from frostfs_sdk.models.dto.container import Container + + +class ContainerMapper: + @staticmethod + def to_grpc_message(container: Container) -> Optional[types_pb2_container.Container]: + """ + Converts Container DTO to gRPC message + + Args: + container: Container DTO object + + Returns: + gRPC Container message builder + """ + if not container: + return None + + attributes = [ + types_pb2_container.Container.Attribute(key=k, value=v) + for k, v in container.attributes.items() + ] + + grpc_container = types_pb2_container.Container( + # nonce=ByteString.copy(container.nonce), + placement_policy=PlacementPolicyMapper.to_grpc_message(container.placementPolicy), + attributes=attributes + + ) + + # if container.owner_id: + # grpc_container.owner_id = OwnerIdMapper.to_grpc_message(container.owner_id) + + # if container.version: + # grpc_container.version = VersionMapper.to_grpc_message(container.version) + + return grpc_container + + @staticmethod + def to_model(container_grpc: types_pb2_container.Container) -> Optional[Container]: + """ + Converts gRPC message to Container DTO + + Args: + container_grpc: gRPC Container message + + Returns: + Container DTO object + """ + if not container_grpc or container_grpc.ByteSize() == 0: + return None + + attributes = {attr.key: attr.value for attr in container_grpc.attributes} + + return Container( + # nonce=UuidUtils.as_uuid(container_grpc.nonce.to_bytes()), + placement_policy=PlacementPolicyMapper.to_model(container_grpc.placement_policy), + # version=VersionMapper.to_model(container_grpc.version), + # owner_id=OwnerIdMapper.to_model(container_grpc.owner_id), + attributes=attributes + ) diff --git a/frostfs_sdk/models/mappers/filter_mapper.py b/frostfs_sdk/models/mappers/filter_mapper.py new file mode 100644 index 0000000..2848a32 --- /dev/null +++ b/frostfs_sdk/models/mappers/filter_mapper.py @@ -0,0 +1,60 @@ +from typing import List, Optional + +from frostfs_sdk.models.enums.filter_operation import FilterOperation +from frostfs_sdk.models.dto.filter import Filter +import protos.models.netmap.types_pb2 as types_pb2_netmap + + +class FilterMapper: + @staticmethod + def to_grpc_messages(filters: List[Filter]) -> List[types_pb2_netmap.Filter]: + """ + Converts list of Filter DTOs to gRPC messages with nested conversion + """ + if not filters: + return [] + + return [FilterMapper.to_grpc_message(f) for f in filters] + + @staticmethod + def to_grpc_message(filter_dto: Filter) -> types_pb2_netmap.Filter: + """ + Converts Filter DTO to gRPC message with nested filters + """ + + operation = types_pb2_netmap.Filter.Operation.Value(filter_dto.operation.value) + return types_pb2_netmap.Filter( + name=filter_dto.name, + key=filter_dto.key, + op=operation, + value=filter_dto.value, + filters=FilterMapper.to_grpc_messages(filter_dto.filters) + ) + + @staticmethod + def to_models(filters_grpc: List[types_pb2_netmap.Filter]) -> Optional[List[Filter]]: + """ + Converts gRPC messages to Filter DTOs with nested conversion + """ + if not filters_grpc: + return None + + return [FilterMapper.to_model(f) for f in filters_grpc] + + @staticmethod + def to_model(filter_grpc: types_pb2_netmap.Filter) -> Optional[Filter]: + """ + Converts gRPC message to Filter DTO with nested filters + """ + if not filter_grpc or filter_grpc.ByteSize() == 0: + return None + + operation = FilterOperation(filter_grpc.op) + + return Filter( + name=filter_grpc.name, + key=filter_grpc.key, + operation=operation, + value=filter_grpc.value, + filters=FilterMapper.to_models(filter_grpc.filters) + ) diff --git a/frostfs_sdk/models/mappers/owner_id_mapper.py b/frostfs_sdk/models/mappers/owner_id_mapper.py new file mode 100644 index 0000000..e69de29 diff --git a/frostfs_sdk/models/mappers/placement_policy_mapper.py b/frostfs_sdk/models/mappers/placement_policy_mapper.py new file mode 100644 index 0000000..98cfedc --- /dev/null +++ b/frostfs_sdk/models/mappers/placement_policy_mapper.py @@ -0,0 +1,50 @@ +from typing import Optional +from frostfs_sdk.models.mappers.filter_mapper import FilterMapper +import protos.models.netmap.types_pb2 as types_pb2_netmap +from frostfs_sdk.models.dto.placement_policy import PlacementPolicy + + +class PlacementPolicyMapper: + @staticmethod + def to_grpc_message(policy: PlacementPolicy) -> Optional[types_pb2_netmap.PlacementPolicy]: + """ + Converts PlacementPolicy DTO to gRPC message + + Args: + policy: PlacementPolicy DTO object + + Returns: + gRPC PlacementPolicy message + """ + if not policy: + return None + + return types_pb2_netmap.PlacementPolicy( + unique=policy.unique, + container_backup_factor=policy.backup_factor, + filters=FilterMapper.to_grpc_messages(policy.filters), + # selectors=SelectorMapper.to_grpc_messages(policy.selectors), + # replicas=ReplicaMapper.to_grpc_messages(policy.replicas) + ) + + @staticmethod + def to_model(policy_grpc: types_pb2_netmap.PlacementPolicy) -> Optional[PlacementPolicy]: + """ + Converts gRPC message to PlacementPolicy DTO + + Args: + policy_grpc: gRPC PlacementPolicy message + + Returns: + PlacementPolicy DTO object + """ + if not policy_grpc or policy_grpc.ByteSize() == 0: + return None + + return PlacementPolicy( + # replicas=ReplicaMapper.to_models(policy_grpc.replicas), + unique=policy_grpc.unique, + backup_factor=policy_grpc.container_backup_factor, + filters=FilterMapper.to_models(policy_grpc.filters), + # selectors=SelectorMapper.to_models(policy_grpc.selectors) + ) diff --git a/frostfs_sdk/models/mappers/replica_mapper.py b/frostfs_sdk/models/mappers/replica_mapper.py new file mode 100644 index 0000000..e69de29 diff --git a/frostfs_sdk/models/mappers/selector_mapper.py b/frostfs_sdk/models/mappers/selector_mapper.py new file mode 100644 index 0000000..e69de29 diff --git a/generate_proto.sh b/generate_proto.sh index ba594f4..00a4046 100755 --- a/generate_proto.sh +++ b/generate_proto.sh @@ -13,23 +13,19 @@ REPO_FROSTFS_API_PROTOS="https://git.frostfs.info/TrueCloudLab/frostfs-api.git" rm -rf "$PROTOS_DIR" -# Step 1: Create folder ./protos echo "1. Create folder ./protos" mkdir -p "$PROTOS_DIR" -# Step 2: Cloning repository into ./protos/source echo "2. Cloning repository into ./protos/source..." mkdir -p "$SOURCE_DIR" git clone "$REPO_FROSTFS_API_PROTOS" "$SOURCE_DIR" -# Step 3: Generating Python code from .proto files echo "3. Generating Python code from .proto files to $MODELS_DIR" mkdir -p "$MODELS_DIR" find "$SOURCE_DIR" -name "*.proto" | while read -r proto_file; do python -m grpc_tools.protoc -I "$SOURCE_DIR" --python_out="$MODELS_DIR" --pyi_out="$MODELS_DIR" --grpc_python_out="$MODELS_DIR" "$proto_file" done -# Step 4: Making ./protos/models a Python package echo "4. Making ./protos/models a Python package..." touch "$MODELS_DIR/__init__.py" diff --git a/tests/client/test_create_container.py b/tests/client/test_create_container.py new file mode 100644 index 0000000..0395d96 --- /dev/null +++ b/tests/client/test_create_container.py @@ -0,0 +1,14 @@ +# import pytest + +# from frostfs_sdk.client.frostfs_client import FrostfsClient +# from frostfs_sdk.client.parameters.container_create import PrmContainerCreate +# from frostfs_sdk.models.dto.container import ContainerId +# from tests.helpers.models import Helpers + + +# @pytest.mark.container +# class TestContainer: +# def test_create_container(self, default_frostfs_client: FrostfsClient, helpers: Helpers): +# req_body: PrmContainerCreate = helpers.container.create_params_container_create() +# cid: ContainerId = default_frostfs_client.container.create(req_body) +# print(cid.value) diff --git a/tests/conftest.py b/tests/conftest.py index 3e26bbe..9dcd47b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,24 @@ import pytest -from tests.helpers.models import ClientCryptograpy +# from frostfs_sdk.client.frostfs_client import FrostfsClient +# from frostfs_sdk.client.models.client_settings import ClientSettings +from tests.helpers.models import ClientCryptograpy, Helpers @pytest.fixture(scope="session") def client_cryptography(): return ClientCryptograpy() + + +# @pytest.fixture(scope="session") +# def default_frostfs_client(): +# client_settings = ClientSettings( +# wif="", +# address="10.78.128.25:8080" +# ) +# return FrostfsClient(client_settings) + + +@pytest.fixture(scope="session") +def helpers(): + return Helpers() diff --git a/tests/helpers/models.py b/tests/helpers/models.py index 56071ad..641710c 100644 --- a/tests/helpers/models.py +++ b/tests/helpers/models.py @@ -1,6 +1,7 @@ -from frostfs_api.cryptography.key_extension import KeyExtension -from frostfs_api.cryptography.verifier import Verifier -from frostfs_api.cryptography.signer import Signer +from frostfs_sdk.cryptography.key_extension import KeyExtension +from frostfs_sdk.cryptography.verifier import Verifier +from frostfs_sdk.cryptography.signer import Signer +from tests.helpers.params_container import ParamsContainerHelper class ClientCryptograpy: @@ -8,3 +9,8 @@ class ClientCryptograpy: self.key_extension = KeyExtension() self.signer = Signer() self.verifier = Verifier() + + +class Helpers: + def __init__(self): + self.container = ParamsContainerHelper() diff --git a/tests/helpers/params_container.py b/tests/helpers/params_container.py new file mode 100644 index 0000000..3817246 --- /dev/null +++ b/tests/helpers/params_container.py @@ -0,0 +1,22 @@ +from frostfs_sdk.client.parameters.container_create import PrmContainerCreate +from frostfs_sdk.client.parameters.wait import PrmWait +from frostfs_sdk.models.dto.container import Container +from frostfs_sdk.models.dto.placement_policy import PlacementPolicy +from frostfs_sdk.models.dto.replica import Replica +from frostfs_sdk.models.dto.session_token import SessionToken +from frostfs_sdk.models.enums.basic_acl import BasicAcl + + +class ParamsContainerHelper: + def create_params_container_create(self): + req_container_create = PrmContainerCreate( + container=Container( + basicAcl=BasicAcl.PUBLIC_RW, + placementPolicy=PlacementPolicy( + replicas=[Replica(count=1)], + unique=True + ) + ), + wait_params=PrmWait() + ) + return req_container_create