[#3] Added generate proto script create container method
Signed-off-by: Ilyas Niyazov <i.niyazov@yadro.com>
This commit is contained in:
parent
f8465e5b99
commit
fba6eaaa9c
34 changed files with 547 additions and 108 deletions
|
@ -0,0 +1,2 @@
|
|||
from frostfs_sdk.client import *
|
||||
from frostfs_sdk.models import *
|
10
frostfs_sdk/client/__init__.py
Normal file
10
frostfs_sdk/client/__init__.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from frostfs_sdk.client.frostfs_client import FrostfsClient
|
||||
|
||||
from frostfs_sdk.client.models.client_environment import ClientEnvironment
|
||||
from frostfs_sdk.client.models.client_settings import ClientSettings
|
||||
from frostfs_sdk.client.models.ecdsa_model import ECDSA
|
||||
|
||||
from frostfs_sdk.client.parameters.container_param import ContainerCreateParam
|
||||
from frostfs_sdk.client.parameters.wait_param import WaitParam
|
||||
|
||||
from frostfs_sdk.client.services.container import ContainerClient
|
|
@ -1,17 +1,20 @@
|
|||
# Create channel and Stubs
|
||||
import grpc
|
||||
|
||||
from frostfs_sdk.client.services.session import SessionCache
|
||||
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.models.ecdsa_model import ECDSA
|
||||
from frostfs_sdk.client.services.container import ContainerClient
|
||||
from frostfs_sdk.models.dto.version import Version
|
||||
|
||||
|
||||
class FrostfsClient:
|
||||
def __init__(self, client_settings: ClientSettings):
|
||||
self.channel = grpc.insecure_channel(f"{client_settings.host}:{client_settings.port}")
|
||||
self.channel = grpc.insecure_channel(client_settings.address)
|
||||
self.ecdsa: ECDSA = ECDSA(wif=client_settings.wif)
|
||||
|
||||
client_environment = ClientEnvironment(self.ecdsa, self.channel)
|
||||
client_environment = ClientEnvironment(self.ecdsa, self.channel, client_settings.address, Version(), SessionCache(0))
|
||||
self.container = ContainerClient(client_environment)
|
||||
|
||||
def close(self):
|
||||
|
|
|
@ -1,8 +1,22 @@
|
|||
import grpc
|
||||
from frostfs_sdk.client.models.ecdsa import ECDSA
|
||||
from frostfs_sdk.cryptography.key_extension import KeyExtension
|
||||
from frostfs_sdk.client.models.ecdsa_model import ECDSA
|
||||
from frostfs_sdk.models.dto.version import Version
|
||||
from frostfs_sdk.models.dto.owner_id import OwnerId
|
||||
from frostfs_sdk.client.services.session import SessionCache
|
||||
|
||||
|
||||
class ClientEnvironment:
|
||||
def __init__(self, ecdsa: ECDSA, channel: grpc.Channel):
|
||||
def __init__(self, ecdsa: ECDSA, channel: grpc.Channel, address: str, version: Version, session_cache: SessionCache):
|
||||
self.ecdsa = ecdsa
|
||||
self.channel = channel
|
||||
self.version = version
|
||||
# self.owner_id = OwnerId(KeyExtension.get_owner_id_by_wif(ecdsa.wif))
|
||||
self.owner_id = "11"
|
||||
self.session_cache = session_cache
|
||||
self.address = address
|
||||
|
||||
def get_session_key(self):
|
||||
if not self._session_key:
|
||||
self._session_key = SessionCache.form_cache_key(self.address, KeyExtension.get_hex_string(self.ecdsa.public_key))
|
||||
return self._session_key
|
||||
|
|
21
frostfs_sdk/client/parameters/call_context_param.py
Normal file
21
frostfs_sdk/client/parameters/call_context_param.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
|
||||
DEFAULT_GRPC_TIMEOUT = 5
|
||||
|
||||
|
||||
class TimeUnit(Enum):
|
||||
MINUTES = "MINUTES"
|
||||
SECONDS = "SECONDS"
|
||||
MILLISECONDS = "MILLISECONDS"
|
||||
|
||||
@dataclass
|
||||
class CallContextParam:
|
||||
timeout: int = DEFAULT_GRPC_TIMEOUT
|
||||
time_unit: TimeUnit = TimeUnit.SECONDS
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
return cls()
|
|
@ -2,17 +2,17 @@ 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
|
||||
from frostfs_sdk.client.services.session import SessionToken
|
||||
from frostfs_sdk.client.parameters.wait_param import WaitParam
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PrmContainerCreate:
|
||||
class ContainerCreateParam:
|
||||
container: Container
|
||||
wait_params: Optional[PrmWait] = None
|
||||
wait_params: Optional[WaitParam] = 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())
|
||||
object.__setattr__(self, 'wait_params', WaitParam())
|
|
@ -4,7 +4,7 @@ from typing import Optional
|
|||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PrmWait:
|
||||
class WaitParam:
|
||||
DEFAULT_TIMEOUT: timedelta = field(default=timedelta(seconds=120), init=False)
|
||||
DEFAULT_POLL_INTERVAL: timedelta = field(default=timedelta(seconds=5), init=False)
|
||||
|
|
@ -1,30 +1,41 @@
|
|||
# implementation Conainer methods
|
||||
from frostfs_sdk.client.models.client_environment import ClientEnvironment
|
||||
from frostfs_sdk.client.services.context_accessor import ContextAccessor
|
||||
from frostfs_sdk.client.parameters.container_param import ContainerCreateParam
|
||||
from frostfs_sdk.client.parameters.call_context_param import CallContextParam
|
||||
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.dto.container import ContainerId, Container
|
||||
from frostfs_sdk.models.mappers.container_mapper import ContainerMapper
|
||||
from frostfs_sdk.models.mappers.owner_id_mapper import OwnerIdMapper
|
||||
from frostfs_sdk.models.mappers.version_mapper import VersionMapper
|
||||
from frostfs_sdk.protos.models.container import service_pb2 as service_pb2_container
|
||||
from frostfs_sdk.protos.models.container import service_pb2_grpc as service_pb2_grpc_container
|
||||
|
||||
|
||||
|
||||
class ContainerClient:
|
||||
class ContainerClient(ContextAccessor):
|
||||
def __init__(self, client_environment: ClientEnvironment):
|
||||
super().__init__(client_environment)
|
||||
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)
|
||||
def create_container(self, container_create_param: ContainerCreateParam, ctx: CallContextParam) -> ContainerId:
|
||||
request = self._create_put_request(container_create_param, ctx)
|
||||
response: service_pb2_container.PutResponse = self.container_stub.Put(request)
|
||||
return ContainerId(value=response.body.container_id)
|
||||
return ContainerId(value=response.body.container_id.value)
|
||||
|
||||
def _create_put_request(self, param: ContainerCreateParam, ctx: CallContextParam) -> service_pb2_container.PutRequest:
|
||||
"""
|
||||
Creates a PUT request for creating a container.
|
||||
"""
|
||||
grpc_container=ContainerMapper().to_grpc_message(param.container)
|
||||
if not grpc_container.owner_id:
|
||||
grpc_container.owner_id = OwnerIdMapper.to_grpc_message(self.get_context.owner_id)
|
||||
if not grpc_container.version:
|
||||
grpc_container.version = VersionMapper.to_grpc_message(self.get_context.version)
|
||||
|
||||
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)
|
||||
signature=Signer.sign_message_rfc_6979(self.get_context.ecdsa, grpc_container)
|
||||
)
|
||||
|
||||
request = service_pb2_container.PutRequest(body=body)
|
||||
|
|
12
frostfs_sdk/client/services/context_accessor.py
Normal file
12
frostfs_sdk/client/services/context_accessor.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from frostfs_sdk.client.models.client_environment import ClientEnvironment
|
||||
|
||||
class ContextAccessor:
|
||||
def __init__(self, context: ClientEnvironment):
|
||||
"""
|
||||
Initializes a ContextAccessor with a given ClientEnvironment.
|
||||
"""
|
||||
self.context: ClientEnvironment = context
|
||||
|
||||
@property
|
||||
def get_context(self) -> ClientEnvironment:
|
||||
return self.context
|
37
frostfs_sdk/client/services/session.py
Normal file
37
frostfs_sdk/client/services/session.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SessionToken:
|
||||
token: bytes
|
||||
|
||||
|
||||
class SessionCache:
|
||||
def __init__(self, session_expiration_duration):
|
||||
self.cache = {}
|
||||
self.token_duration = session_expiration_duration
|
||||
self.current_epoch = 0
|
||||
|
||||
|
||||
def contains(self, key):
|
||||
return key in self.cache
|
||||
|
||||
def try_get_value(self, key):
|
||||
if not key:
|
||||
return None
|
||||
return self.cache.get(key)
|
||||
|
||||
|
||||
def set_value(self, key, value):
|
||||
if key is not None:
|
||||
self.cache[key] = value
|
||||
|
||||
def delete_by_prefix(self, prefix):
|
||||
# Collect keys to avoid modifying dictionary during iteration
|
||||
keys_to_delete = [key for key in self.cache if key.startswith(prefix)]
|
||||
for key in keys_to_delete:
|
||||
del self.cache[key]
|
||||
|
||||
@staticmethod
|
||||
def form_cache_key(address: str, key: str):
|
||||
return address + key
|
30
frostfs_sdk/client/utils/message_helper.py
Normal file
30
frostfs_sdk/client/utils/message_helper.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
from typing import Any
|
||||
from google.protobuf.message import Message
|
||||
|
||||
class MessageHelper:
|
||||
@staticmethod
|
||||
def get_field(message: Message, field_name: str):
|
||||
"""
|
||||
Retrieves the value of a field from a Protobuf message.
|
||||
|
||||
:param message: A Protobuf Message object.
|
||||
:param field_name: The name of the field to retrieve.
|
||||
:return: The value of the specified field.
|
||||
:raises ValueError: If the input parameters are invalid.
|
||||
"""
|
||||
if not message or not field_name or not isinstance(field_name, str) or not field_name.strip():
|
||||
raise ValueError("Some parameter is missing")
|
||||
|
||||
descriptor = message.DESCRIPTOR
|
||||
field_descriptor = descriptor.fields[field_name]
|
||||
if not field_descriptor:
|
||||
raise ValueError(f"Field '{field_name}' not found in message descriptor")
|
||||
|
||||
return message.GetField(field_descriptor[field_name])
|
||||
|
||||
@staticmethod
|
||||
def set_field(message: Message, field_name: str, value: Any) -> None:
|
||||
if message is None or not field_name.strip() or value is None:
|
||||
raise ValueError("Some parameter is missing")
|
||||
|
||||
message.SetField(message.DESCRIPTOR.fields[field_name], value)
|
|
@ -42,3 +42,11 @@ class KeyExtension:
|
|||
if len(sequence_symbols) == 0 or sequence_symbols is None:
|
||||
raise ValueError(f"Empty sequence symbols of key: {sequence_symbols}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_hex_string(value):
|
||||
if value is None or len(value) == 0:
|
||||
raise ValueError("Input parameter is missing")
|
||||
|
||||
# Convert byte array to hexadecimal string
|
||||
return f"{int.from_bytes(value, byteorder='big'):0{len(value) * 2}x}"
|
||||
|
|
|
@ -1,8 +1,20 @@
|
|||
import ecdsa
|
||||
from hashlib import sha256, sha512
|
||||
|
||||
from google.protobuf.message import Message
|
||||
|
||||
from frostfs_sdk.client.models.ecdsa_model import ECDSA
|
||||
from frostfs_sdk.protos.models.refs.types_pb2 import SignatureRFC6979, Signature
|
||||
|
||||
|
||||
class Signer:
|
||||
@staticmethod
|
||||
def sign_message_rfc_6979(key: ECDSA, message: Message) -> SignatureRFC6979:
|
||||
return SignatureRFC6979(
|
||||
key=key.public_key,
|
||||
sign=Signer.sign_rfc6979(key.private_key, message.SerializeToString())
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def sign_rfc6979(private_key: bytes, message: bytes) -> bytes:
|
||||
if len(private_key) == 0 or private_key is None:
|
||||
|
@ -14,6 +26,13 @@ class Signer:
|
|||
|
||||
return signature
|
||||
|
||||
@staticmethod
|
||||
def sign_message(key: ECDSA, message: Message) -> SignatureRFC6979:
|
||||
return SignatureRFC6979(
|
||||
key=key.public_key,
|
||||
sign=Signer.sign(key.private_key, message.SerializeToString())
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def sign(private_key: bytes, message: bytes) -> bytes:
|
||||
if len(private_key) == 0 or private_key is None:
|
||||
|
@ -26,3 +45,10 @@ class Signer:
|
|||
signature_with_marker = bytes([0x04]) + signature
|
||||
|
||||
return signature_with_marker
|
||||
|
||||
@staticmethod
|
||||
def _sign_message_part(key: ECDSA, data: Message) -> Signature:
|
||||
return Signature(
|
||||
key=key.public_key,
|
||||
sign=Signature.sign(key.private_key, data.SerializeToString())
|
||||
)
|
||||
|
|
9
frostfs_sdk/models/__init__.py
Normal file
9
frostfs_sdk/models/__init__.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from frostfs_sdk.models.dto.container import Container, ContainerId
|
||||
from frostfs_sdk.models.dto.filter import Filter
|
||||
from frostfs_sdk.models.dto.placement_policy import PlacementPolicy
|
||||
from frostfs_sdk.models.dto.replica import Replica
|
||||
from frostfs_sdk.models.dto.selector import Selector
|
||||
|
||||
from frostfs_sdk.models.enums.basic_acl import BasicAcl
|
||||
from frostfs_sdk.models.enums.filter_operation import FilterOperation
|
||||
from frostfs_sdk.models.enums.selector_clause import SelectorClause
|
|
@ -2,6 +2,8 @@ from dataclasses import dataclass, field
|
|||
from typing import Dict, Optional
|
||||
import uuid
|
||||
|
||||
from frostfs_sdk.models.dto.owner_id import OwnerId
|
||||
from frostfs_sdk.models.dto.version import Version
|
||||
from frostfs_sdk.models.enums.basic_acl import BasicAcl
|
||||
from frostfs_sdk.models.dto.placement_policy import PlacementPolicy
|
||||
|
||||
|
@ -9,15 +11,17 @@ from frostfs_sdk.models.dto.placement_policy import PlacementPolicy
|
|||
|
||||
@dataclass
|
||||
class Container:
|
||||
basicAcl: BasicAcl
|
||||
# basicAcl: BasicAcl # TODO: will remove it?
|
||||
placementPolicy: PlacementPolicy
|
||||
nonce: uuid.UUID = field(default_factory=uuid.uuid4)
|
||||
version: Optional[str] = None
|
||||
version: Optional[Version] = None
|
||||
owner_id: Optional[OwnerId] = None
|
||||
attributes: Dict[str, str] = field(default_factory=dict)
|
||||
|
||||
def __init__(self, basicAcl: BasicAcl, placementPolicy: PlacementPolicy):
|
||||
self.basicAcl = basicAcl
|
||||
def __init__(self, placementPolicy: PlacementPolicy):
|
||||
self.nonce = uuid.uuid4()
|
||||
self.placementPolicy = placementPolicy
|
||||
self.attributes = {}
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
20
frostfs_sdk/models/dto/owner_id.py
Normal file
20
frostfs_sdk/models/dto/owner_id.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from base58 import b58decode
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OwnerId:
|
||||
value: str
|
||||
|
||||
def __post_init__(self):
|
||||
if not self.value or self.value.strip() == "":
|
||||
raise ValueError(f"{self.__class__.__name__} value is not present")
|
||||
|
||||
def to_hash(self) -> bytes:
|
||||
"""Decodes the Base58-encoded value into a byte array."""
|
||||
try:
|
||||
return b58decode(self.value)
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to decode Base58 value: {self.value}") from e
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
|
@ -2,8 +2,13 @@ from dataclasses import dataclass
|
|||
from typing import List
|
||||
|
||||
from frostfs_sdk.models.dto.replica import Replica
|
||||
from frostfs_sdk.models.dto.selector import Selector
|
||||
from frostfs_sdk.models.dto.filter import Filter
|
||||
|
||||
@dataclass
|
||||
class PlacementPolicy:
|
||||
replicas: List[Replica]
|
||||
unique: bool
|
||||
backup_factory: int
|
||||
filters: List[Filter] = None
|
||||
selectors: List[Selector] = None
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SessionToken:
|
||||
token: bytes
|
19
frostfs_sdk/models/dto/version.py
Normal file
19
frostfs_sdk/models/dto/version.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
DEFAULT_MAJOR_VERSION = 2
|
||||
DEFAULT_MINOR_VERSION = 13
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Version:
|
||||
major: int = DEFAULT_MAJOR_VERSION
|
||||
minor: int = DEFAULT_MINOR_VERSION
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"v{self.major}.{self.minor}"
|
||||
|
||||
def is_supported(self, other):
|
||||
if not isinstance(other, Version):
|
||||
return False
|
||||
return self.major == other.major
|
||||
|
|
@ -1,26 +1,14 @@
|
|||
class SelectorClause:
|
||||
"""
|
||||
Enum for selector clauses with integer value mapping
|
||||
"""
|
||||
from enum import Enum, unique
|
||||
|
||||
@unique
|
||||
class SelectorClause(Enum):
|
||||
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)
|
||||
def get(cls, value: int):
|
||||
try:
|
||||
return cls(value)
|
||||
except ValueError:
|
||||
raise KeyError(f"Unknown enum value: {value}")
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
from typing import ByteString, Optional
|
||||
from typing import Optional
|
||||
|
||||
import grpc
|
||||
|
||||
from frostfs_sdk.models.mappers.placement_policy_mapper import PlacementPolicyMapper
|
||||
import protos.models.container.types_pb2 as types_pb2_container
|
||||
from frostfs_sdk.models.mappers.owner_id_mapper import OwnerIdMapper
|
||||
from frostfs_sdk.models.mappers.version_mapper import VersionMapper
|
||||
from frostfs_sdk.models.mappers.uuid_extension import UuidExtension
|
||||
from frostfs_sdk.models.dto.container import Container
|
||||
from frostfs_sdk.protos.models.container import types_pb2 as types_pb2_container
|
||||
|
||||
|
||||
class ContainerMapper:
|
||||
|
@ -26,17 +31,16 @@ class ContainerMapper:
|
|||
]
|
||||
|
||||
grpc_container = types_pb2_container.Container(
|
||||
# nonce=ByteString.copy(container.nonce),
|
||||
nonce=container.nonce.bytes,
|
||||
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.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)
|
||||
if container.version:
|
||||
grpc_container.version = VersionMapper.to_grpc_message(container.version)
|
||||
|
||||
return grpc_container
|
||||
|
||||
|
@ -57,9 +61,9 @@ class ContainerMapper:
|
|||
attributes = {attr.key: attr.value for attr in container_grpc.attributes}
|
||||
|
||||
return Container(
|
||||
# nonce=UuidUtils.as_uuid(container_grpc.nonce.to_bytes()),
|
||||
nonce=UuidExtension.to_uuid(container_grpc.nonce),
|
||||
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),
|
||||
version=VersionMapper.to_model(container_grpc.version),
|
||||
owner_id=OwnerIdMapper.to_model(container_grpc.owner_id),
|
||||
attributes=attributes
|
||||
)
|
||||
|
|
|
@ -2,7 +2,7 @@ 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
|
||||
from frostfs_sdk.protos.models.netmap import types_pb2 as types_pb2_netmap
|
||||
|
||||
|
||||
class FilterMapper:
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
from base58 import b58encode
|
||||
|
||||
from frostfs_sdk.models.dto.owner_id import OwnerId
|
||||
from frostfs_sdk.protos.models.refs import types_pb2 as types_pb2_refs
|
||||
|
||||
class OwnerIdMapper:
|
||||
@staticmethod
|
||||
def to_grpc_message(owner_id: OwnerId) -> types_pb2_refs.OwnerID:
|
||||
"""
|
||||
Converts OwnerId DTO to gRPC message
|
||||
"""
|
||||
if not owner_id:
|
||||
return None
|
||||
|
||||
return types_pb2_refs.OwnerID(
|
||||
value=owner_id.to_hash(),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def to_model(owner_id_grpc: types_pb2_refs.OwnerID) -> OwnerId:
|
||||
"""
|
||||
Converts gRPC message to OwnerId DTO
|
||||
"""
|
||||
if not owner_id_grpc or owner_id_grpc.ByteSize() == 0:
|
||||
return None
|
||||
|
||||
try:
|
||||
return OwnerId(
|
||||
value=b58encode(owner_id_grpc.value).decode('utf-8')
|
||||
)
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to encode Base58 value: {owner_id_grpc.value}")
|
|
@ -1,7 +1,10 @@
|
|||
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.mappers.selector_mapper import SelectorMapper
|
||||
from frostfs_sdk.models.mappers.replica_mapper import ReplicaMapper
|
||||
from frostfs_sdk.models.dto.placement_policy import PlacementPolicy
|
||||
from frostfs_sdk.protos.models.netmap import types_pb2 as types_pb2_netmap
|
||||
|
||||
|
||||
class PlacementPolicyMapper:
|
||||
|
@ -21,10 +24,10 @@ class PlacementPolicyMapper:
|
|||
|
||||
return types_pb2_netmap.PlacementPolicy(
|
||||
unique=policy.unique,
|
||||
container_backup_factor=policy.backup_factor,
|
||||
container_backup_factor=policy.backup_factory,
|
||||
filters=FilterMapper.to_grpc_messages(policy.filters),
|
||||
# selectors=SelectorMapper.to_grpc_messages(policy.selectors),
|
||||
# replicas=ReplicaMapper.to_grpc_messages(policy.replicas)
|
||||
selectors=SelectorMapper.to_grpc_messages(policy.selectors),
|
||||
replicas=ReplicaMapper.to_grpc_messages(policy.replicas)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -42,9 +45,9 @@ class PlacementPolicyMapper:
|
|||
return None
|
||||
|
||||
return PlacementPolicy(
|
||||
# replicas=ReplicaMapper.to_models(policy_grpc.replicas),
|
||||
replicas=ReplicaMapper.to_models(policy_grpc.replicas),
|
||||
unique=policy_grpc.unique,
|
||||
backup_factor=policy_grpc.container_backup_factor,
|
||||
backup_factory=policy_grpc.container_backup_factor,
|
||||
filters=FilterMapper.to_models(policy_grpc.filters),
|
||||
# selectors=SelectorMapper.to_models(policy_grpc.selectors)
|
||||
selectors=SelectorMapper.to_models(policy_grpc.selectors)
|
||||
)
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from frostfs_sdk.models.dto.replica import Replica
|
||||
from frostfs_sdk.protos.models.netmap import types_pb2 as types_pb2_netmap
|
||||
|
||||
|
||||
class ReplicaMapper:
|
||||
@staticmethod
|
||||
def to_grpc_messages(replicas: List[Replica]) -> Optional[List[types_pb2_netmap.Replica]]:
|
||||
if not replicas:
|
||||
return None
|
||||
return [ReplicaMapper.to_grpc_message(selector) for selector in replicas]
|
||||
|
||||
@staticmethod
|
||||
def to_grpc_message(replica: Replica) -> Optional[types_pb2_netmap.Replica]:
|
||||
"""
|
||||
Converts Replica DTO to gRPC message
|
||||
|
||||
Args:
|
||||
replice: Replica DTO object
|
||||
|
||||
Returns:
|
||||
gRPC Replica message
|
||||
"""
|
||||
if not replica:
|
||||
return None
|
||||
|
||||
return types_pb2_netmap.Replica(
|
||||
count=replica.count,
|
||||
selector=replica.selector
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def to_models(grpc_replicas: List[types_pb2_netmap.Replica]) -> Optional[List[Replica]]:
|
||||
if not grpc_replicas:
|
||||
return None
|
||||
return [ReplicaMapper.to_model(grpc_replica) for grpc_replica in grpc_replicas]
|
||||
|
||||
@staticmethod
|
||||
def to_model(grpc_replica: types_pb2_netmap.Replica) -> Optional[Replica]:
|
||||
"""
|
||||
Converts gRPC message to Replica DTO
|
||||
|
||||
Args:
|
||||
grpc_replica: gRPC Replica message
|
||||
|
||||
Returns:
|
||||
Replica DTO object
|
||||
"""
|
||||
if not grpc_replica or grpc_replica.ByteSize() == 0:
|
||||
return None
|
||||
|
||||
return Replica(
|
||||
count=grpc_replica.count,
|
||||
selectors=grpc_replica.selector
|
||||
)
|
|
@ -0,0 +1,73 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from frostfs_sdk.models.dto.selector import Selector
|
||||
from frostfs_sdk.models.enums.selector_clause import SelectorClause
|
||||
from frostfs_sdk.protos.models.netmap import types_pb2 as types_pb2_netmap
|
||||
|
||||
|
||||
class SelectorMapper:
|
||||
@staticmethod
|
||||
def to_grpc_messages(selectors: List[Selector]) -> Optional[List[types_pb2_netmap.Selector]]:
|
||||
if not selectors:
|
||||
return None
|
||||
return [SelectorMapper.to_grpc_message(selector) for selector in selectors]
|
||||
|
||||
@staticmethod
|
||||
def to_grpc_message(selector: Selector) -> Optional[types_pb2_netmap.Selector]:
|
||||
"""
|
||||
Converts Selector DTO to gRPC message
|
||||
|
||||
Args:
|
||||
selector: Selector DTO object
|
||||
|
||||
Returns:
|
||||
gRPC Selector message
|
||||
"""
|
||||
if not selector:
|
||||
return None
|
||||
|
||||
clause_grpc = types_pb2_netmap.Clause(selector.clause.value)
|
||||
if clause_grpc is None:
|
||||
raise ValueError(f"Unknown enum value: {selector.clause.name} for {types_pb2_netmap.Clause.__name__}")
|
||||
|
||||
|
||||
return types_pb2_netmap.Selector(
|
||||
name=selector.name,
|
||||
count=selector.count,
|
||||
clause=clause_grpc,
|
||||
attribute=selector.attribute,
|
||||
filter=selector.filter
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def to_models(grpc_selectors: List[types_pb2_netmap.Selector]) -> Optional[List[Selector]]:
|
||||
if not grpc_selectors:
|
||||
return None
|
||||
return [SelectorMapper.to_model(grpc_selector) for grpc_selector in grpc_selectors]
|
||||
|
||||
@staticmethod
|
||||
def to_model(selector_grpc: types_pb2_netmap.Selector) -> Optional[Selector]:
|
||||
"""
|
||||
Converts gRPC message to Selector DTO
|
||||
|
||||
Args:
|
||||
selector_grpc: gRPC Selector message
|
||||
|
||||
Returns:
|
||||
Selector DTO object
|
||||
"""
|
||||
if not selector_grpc or selector_grpc.ByteSize() == 0:
|
||||
return None
|
||||
|
||||
clause = SelectorClause.get(selector_grpc.clause)
|
||||
if clause is None:
|
||||
raise ValueError(f"Unknown enum value: {selector_grpc.clause} for {SelectorClause.__name__}")
|
||||
|
||||
|
||||
return Selector(
|
||||
name=selector_grpc.name,
|
||||
count=selector_grpc.count,
|
||||
clause=clause,
|
||||
attribute=selector_grpc.attribute,
|
||||
filter=selector_grpc.filter
|
||||
)
|
31
frostfs_sdk/models/mappers/uuid_extension.py
Normal file
31
frostfs_sdk/models/mappers/uuid_extension.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
UUID_BYTE_ARRAY_LENGTH = 16
|
||||
|
||||
|
||||
class UuidExtension:
|
||||
@staticmethod
|
||||
def to_uuid(bytes_: Optional[bytes]) -> uuid.UUID:
|
||||
"""
|
||||
Converts a byte array into a UUID object.
|
||||
"""
|
||||
if bytes_ is None or len(bytes_) != UUID_BYTE_ARRAY_LENGTH:
|
||||
raise ValueError(f"Wrong UUID size: expected {UUID_BYTE_ARRAY_LENGTH} bytes, got {len(bytes_)}")
|
||||
|
||||
# Unpack the byte array into two 64-bit integers
|
||||
most_sig_bits = int.from_bytes(bytes_[:8], byteorder='big', signed=False)
|
||||
least_sig_bits = int.from_bytes(bytes_[8:], byteorder='big', signed=False)
|
||||
|
||||
return uuid.UUID(int=(most_sig_bits << 64) | least_sig_bits)
|
||||
|
||||
@staticmethod
|
||||
def to_bytes(uuid_: Optional[uuid.UUID]) -> bytes:
|
||||
"""
|
||||
Converts a UUID object into a byte array.
|
||||
"""
|
||||
if uuid_ is None:
|
||||
raise ValueError(f"Input parameter is missing: {uuid.UUID.__name__}")
|
||||
|
||||
# Pack the UUID into a 16-byte array
|
||||
return (uuid_.int >> 64).to_bytes(8, byteorder='big') + (uuid_.int & 0xFFFFFFFFFFFFFFFF).to_bytes(8, byteorder='big')
|
31
frostfs_sdk/models/mappers/version_mapper.py
Normal file
31
frostfs_sdk/models/mappers/version_mapper.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
from typing import Optional
|
||||
from frostfs_sdk.models.dto.version import Version
|
||||
from frostfs_sdk.protos.models.refs import types_pb2 as types_pb2_refs
|
||||
|
||||
|
||||
class VersionMapper:
|
||||
@staticmethod
|
||||
def to_grpc_message(version: Optional[Version]) -> Optional[types_pb2_refs.Version]:
|
||||
"""
|
||||
Converts a Version object to a gRPC Version message.
|
||||
"""
|
||||
if version is None:
|
||||
return None
|
||||
|
||||
return types_pb2_refs.Version(
|
||||
major=version.major,
|
||||
minor=version.minor
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def to_model(grpc_version: Optional[types_pb2_refs.Version]) -> Optional[Version]:
|
||||
"""
|
||||
Converts a gRPC Version message to a Version object.
|
||||
"""
|
||||
if grpc_version is None or grpc_version.ByteSize() == 0:
|
||||
return None
|
||||
|
||||
return Version(
|
||||
major=grpc_version.major,
|
||||
minor=grpc_version.minor
|
||||
)
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
|
||||
# Define directories
|
||||
PROTOS_DIR="./protos"
|
||||
PROTOS_DIR="./frostfs_sdk/protos"
|
||||
SOURCE_DIR="${PROTOS_DIR}/source"
|
||||
MODELS_DIR="${PROTOS_DIR}/models"
|
||||
REPO_FROSTFS_API_PROTOS="https://git.frostfs.info/TrueCloudLab/frostfs-api.git"
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
# import pytest
|
||||
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
|
||||
from frostfs_sdk import FrostfsClient, ContainerCreateParam, ContainerId
|
||||
from frostfs_sdk.client.parameters.call_context_param import CallContextParam, TimeUnit
|
||||
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)
|
||||
@pytest.mark.container
|
||||
class TestContainer:
|
||||
def test_create_container(self, default_frostfs_client: FrostfsClient, helpers: Helpers):
|
||||
call_context = CallContextParam(timeout=1, time_unit=TimeUnit.MINUTES)
|
||||
req_body: ContainerCreateParam = helpers.container.create_params_container_create()
|
||||
|
||||
cid: ContainerId = default_frostfs_client.container.create_container(req_body, call_context)
|
||||
print(cid.value)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import pytest
|
||||
|
||||
# from frostfs_sdk.client.frostfs_client import FrostfsClient
|
||||
# from frostfs_sdk.client.models.client_settings import ClientSettings
|
||||
from frostfs_sdk import ClientSettings, FrostfsClient
|
||||
from tests.helpers.models import ClientCryptograpy, Helpers
|
||||
|
||||
|
||||
|
@ -10,13 +9,14 @@ 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 default_frostfs_client():
|
||||
client_settings = ClientSettings(
|
||||
wif="KxnEZ7FsPgKMdL9PYt9vsDkXiSw6qP9J8dpR4eVMsDpJyJxcYpve",
|
||||
address="10.78.130.201:8080",
|
||||
# address="localhost:8080"
|
||||
)
|
||||
return FrostfsClient(client_settings)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
|
|
|
@ -1,22 +1,16 @@
|
|||
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
|
||||
from frostfs_sdk import ContainerCreateParam, WaitParam, Container, PlacementPolicy, Replica, BasicAcl
|
||||
|
||||
|
||||
class ParamsContainerHelper:
|
||||
def create_params_container_create(self):
|
||||
req_container_create = PrmContainerCreate(
|
||||
req_container_create = ContainerCreateParam(
|
||||
container=Container(
|
||||
basicAcl=BasicAcl.PUBLIC_RW,
|
||||
placementPolicy=PlacementPolicy(
|
||||
replicas=[Replica(count=1)],
|
||||
unique=True
|
||||
unique=True,
|
||||
backup_factory=0
|
||||
)
|
||||
),
|
||||
wait_params=PrmWait()
|
||||
wait_params=WaitParam()
|
||||
)
|
||||
return req_container_create
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue