forked from TrueCloudLab/frostfs-testlib
[#326] Automation of PATCH method in GRPC
Signed-off-by: Kirill Sosnovskikh <k.sosnovskikh@yadro.com>
This commit is contained in:
parent
0e040d2722
commit
1e8e983d05
5 changed files with 165 additions and 0 deletions
|
@ -276,6 +276,53 @@ class FrostfsCliObject(CliCommand):
|
||||||
**{param: value for param, value in locals().items() if param not in ["self"]},
|
**{param: value for param, value in locals().items() if param not in ["self"]},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def patch(
|
||||||
|
self,
|
||||||
|
rpc_endpoint: str,
|
||||||
|
cid: str,
|
||||||
|
oid: str,
|
||||||
|
range: list[str] = None,
|
||||||
|
payload: list[str] = None,
|
||||||
|
new_attrs: Optional[str] = None,
|
||||||
|
replace_attrs: bool = False,
|
||||||
|
address: Optional[str] = None,
|
||||||
|
bearer: Optional[str] = None,
|
||||||
|
generate_key: Optional[bool] = None,
|
||||||
|
session: Optional[str] = None,
|
||||||
|
timeout: Optional[str] = None,
|
||||||
|
trace: bool = False,
|
||||||
|
ttl: Optional[int] = None,
|
||||||
|
wallet: Optional[str] = None,
|
||||||
|
xhdr: Optional[dict] = None,
|
||||||
|
) -> CommandResult:
|
||||||
|
"""
|
||||||
|
PATCH an object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
rpc_endpoint: Remote node address (as 'multiaddr' or '<host>:<port>')
|
||||||
|
cid: Container ID
|
||||||
|
oid: Object ID
|
||||||
|
range: An array of ranges in which to replace data in the format [offset1:length1, offset2:length2]
|
||||||
|
payload: An array of file paths to be applied in each range
|
||||||
|
new_attrs: Attributes to be changed in the format Key1=Value1,Key2=Value2
|
||||||
|
replace_attrs: Replace all attributes completely with new ones specified in new_attrs
|
||||||
|
address: Address of wallet account
|
||||||
|
bearer: File with signed JSON or binary encoded bearer token
|
||||||
|
generate_key: Generate new private key
|
||||||
|
session: Filepath to a JSON- or binary-encoded token of the object RANGE session
|
||||||
|
timeout: Timeout for the operation
|
||||||
|
trace: Generate trace ID and print it
|
||||||
|
ttl: TTL value in request meta header (default 2)
|
||||||
|
wallet: WIF (NEP-2) string or path to the wallet or binary key
|
||||||
|
xhdr: Dict with request X-Headers
|
||||||
|
Returns:
|
||||||
|
(str): ID of patched Object
|
||||||
|
"""
|
||||||
|
return self._execute(
|
||||||
|
"object patch",
|
||||||
|
**{param: value for param, value in locals().items() if param not in ["self"]},
|
||||||
|
)
|
||||||
|
|
||||||
def range(
|
def range(
|
||||||
self,
|
self,
|
||||||
rpc_endpoint: str,
|
rpc_endpoint: str,
|
||||||
|
|
|
@ -23,4 +23,6 @@ class PlacementRule:
|
||||||
DEFAULT_PLACEMENT_RULE = "REP 2 IN X CBF 1 SELECT 4 FROM * AS X"
|
DEFAULT_PLACEMENT_RULE = "REP 2 IN X CBF 1 SELECT 4 FROM * AS X"
|
||||||
SINGLE_PLACEMENT_RULE = "REP 1 IN X CBF 1 SELECT 4 FROM * AS X"
|
SINGLE_PLACEMENT_RULE = "REP 1 IN X CBF 1 SELECT 4 FROM * AS X"
|
||||||
REP_2_FOR_3_NODES_PLACEMENT_RULE = "REP 2 IN X CBF 1 SELECT 3 FROM * AS X"
|
REP_2_FOR_3_NODES_PLACEMENT_RULE = "REP 2 IN X CBF 1 SELECT 3 FROM * AS X"
|
||||||
|
REP_1_FOR_2_NODES_PLACEMENT_RULE = "REP 1 IN X CBF 1 SELECT 2 FROM * AS X"
|
||||||
DEFAULT_EC_PLACEMENT_RULE = "EC 3.1"
|
DEFAULT_EC_PLACEMENT_RULE = "EC 3.1"
|
||||||
|
EC_1_1_FOR_2_NODES_PLACEMENT_RULE = "EC 1.1 IN X CBF 1 SELECT 2 FROM * AS X"
|
||||||
|
|
|
@ -13,6 +13,7 @@ FROSTFS_CONTRACT_CACHE_TIMEOUT = 30
|
||||||
|
|
||||||
class ObjectOperations(HumanReadableEnum):
|
class ObjectOperations(HumanReadableEnum):
|
||||||
PUT = "object.put"
|
PUT = "object.put"
|
||||||
|
PATCH = "object.patch"
|
||||||
GET = "object.get"
|
GET = "object.get"
|
||||||
HEAD = "object.head"
|
HEAD = "object.head"
|
||||||
GET_RANGE = "object.range"
|
GET_RANGE = "object.range"
|
||||||
|
|
|
@ -206,6 +206,11 @@ class ObjectOperations(interfaces.ObjectInterface):
|
||||||
hash_type=hash_type,
|
hash_type=hash_type,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if range:
|
||||||
|
# Cut off the range and return only hash
|
||||||
|
return result.stdout.split(":")[1].strip()
|
||||||
|
|
||||||
return result.stdout
|
return result.stdout
|
||||||
|
|
||||||
@reporter.step("Head object")
|
@reporter.step("Head object")
|
||||||
|
@ -407,6 +412,57 @@ class ObjectOperations(interfaces.ObjectInterface):
|
||||||
oid = id_str.split(":")[1]
|
oid = id_str.split(":")[1]
|
||||||
return oid.strip()
|
return oid.strip()
|
||||||
|
|
||||||
|
@reporter.step("Patch object")
|
||||||
|
def patch(
|
||||||
|
self,
|
||||||
|
cid: str,
|
||||||
|
oid: str,
|
||||||
|
endpoint: str,
|
||||||
|
ranges: list[str] = None,
|
||||||
|
payloads: list[str] = None,
|
||||||
|
new_attrs: Optional[str] = None,
|
||||||
|
replace_attrs: bool = False,
|
||||||
|
bearer: str = "",
|
||||||
|
xhdr: Optional[dict] = None,
|
||||||
|
session: Optional[str] = None,
|
||||||
|
timeout: Optional[str] = CLI_DEFAULT_TIMEOUT,
|
||||||
|
trace: bool = False,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
PATCH an object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cid: ID of Container where we get the Object from
|
||||||
|
oid: Object ID
|
||||||
|
endpoint: FrostFS endpoint to send request to, appends to `--rpc-endpoint` key
|
||||||
|
ranges: An array of ranges in which to replace data in the format [offset1:length1, offset2:length2]
|
||||||
|
payloads: An array of file paths to be applied in each range
|
||||||
|
new_attrs: Attributes to be changed in the format "key1=value1,key2=value2"
|
||||||
|
replace_attrs: Replace all attributes completely with new ones specified in new_attrs
|
||||||
|
bearer: Path to Bearer Token file, appends to `--bearer` key
|
||||||
|
xhdr: Request X-Headers in form of Key=Value
|
||||||
|
session: Path to a JSON-encoded container session token
|
||||||
|
timeout: Timeout for the operation
|
||||||
|
trace: Generate trace ID and print it
|
||||||
|
Returns:
|
||||||
|
(str): ID of patched Object
|
||||||
|
"""
|
||||||
|
result = self.cli.object.patch(
|
||||||
|
rpc_endpoint=endpoint,
|
||||||
|
cid=cid,
|
||||||
|
oid=oid,
|
||||||
|
range=ranges,
|
||||||
|
payload=payloads,
|
||||||
|
new_attrs=new_attrs,
|
||||||
|
replace_attrs=replace_attrs,
|
||||||
|
bearer=bearer,
|
||||||
|
xhdr=xhdr,
|
||||||
|
session=session,
|
||||||
|
timeout=timeout,
|
||||||
|
trace=trace,
|
||||||
|
)
|
||||||
|
return result.stdout.split(":")[1].strip()
|
||||||
|
|
||||||
@reporter.step("Put object to random node")
|
@reporter.step("Put object to random node")
|
||||||
def put_to_random_node(
|
def put_to_random_node(
|
||||||
self,
|
self,
|
||||||
|
@ -622,3 +678,30 @@ class ObjectOperations(interfaces.ObjectInterface):
|
||||||
]
|
]
|
||||||
|
|
||||||
return object_nodes
|
return object_nodes
|
||||||
|
|
||||||
|
@reporter.step("Search parts of object")
|
||||||
|
def parts(
|
||||||
|
self,
|
||||||
|
cid: str,
|
||||||
|
oid: str,
|
||||||
|
alive_node: ClusterNode,
|
||||||
|
bearer: str = "",
|
||||||
|
xhdr: Optional[dict] = None,
|
||||||
|
is_direct: bool = False,
|
||||||
|
verify_presence_all: bool = False,
|
||||||
|
timeout: Optional[str] = CLI_DEFAULT_TIMEOUT,
|
||||||
|
) -> list[str]:
|
||||||
|
endpoint = alive_node.storage_node.get_rpc_endpoint()
|
||||||
|
response = self.cli.object.nodes(
|
||||||
|
rpc_endpoint=endpoint,
|
||||||
|
cid=cid,
|
||||||
|
oid=oid,
|
||||||
|
bearer=bearer,
|
||||||
|
ttl=1 if is_direct else None,
|
||||||
|
json=True,
|
||||||
|
xhdr=xhdr,
|
||||||
|
timeout=timeout,
|
||||||
|
verify_presence_all=verify_presence_all,
|
||||||
|
)
|
||||||
|
response_json = json.loads(response.stdout)
|
||||||
|
return [data_object["object_id"] for data_object in response_json["data_objects"]]
|
||||||
|
|
|
@ -198,6 +198,24 @@ class ObjectInterface(ABC):
|
||||||
) -> str:
|
) -> str:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def patch(
|
||||||
|
self,
|
||||||
|
cid: str,
|
||||||
|
oid: str,
|
||||||
|
endpoint: str,
|
||||||
|
ranges: Optional[list[str]] = None,
|
||||||
|
payloads: Optional[list[str]] = None,
|
||||||
|
new_attrs: Optional[str] = None,
|
||||||
|
replace_attrs: bool = False,
|
||||||
|
bearer: Optional[str] = None,
|
||||||
|
xhdr: Optional[dict] = None,
|
||||||
|
session: Optional[str] = None,
|
||||||
|
timeout: Optional[str] = None,
|
||||||
|
trace: bool = False,
|
||||||
|
) -> str:
|
||||||
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def put_to_random_node(
|
def put_to_random_node(
|
||||||
self,
|
self,
|
||||||
|
@ -264,6 +282,20 @@ class ObjectInterface(ABC):
|
||||||
) -> List[ClusterNode]:
|
) -> List[ClusterNode]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def parts(
|
||||||
|
self,
|
||||||
|
cid: str,
|
||||||
|
oid: str,
|
||||||
|
alive_node: ClusterNode,
|
||||||
|
bearer: str = "",
|
||||||
|
xhdr: Optional[dict] = None,
|
||||||
|
is_direct: bool = False,
|
||||||
|
verify_presence_all: bool = False,
|
||||||
|
timeout: Optional[str] = None,
|
||||||
|
) -> List[str]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ContainerInterface(ABC):
|
class ContainerInterface(ABC):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|
Loading…
Reference in a new issue