Skip to content

Commit 2e92a1c

Browse files
authored
Restore legacy kv server (#10132)
1 parent 791558d commit 2e92a1c

File tree

10 files changed

+276
-2
lines changed

10 files changed

+276
-2
lines changed

ydb/core/client/server/grpc_server.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,8 @@ void TGRpcService::SetupIncomingRequests() {
444444
// actor requests
445445
ADD_ACTOR_REQUEST(BlobStorageConfig, TBlobStorageConfigRequest, MTYPE_CLIENT_BLOB_STORAGE_CONFIG_REQUEST)
446446
ADD_ACTOR_REQUEST(HiveCreateTablet, THiveCreateTablet, MTYPE_CLIENT_HIVE_CREATE_TABLET)
447+
ADD_ACTOR_REQUEST(LocalEnumerateTablets, TLocalEnumerateTablets, MTYPE_CLIENT_LOCAL_ENUMERATE_TABLETS)
448+
ADD_ACTOR_REQUEST(KeyValue, TKeyValueRequest, MTYPE_CLIENT_KEYVALUE)
447449
ADD_ACTOR_REQUEST(TabletStateRequest, TTabletStateRequest, MTYPE_CLIENT_TABLET_STATE_REQUEST)
448450
ADD_ACTOR_REQUEST(LocalMKQL, TLocalMKQL, MTYPE_CLIENT_LOCAL_MINIKQL)
449451
ADD_ACTOR_REQUEST(LocalSchemeTx, TLocalSchemeTx, MTYPE_CLIENT_LOCAL_SCHEME_TX)

ydb/core/client/server/msgbus_server.cpp

+12
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ class TBusMessageContext::TImplGRpc
9595
MTYPE(TBusHiveCreateTablet)
9696
MTYPE(TBusOldHiveCreateTablet)
9797
MTYPE(TBusHiveCreateTabletResult)
98+
MTYPE(TBusLocalEnumerateTablets)
99+
MTYPE(TBusOldLocalEnumerateTablets)
100+
MTYPE(TBusLocalEnumerateTabletsResult)
101+
MTYPE(TBusKeyValue)
102+
MTYPE(TBusOldKeyValue)
103+
MTYPE(TBusKeyValueResponse)
98104
MTYPE(TBusPersQueue)
99105
MTYPE(TBusTabletKillRequest)
100106
MTYPE(TBusTabletStateRequest)
@@ -491,6 +497,12 @@ void TMessageBusServer::OnMessage(TBusMessageContext &msg) {
491497
case MTYPE_CLIENT_HIVE_CREATE_TABLET:
492498
case MTYPE_CLIENT_OLD_HIVE_CREATE_TABLET:
493499
return ClientActorRequest(CreateMessageBusHiveCreateTablet, msg);
500+
case MTYPE_CLIENT_LOCAL_ENUMERATE_TABLETS:
501+
case MTYPE_CLIENT_OLD_LOCAL_ENUMERATE_TABLETS:
502+
return ClientActorRequest(CreateMessageBusLocalEnumerateTablets, msg);
503+
case MTYPE_CLIENT_KEYVALUE:
504+
case MTYPE_CLIENT_OLD_KEYVALUE:
505+
return ClientActorRequest(CreateMessageBusKeyValue, msg);
494506
case MTYPE_CLIENT_PERSQUEUE:
495507
return ClientProxyRequest<TEvBusProxy::TEvPersQueue>(msg);
496508
case MTYPE_CLIENT_CHOOSE_PROXY:

ydb/core/client/server/msgbus_server.h

+2
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,8 @@ IActor* CreateMessageBusLocalSchemeTx(TBusMessageContext &msg);
277277
IActor* CreateMessageBusSchemeInitRoot(TBusMessageContext &msg);
278278
IActor* CreateMessageBusGetTypes(TBusMessageContext &msg);
279279
IActor* CreateMessageBusHiveCreateTablet(TBusMessageContext &msg);
280+
IActor* CreateMessageBusLocalEnumerateTablets(TBusMessageContext &msg);
281+
IActor* CreateMessageBusKeyValue(TBusMessageContext &msg);
280282
IActor* CreateMessageBusPersQueue(TBusMessageContext &msg);
281283
IActor* CreateMessageBusChooseProxy(TBusMessageContext &msg);
282284
IActor* CreateMessageBusTabletStateRequest(TBusMessageContext &msg);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#include "msgbus_tabletreq.h"
2+
#include <ydb/core/keyvalue/keyvalue_events.h>
3+
4+
namespace NKikimr {
5+
namespace NMsgBusProxy {
6+
7+
namespace {
8+
const ui32 DefaultTimeoutMs = 1000 * 90; // 90 seconds is a good default
9+
const ui64 MaxAllowedTimeoutMs = 1000 * 60 * 30; // 30 minutes is an instanely long request
10+
}
11+
12+
template <typename ResponseType>
13+
class TMessageBusKeyValue
14+
: public TMessageBusSimpleTabletRequest<TMessageBusKeyValue<ResponseType>, TEvKeyValue::TEvResponse, NKikimrServices::TActivity::FRONT_KV_REQUEST> {
15+
using TBase = TMessageBusSimpleTabletRequest<TMessageBusKeyValue<ResponseType>, TEvKeyValue::TEvResponse, NKikimrServices::TActivity::FRONT_KV_REQUEST>;
16+
public:
17+
NKikimrClient::TKeyValueRequest RequestProto;
18+
ui64 TabletId;
19+
20+
TMessageBusKeyValue(TBusMessageContext &msg, ui64 tabletId, bool withRetry, TDuration timeout)
21+
: TBase(msg, tabletId, withRetry, timeout, false)
22+
, RequestProto(static_cast<TBusKeyValue *>(msg.GetMessage())->Record)
23+
, TabletId(tabletId)
24+
{}
25+
26+
void Handle(TEvKeyValue::TEvResponse::TPtr &ev, const TActorContext &ctx) {
27+
TEvKeyValue::TEvResponse *msg = ev->Get();
28+
TAutoPtr<ResponseType> response(new ResponseType());
29+
CopyProtobufsByFieldName(response->Record, msg->Record);
30+
TBase::SendReplyAndDie(response.Release(), ctx);
31+
}
32+
33+
TEvKeyValue::TEvRequest* MakeReq(const TActorContext &ctx) {
34+
Y_UNUSED(ctx);
35+
THolder<TEvKeyValue::TEvRequest> request(new TEvKeyValue::TEvRequest());
36+
request->Record = RequestProto;
37+
return request.Release();
38+
}
39+
40+
NBus::TBusMessage* CreateErrorReply(EResponseStatus status, const TActorContext &ctx,
41+
const TString& text = TString()) override {
42+
LOG_WARN_S(ctx, NKikimrServices::MSGBUS_REQUEST, "TMessageBusKeyValue TabletId# " << TabletId
43+
<< " status# " << status << " text# \"" << text << "\" Marker# MBKV2" << Endl);
44+
45+
TAutoPtr<ResponseType> response(new ResponseType());
46+
response->Record.SetStatus(status);
47+
if (text) {
48+
response->Record.SetErrorReason(text);
49+
} else {
50+
TStringStream str;
51+
str << "TMessageBusKeyValue unknown error, TabletId# " << TabletId;
52+
str << " status# " << status;
53+
str << " Marker# MBKV1" << Endl;
54+
response->Record.SetErrorReason(str.Str());
55+
}
56+
return response.Release();
57+
}
58+
};
59+
60+
IActor* CreateMessageBusKeyValue(NKikimr::NMsgBusProxy::TBusMessageContext &msg) {
61+
auto &record = static_cast<TBusKeyValue *>(msg.GetMessage())->Record;
62+
63+
const ui64 tabletId = record.GetTabletId();
64+
const bool withRetry = true;
65+
TDuration timeout = TDuration::MilliSeconds(DefaultTimeoutMs);
66+
TInstant now(TInstant::Now());
67+
bool doSetDeadline = true;
68+
if (record.HasDeadlineInstantMs()) {
69+
ui64 nowMs = now.MilliSeconds();
70+
ui64 deadlineMs = record.GetDeadlineInstantMs();
71+
if (deadlineMs >= nowMs) {
72+
ui64 timeoutMs = deadlineMs - nowMs;
73+
if (timeoutMs > MaxAllowedTimeoutMs) {
74+
// overwrite the deadline since there is no sense in allowing requests longer than MaxAllowedTimeoutMs
75+
timeout = TDuration::MilliSeconds(MaxAllowedTimeoutMs);
76+
} else {
77+
doSetDeadline = false;
78+
timeout = TDuration::MilliSeconds(timeoutMs);
79+
}
80+
} else {
81+
doSetDeadline = false;
82+
timeout = TDuration::MilliSeconds(0);
83+
}
84+
}
85+
if (doSetDeadline) {
86+
TInstant deadlineInstant = now + timeout;
87+
ui64 deadlineInstantMs = deadlineInstant.MilliSeconds();
88+
record.SetDeadlineInstantMs(deadlineInstantMs);
89+
}
90+
if (msg.GetMessage()->GetHeader()->Type == MTYPE_CLIENT_OLD_KEYVALUE) {
91+
return new TMessageBusKeyValue<TBusKeyValueResponse>(msg, tabletId, withRetry, timeout);
92+
} else {
93+
return new TMessageBusKeyValue<TBusResponse>(msg, tabletId, withRetry, timeout);
94+
}
95+
}
96+
97+
}
98+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
#include "msgbus_servicereq.h"
2+
#include <ydb/core/mind/local.h>
3+
#include <ydb/core/protos/local.pb.h>
4+
namespace NKikimr {
5+
namespace NMsgBusProxy {
6+
7+
namespace {
8+
const ui32 DefaultTimeout = 90000;
9+
}
10+
11+
template <typename ResponseType>
12+
class TMessageBusLocalEnumerateTablets: public TMessageBusLocalServiceRequest<TMessageBusLocalEnumerateTablets<ResponseType>, NKikimrServices::TActivity::FRONT_ENUMERATE_TABLETS> {
13+
using TBase = TMessageBusLocalServiceRequest<TMessageBusLocalEnumerateTablets<ResponseType>, NKikimrServices::TActivity::FRONT_ENUMERATE_TABLETS>;
14+
ui64 DomainUid;
15+
ui32 NodeId;
16+
TTabletTypes::EType TabletType;
17+
bool IsFiltered;
18+
bool IsOk;
19+
bool IsNodeIdPresent;
20+
public:
21+
TMessageBusLocalEnumerateTablets(TBusMessageContext &msg, TDuration timeout)
22+
: TBase(msg, timeout)
23+
, DomainUid(0)
24+
, NodeId(0)
25+
, TabletType()
26+
, IsFiltered(false)
27+
, IsOk(true)
28+
, IsNodeIdPresent(false)
29+
{
30+
const auto &record = static_cast<TBusLocalEnumerateTablets*>(msg.GetMessage())->Record;
31+
IsOk = IsOk && record.HasDomainUid();
32+
if (record.HasNodeId()) {
33+
IsNodeIdPresent = true;
34+
NodeId = record.GetNodeId();
35+
}
36+
if (IsOk) {
37+
DomainUid = record.GetDomainUid();
38+
if (record.HasTabletType()) {
39+
IsFiltered = true;
40+
TabletType = record.GetTabletType();
41+
}
42+
}
43+
}
44+
45+
void Handle(TEvLocal::TEvEnumerateTabletsResult::TPtr &ev, const TActorContext &ctx) {
46+
const NKikimrLocal::TEvEnumerateTabletsResult &record = ev->Get()->Record;
47+
Y_ABORT_UNLESS(record.HasStatus());
48+
THolder<ResponseType> response(new ResponseType());
49+
if (record.GetStatus() != NKikimrProto::OK) {
50+
response->Record.SetStatus(MSTATUS_ERROR);
51+
response->Record.SetErrorReason(Sprintf("Local response is not OK (Status# %s), Marker# LE1",
52+
NKikimrProto::EReplyStatus_Name(record.GetStatus()).data()));
53+
TBase::SendReplyAndDie(response.Release(), ctx);
54+
} else {
55+
response->Record.SetStatus(MSTATUS_OK);
56+
for (ui32 i = 0; i < record.TabletInfoSize(); ++i) {
57+
auto &srcInfo = record.GetTabletInfo(i);
58+
auto *dstInfo = response->Record.AddTabletInfo();
59+
if (srcInfo.HasTabletId()) {
60+
dstInfo->SetTabletId(srcInfo.GetTabletId());
61+
}
62+
if (srcInfo.HasTabletType()) {
63+
dstInfo->SetTabletType(srcInfo.GetTabletType());
64+
}
65+
}
66+
TBase::SendReplyAndDie(response.Release(), ctx);
67+
}
68+
}
69+
70+
TActorId MakeServiceID(const TActorContext &ctx) {
71+
auto &domainsInfo = AppData(ctx)->DomainsInfo;
72+
if (!domainsInfo->Domain || domainsInfo->GetDomain()->DomainUid != DomainUid) {
73+
// Report details in CreateErrorReply
74+
TActorId invalidId;
75+
return invalidId;
76+
}
77+
ui32 nodeId = IsNodeIdPresent ? NodeId : ctx.SelfID.NodeId();
78+
ui64 hiveId = domainsInfo->GetHive();
79+
return MakeLocalRegistrarID(nodeId, hiveId);
80+
}
81+
82+
TEvLocal::TEvEnumerateTablets* MakeReq(const TActorContext &ctx) {
83+
Y_UNUSED(ctx);
84+
if (IsFiltered) {
85+
return new TEvLocal::TEvEnumerateTablets(TabletType);
86+
}
87+
return new TEvLocal::TEvEnumerateTablets();
88+
}
89+
90+
NBus::TBusMessage* CreateErrorReply(EResponseStatus status, const TActorContext &ctx) {
91+
Y_UNUSED(ctx);
92+
Y_UNUSED(status);
93+
ui64 nodeId = IsNodeIdPresent ? NodeId : ctx.SelfID.NodeId();
94+
THolder<ResponseType> response(new ResponseType());
95+
response->Record.SetStatus(MSTATUS_ERROR);
96+
response->Record.SetErrorReason(Sprintf("Invalid DomainUid# %" PRIu64 ", NodeId# %" PRIu64
97+
" or kikimr hive/domain/node configuration, Marker# LE3", (ui64)DomainUid, (ui64)nodeId));
98+
return response.Release();
99+
}
100+
101+
void HandleTimeout(const TActorContext &ctx) {
102+
Y_UNUSED(ctx);
103+
TAutoPtr<TBusResponse> response(new TBusResponseStatus(MSTATUS_TIMEOUT, ""));
104+
TBase::SendReplyAndDie(response.Release(), ctx);
105+
}
106+
107+
void HandleUndelivered(TEvents::TEvUndelivered::TPtr& ev, const TActorContext& ctx) {
108+
Y_UNUSED(ev);
109+
THolder<ResponseType> response(new ResponseType());
110+
ui64 nodeId = IsNodeIdPresent ? NodeId : ctx.SelfID.NodeId();
111+
response->Record.SetStatus(MSTATUS_ERROR);
112+
response->Record.SetErrorReason(Sprintf("Request was not delivered to Local, NodeId# %" PRIu64
113+
", Marker# LE2", (ui64)nodeId));
114+
TBase::SendReplyAndDie(response.Release(), ctx);
115+
116+
}
117+
118+
STFUNC(StateFunc) {
119+
switch (ev->GetTypeRewrite()) {
120+
HFunc(TEvLocal::TEvEnumerateTabletsResult, Handle);
121+
HFunc(TEvents::TEvUndelivered, HandleUndelivered);
122+
CFunc(TEvents::TSystem::Wakeup, HandleTimeout);
123+
}
124+
}
125+
};
126+
127+
IActor* CreateMessageBusLocalEnumerateTablets(TBusMessageContext &msg) {
128+
//const auto &record = static_cast<TBusLocalEnumerateTablets*>(msg.GetMessage())->Record;
129+
//const TDuration timeout = TDuration::MilliSeconds(record.HasTimeout() ? record.GetTimeout() : DefaultTimeout);
130+
const TDuration timeout = TDuration::MilliSeconds(DefaultTimeout);
131+
132+
if (msg.GetMessage()->GetHeader()->Type == MTYPE_CLIENT_OLD_LOCAL_ENUMERATE_TABLETS) {
133+
return new TMessageBusLocalEnumerateTablets<TBusLocalEnumerateTabletsResult>(msg, timeout);
134+
} else {
135+
return new TMessageBusLocalEnumerateTablets<TBusResponse>(msg, timeout);
136+
}
137+
}
138+
139+
}
140+
}

ydb/core/client/server/ya.make

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ SRCS(
1515
msgbus_server_drain_node.cpp
1616
msgbus_server_fill_node.cpp
1717
msgbus_server_hive_create_tablet.cpp
18+
msgbus_server_keyvalue.cpp
1819
msgbus_server_persqueue.cpp
1920
msgbus_server_persqueue.h
2021
msgbus_server_pq_metacache.h
@@ -24,6 +25,7 @@ SRCS(
2425
msgbus_server_pq_read_session_info.cpp
2526
msgbus_server_resolve_node.cpp
2627
msgbus_server_ic_debug.cpp
28+
msgbus_server_local_enumerate_tablets.cpp
2729
msgbus_server_local_minikql.cpp
2830
msgbus_server_local_scheme_tx.cpp
2931
msgbus_server_node_registration.cpp

ydb/core/protos/grpc.proto

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ service TGRpcServer {
5353
// KV-TABLET INTERNAL INTERFACE
5454
/////////////////////////////////////////////////////////////////////////////////////////////////
5555
rpc HiveCreateTablet(THiveCreateTablet) returns (TResponse);
56-
//rpc LocalEnumerateTablets(TLocalEnumerateTablets) returns (TResponse);
57-
//rpc KeyValue(TKeyValueRequest) returns (TResponse);
56+
rpc LocalEnumerateTablets(TLocalEnumerateTablets) returns (TResponse);
57+
rpc KeyValue(TKeyValueRequest) returns (TResponse);
5858
rpc TestShardControl(TTestShardControlRequest) returns (TResponse);
5959

6060
/////////////////////////////////////////////////////////////////////////////////////////////////

ydb/public/lib/base/msgbus.h

+12
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ struct TBusTypesResponse : TBusMessage<TBusTypesResponse, NKikimrClient::TTypeMe
103103
struct TBusHiveCreateTablet : TBusMessage<TBusHiveCreateTablet, NKikimrClient::THiveCreateTablet, MTYPE_CLIENT_HIVE_CREATE_TABLET> {};
104104
struct TBusOldHiveCreateTablet : TBusMessage<TBusOldHiveCreateTablet, NKikimrClient::THiveCreateTablet, MTYPE_CLIENT_OLD_HIVE_CREATE_TABLET> {};
105105
struct TBusHiveCreateTabletResult : TBusMessage<TBusHiveCreateTabletResult, NKikimrClient::THiveCreateTabletResult, MTYPE_CLIENT_HIVE_CREATE_TABLET_RESULT> {};
106+
struct TBusLocalEnumerateTablets : TBusMessage<TBusLocalEnumerateTablets, NKikimrClient::TLocalEnumerateTablets, MTYPE_CLIENT_LOCAL_ENUMERATE_TABLETS> {};
107+
struct TBusOldLocalEnumerateTablets : TBusMessage<TBusOldLocalEnumerateTablets, NKikimrClient::TLocalEnumerateTablets, MTYPE_CLIENT_OLD_LOCAL_ENUMERATE_TABLETS> {};
108+
struct TBusLocalEnumerateTabletsResult : TBusMessage<TBusLocalEnumerateTabletsResult, NKikimrClient::TLocalEnumerateTabletsResult, MTYPE_CLIENT_LOCAL_ENUMERATE_TABLETS_RESULT> {};
109+
struct TBusKeyValue : TBusMessage<TBusKeyValue, NKikimrClient::TKeyValueRequest, MTYPE_CLIENT_KEYVALUE> {};
110+
struct TBusOldKeyValue : TBusMessage<TBusOldKeyValue, NKikimrClient::TKeyValueRequest, MTYPE_CLIENT_OLD_KEYVALUE> {};
111+
struct TBusKeyValueResponse : TBusMessage<TBusKeyValueResponse, NKikimrClient::TKeyValueResponse, MTYPE_CLIENT_KEYVALUE_RESPONSE> {};
106112
struct TBusPersQueue : TBusMessage<TBusPersQueue, NKikimrClient::TPersQueueRequest, MTYPE_CLIENT_PERSQUEUE> {};
107113
struct TBusTabletKillRequest : TBusMessage<TBusTabletKillRequest, NKikimrClient::TTabletKillRequest, MTYPE_CLIENT_TABLET_KILL_REQUEST> {};
108114
struct TBusTabletStateRequest : TBusMessage<TBusTabletStateRequest, NKikimrClient::TTabletStateRequest, MTYPE_CLIENT_TABLET_STATE_REQUEST> {};
@@ -180,6 +186,12 @@ class TProtocol : public NBus::TBusBufferProtocol {
180186
RegisterType(new TBusHiveCreateTablet);
181187
RegisterType(new TBusOldHiveCreateTablet);
182188
RegisterType(new TBusHiveCreateTabletResult);
189+
RegisterType(new TBusLocalEnumerateTablets);
190+
RegisterType(new TBusOldLocalEnumerateTablets);
191+
RegisterType(new TBusLocalEnumerateTabletsResult);
192+
RegisterType(new TBusKeyValue);
193+
RegisterType(new TBusOldKeyValue);
194+
RegisterType(new TBusKeyValueResponse);
183195
RegisterType(new TBusPersQueue);
184196
RegisterType(new TBusTabletKillRequest);
185197
RegisterType(new TBusTabletStateRequest);

ydb/public/lib/deprecated/client/grpc_client.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,8 @@ namespace NKikimr {
326326
IMPL_REQUEST(ResolveNode, TResolveNodeRequest, TResponse)
327327
IMPL_REQUEST(HiveCreateTablet, THiveCreateTablet, TResponse)
328328
IMPL_REQUEST(RegisterNode, TNodeRegistrationRequest, TNodeRegistrationResponse)
329+
IMPL_REQUEST(LocalEnumerateTablets, TLocalEnumerateTablets, TResponse)
330+
IMPL_REQUEST(KeyValue, TKeyValueRequest, TResponse)
329331
IMPL_REQUEST(CmsRequest, TCmsRequest, TCmsResponse)
330332
IMPL_REQUEST(SqsRequest, TSqsRequest, TSqsResponse)
331333
IMPL_REQUEST(LocalMKQL, TLocalMKQL, TResponse)

ydb/public/lib/deprecated/kicli/kikimr.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,10 @@ class TKikimr::TGRpcImpl : public TKikimr::TImpl {
246246
return ExecuteGRpcRequest<NMsgBusProxy::TBusBlobStorageConfigRequest>(&NGRpcProxy::TGRpcClient::BlobStorageConfig, promise, request);
247247
case NMsgBusProxy::MTYPE_CLIENT_HIVE_CREATE_TABLET:
248248
return ExecuteGRpcRequest<NMsgBusProxy::TBusHiveCreateTablet>(&NGRpcProxy::TGRpcClient::HiveCreateTablet, promise, request);
249+
case NMsgBusProxy::MTYPE_CLIENT_LOCAL_ENUMERATE_TABLETS:
250+
return ExecuteGRpcRequest<NMsgBusProxy::TBusLocalEnumerateTablets>(&NGRpcProxy::TGRpcClient::LocalEnumerateTablets, promise, request);
251+
case NMsgBusProxy::MTYPE_CLIENT_KEYVALUE:
252+
return ExecuteGRpcRequest<NMsgBusProxy::TBusKeyValue>(&NGRpcProxy::TGRpcClient::KeyValue, promise, request);
249253
case NMsgBusProxy::MTYPE_CLIENT_LOCAL_MINIKQL:
250254
return ExecuteGRpcRequest<NMsgBusProxy::TBusTabletLocalMKQL>(&NGRpcProxy::TGRpcClient::LocalMKQL, promise, request);
251255
case NMsgBusProxy::MTYPE_CLIENT_LOCAL_SCHEME_TX:

0 commit comments

Comments
 (0)