nucliadb 6.2.1.post2954__py3-none-any.whl → 6.2.1.post2972__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. nucliadb/common/cluster/manager.py +33 -331
  2. nucliadb/common/cluster/rebalance.py +2 -2
  3. nucliadb/common/cluster/rollover.py +12 -71
  4. nucliadb/common/cluster/standalone/utils.py +0 -43
  5. nucliadb/common/cluster/utils.py +0 -16
  6. nucliadb/common/nidx.py +21 -23
  7. nucliadb/health.py +0 -7
  8. nucliadb/ingest/app.py +0 -8
  9. nucliadb/ingest/consumer/auditing.py +1 -1
  10. nucliadb/ingest/consumer/shard_creator.py +1 -1
  11. nucliadb/ingest/orm/entities.py +3 -6
  12. nucliadb/purge/orphan_shards.py +6 -4
  13. nucliadb/search/api/v1/knowledgebox.py +1 -5
  14. nucliadb/search/predict.py +4 -4
  15. nucliadb/search/requesters/utils.py +1 -2
  16. nucliadb/search/search/chat/ask.py +18 -11
  17. nucliadb/search/search/chat/query.py +1 -1
  18. nucliadb/search/search/shards.py +19 -0
  19. nucliadb/standalone/introspect.py +0 -25
  20. nucliadb/train/lifecycle.py +0 -6
  21. nucliadb/train/nodes.py +1 -5
  22. nucliadb/writer/back_pressure.py +17 -46
  23. nucliadb/writer/settings.py +2 -2
  24. {nucliadb-6.2.1.post2954.dist-info → nucliadb-6.2.1.post2972.dist-info}/METADATA +5 -7
  25. {nucliadb-6.2.1.post2954.dist-info → nucliadb-6.2.1.post2972.dist-info}/RECORD +29 -39
  26. nucliadb/common/cluster/discovery/__init__.py +0 -19
  27. nucliadb/common/cluster/discovery/base.py +0 -178
  28. nucliadb/common/cluster/discovery/k8s.py +0 -301
  29. nucliadb/common/cluster/discovery/manual.py +0 -57
  30. nucliadb/common/cluster/discovery/single.py +0 -51
  31. nucliadb/common/cluster/discovery/types.py +0 -32
  32. nucliadb/common/cluster/discovery/utils.py +0 -67
  33. nucliadb/common/cluster/standalone/grpc_node_binding.py +0 -349
  34. nucliadb/common/cluster/standalone/index_node.py +0 -123
  35. nucliadb/common/cluster/standalone/service.py +0 -84
  36. {nucliadb-6.2.1.post2954.dist-info → nucliadb-6.2.1.post2972.dist-info}/WHEEL +0 -0
  37. {nucliadb-6.2.1.post2954.dist-info → nucliadb-6.2.1.post2972.dist-info}/entry_points.txt +0 -0
  38. {nucliadb-6.2.1.post2954.dist-info → nucliadb-6.2.1.post2972.dist-info}/top_level.txt +0 -0
  39. {nucliadb-6.2.1.post2954.dist-info → nucliadb-6.2.1.post2972.dist-info}/zip-safe +0 -0
@@ -1,349 +0,0 @@
1
- # Copyright (C) 2021 Bosutech XXI S.L.
2
- #
3
- # nucliadb is offered under the AGPL v3.0 and as commercial software.
4
- # For commercial licensing, contact us at info@nuclia.com.
5
- #
6
- # AGPL:
7
- # This program is free software: you can redistribute it and/or modify
8
- # it under the terms of the GNU Affero General Public License as
9
- # published by the Free Software Foundation, either version 3 of the
10
- # License, or (at your option) any later version.
11
- #
12
- # This program is distributed in the hope that it will be useful,
13
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
- # GNU Affero General Public License for more details.
16
- #
17
- # You should have received a copy of the GNU Affero General Public License
18
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
- #
20
- from __future__ import annotations
21
-
22
- import asyncio
23
- import logging
24
- import os
25
- import threading
26
- from concurrent.futures import ThreadPoolExecutor
27
- from typing import AsyncIterator
28
-
29
- from nucliadb_protos.nodereader_pb2 import (
30
- DocumentItem,
31
- EdgeList,
32
- GetShardRequest,
33
- IdCollection,
34
- ParagraphItem,
35
- ParagraphSearchRequest,
36
- ParagraphSearchResponse,
37
- RelationSearchRequest,
38
- RelationSearchResponse,
39
- SearchRequest,
40
- SearchResponse,
41
- StreamRequest,
42
- SuggestRequest,
43
- SuggestResponse,
44
- )
45
- from nucliadb_protos.noderesources_pb2 import (
46
- EmptyQuery,
47
- EmptyResponse,
48
- Resource,
49
- ResourceID,
50
- ShardCreated,
51
- ShardId,
52
- ShardIds,
53
- VectorSetID,
54
- VectorSetList,
55
- )
56
- from nucliadb_protos.noderesources_pb2 import Shard as NodeResourcesShard
57
- from nucliadb_protos.nodewriter_pb2 import NewShardRequest, OpStatus
58
-
59
- from ..settings import settings
60
-
61
- logger = logging.getLogger(__name__)
62
-
63
- try:
64
- from nucliadb_node_binding import IndexNodeException # type: ignore
65
- except ImportError: # pragma: no cover
66
- logger.warning("Import error while importing IndexNodeException")
67
- IndexNodeException = Exception
68
-
69
- try:
70
- from nucliadb_node_binding import NodeReader, NodeWriter
71
- except ImportError: # pragma: no cover
72
- NodeReader = None
73
- NodeWriter = None
74
-
75
-
76
- class StandaloneReaderWrapper:
77
- reader: NodeReader
78
-
79
- def __init__(self):
80
- if NodeReader is None:
81
- raise ImportError("NucliaDB index node bindings are not installed (reader not found)")
82
- self.reader = NodeReader()
83
- self.executor = ThreadPoolExecutor(settings.local_reader_threads)
84
-
85
- async def Search(self, request: SearchRequest, retry: bool = False) -> SearchResponse:
86
- try:
87
- loop = asyncio.get_running_loop()
88
- result = await loop.run_in_executor(
89
- self.executor, self.reader.search, request.SerializeToString()
90
- )
91
- pb_bytes = bytes(result)
92
- pb = SearchResponse()
93
- pb.ParseFromString(pb_bytes)
94
- return pb
95
- except IndexNodeException as exc:
96
- if "IO error" not in str(exc):
97
- # ignore any other error
98
- raise
99
-
100
- # try some mitigations...
101
- logger.error(f"IndexNodeException in Search: {request}", exc_info=True)
102
- if not retry:
103
- # reinit?
104
- self.reader = NodeReader()
105
- return await self.Search(request, retry=True)
106
- else:
107
- raise
108
-
109
- async def GetShard(self, request: GetShardRequest) -> NodeResourcesShard:
110
- loop = asyncio.get_running_loop()
111
- result = await loop.run_in_executor(
112
- self.executor, self.reader.get_shard, request.SerializeToString()
113
- )
114
- pb_bytes = bytes(result)
115
- shard = NodeResourcesShard()
116
- shard.ParseFromString(pb_bytes)
117
- return shard
118
-
119
- async def Suggest(self, request: SuggestRequest) -> SuggestResponse:
120
- loop = asyncio.get_running_loop()
121
- result = await loop.run_in_executor(
122
- self.executor, self.reader.suggest, request.SerializeToString()
123
- )
124
- pb_bytes = bytes(result)
125
- pb = SuggestResponse()
126
- pb.ParseFromString(pb_bytes)
127
- return pb
128
-
129
- async def Documents(
130
- self, stream_request: StreamRequest
131
- ) -> AsyncIterator[DocumentItem]: # pragma: no cover
132
- """
133
- This is a workaround for the fact that the node binding does not support async generators.
134
-
135
- Very difficult to write tests for
136
- """
137
- loop = asyncio.get_running_loop()
138
- q: asyncio.Queue[DocumentItem] = asyncio.Queue(1)
139
- exception = None
140
- _END = object()
141
-
142
- def thread_generator():
143
- nonlocal exception
144
- generator = self.reader.documents(stream_request.SerializeToString())
145
- try:
146
- element = generator.next()
147
- while element is not None:
148
- pb_bytes = bytes(element)
149
- pb = DocumentItem()
150
- pb.ParseFromString(pb_bytes)
151
- asyncio.run_coroutine_threadsafe(q.put(pb), loop).result()
152
- element = generator.next()
153
- except StopIteration:
154
- # this is the end
155
- pass
156
- except Exception as e:
157
- exception = e
158
- finally:
159
- asyncio.run_coroutine_threadsafe(q.put(_END), loop).result()
160
-
161
- t1 = threading.Thread(target=thread_generator)
162
- t1.start()
163
-
164
- while True:
165
- next_item = await q.get()
166
- if next_item is _END:
167
- break
168
- yield next_item
169
- if exception is not None:
170
- raise exception
171
- await loop.run_in_executor(self.executor, t1.join)
172
-
173
- async def Paragraphs(self, stream_request: StreamRequest) -> AsyncIterator[ParagraphItem]:
174
- loop = asyncio.get_running_loop()
175
- q: asyncio.Queue[ParagraphItem] = asyncio.Queue(1)
176
- exception = None
177
- _END = object()
178
-
179
- def thread_generator():
180
- nonlocal exception
181
- generator = self.reader.paragraphs(stream_request.SerializeToString())
182
- try:
183
- element = generator.next()
184
- while element is not None:
185
- pb_bytes = bytes(element)
186
- pb = ParagraphItem()
187
- pb.ParseFromString(pb_bytes)
188
- asyncio.run_coroutine_threadsafe(q.put(pb), loop).result()
189
- element = generator.next()
190
- except StopIteration:
191
- # this is the end
192
- pass
193
- except Exception as e:
194
- exception = e
195
- finally:
196
- asyncio.run_coroutine_threadsafe(q.put(_END), loop).result()
197
-
198
- t1 = threading.Thread(target=thread_generator)
199
- t1.start()
200
- while True:
201
- next_item = await q.get()
202
- if next_item is _END:
203
- break
204
- yield next_item
205
- if exception is not None:
206
- raise exception
207
- await loop.run_in_executor(self.executor, t1.join)
208
-
209
- async def RelationEdges(self, request: ShardId):
210
- loop = asyncio.get_running_loop()
211
- result = await loop.run_in_executor(
212
- self.executor, self.reader.relation_edges, request.SerializeToString()
213
- )
214
- pb_bytes = bytes(result)
215
- edge_list = EdgeList()
216
- edge_list.ParseFromString(pb_bytes)
217
- return edge_list
218
-
219
- async def VectorIds(self, request: VectorSetID) -> IdCollection:
220
- loop = asyncio.get_running_loop()
221
- result = await loop.run_in_executor(
222
- self.executor, self.reader.vector_ids, request.SerializeToString()
223
- )
224
- pb_bytes = bytes(result)
225
- ids = IdCollection()
226
- ids.ParseFromString(pb_bytes)
227
- return ids
228
-
229
-
230
- class StandaloneWriterWrapper:
231
- writer: NodeWriter
232
-
233
- def __init__(self):
234
- os.makedirs(settings.data_path, exist_ok=True)
235
- if NodeWriter is None:
236
- raise ImportError("NucliaDB index node bindings are not installed (writer not found)")
237
- self.writer = NodeWriter()
238
- self.executor = ThreadPoolExecutor(settings.local_writer_threads)
239
-
240
- async def NewShard(self, request: NewShardRequest) -> ShardCreated:
241
- loop = asyncio.get_running_loop()
242
- resp = await loop.run_in_executor(
243
- self.executor, self.writer.new_shard, request.SerializeToString()
244
- )
245
- pb_bytes = bytes(resp)
246
- shard_created = ShardCreated()
247
- shard_created.ParseFromString(pb_bytes)
248
- return shard_created
249
-
250
- async def DeleteShard(self, request: ShardId) -> ShardId:
251
- loop = asyncio.get_running_loop()
252
- resp = await loop.run_in_executor(
253
- self.executor, self.writer.delete_shard, request.SerializeToString()
254
- )
255
- pb_bytes = bytes(resp)
256
- shard_id = ShardId()
257
- shard_id.ParseFromString(pb_bytes)
258
- return shard_id
259
-
260
- async def ListShards(self, request: EmptyQuery) -> ShardIds:
261
- loop = asyncio.get_running_loop()
262
- resp = await loop.run_in_executor(
263
- self.executor,
264
- self.writer.list_shards,
265
- )
266
- pb_bytes = bytes(resp)
267
- shard_ids = ShardIds()
268
- shard_ids.ParseFromString(pb_bytes)
269
- return shard_ids
270
-
271
- async def AddVectorSet(self, request: VectorSetID):
272
- loop = asyncio.get_running_loop()
273
- resp = await loop.run_in_executor(
274
- self.executor, self.writer.add_vectorset, request.SerializeToString()
275
- )
276
- pb_bytes = bytes(resp)
277
- resp = OpStatus()
278
- resp.ParseFromString(pb_bytes)
279
- return resp
280
-
281
- async def ListVectorSets(self, request: ShardId):
282
- loop = asyncio.get_running_loop()
283
- resp = await loop.run_in_executor(
284
- self.executor, self.writer.list_vectorsets, request.SerializeToString()
285
- )
286
- pb_bytes = bytes(resp)
287
- resp = VectorSetList()
288
- resp.ParseFromString(pb_bytes)
289
- return resp
290
-
291
- async def RemoveVectorSet(self, request: VectorSetID):
292
- loop = asyncio.get_running_loop()
293
- resp = await loop.run_in_executor(
294
- self.executor, self.writer.remove_vectorset, request.SerializeToString()
295
- )
296
- pb_bytes = bytes(resp)
297
- resp = OpStatus()
298
- resp.ParseFromString(pb_bytes)
299
- return resp
300
-
301
- async def SetResource(self, request: Resource) -> OpStatus:
302
- loop = asyncio.get_running_loop()
303
- resp = await loop.run_in_executor(
304
- self.executor, self.writer.set_resource, request.SerializeToString()
305
- )
306
- pb_bytes = bytes(resp)
307
- op_status = OpStatus()
308
- op_status.ParseFromString(pb_bytes)
309
- return op_status
310
-
311
- async def RemoveResource(self, request: ResourceID) -> OpStatus:
312
- loop = asyncio.get_running_loop()
313
- resp = await loop.run_in_executor(
314
- self.executor, self.writer.remove_resource, request.SerializeToString()
315
- )
316
- pb_bytes = bytes(resp)
317
- op_status = OpStatus()
318
- op_status.ParseFromString(pb_bytes)
319
- return op_status
320
-
321
- async def GC(self, request: ShardId) -> EmptyResponse:
322
- loop = asyncio.get_running_loop()
323
- resp = await loop.run_in_executor(self.executor, self.writer.gc, request.SerializeToString())
324
- pb_bytes = bytes(resp)
325
- op_status = EmptyResponse()
326
- op_status.ParseFromString(pb_bytes)
327
- return op_status
328
-
329
-
330
- # supported marshalled reader methods for standalone node support
331
- READER_METHODS = {
332
- "Search": (SearchRequest, SearchResponse),
333
- "ParagraphSearch": (ParagraphSearchRequest, ParagraphSearchResponse),
334
- "RelationSearch": (RelationSearchRequest, RelationSearchResponse),
335
- "GetShard": (GetShardRequest, NodeResourcesShard),
336
- "Suggest": (SuggestRequest, SuggestResponse),
337
- "RelationEdges": (ShardId, EdgeList),
338
- }
339
- WRITER_METHODS = {
340
- "NewShard": (NewShardRequest, ShardCreated),
341
- "DeleteShard": (ShardId, ShardId),
342
- "ListShards": (EmptyQuery, ShardIds),
343
- "RemoveVectorSet": (VectorSetID, OpStatus),
344
- "AddVectorSet": (VectorSetID, OpStatus),
345
- "ListVectorSets": (ShardId, VectorSetList),
346
- "SetResource": (Resource, OpStatus),
347
- "RemoveResource": (ResourceID, OpStatus),
348
- "GC": (ShardId, EmptyResponse),
349
- }
@@ -1,123 +0,0 @@
1
- # Copyright (C) 2021 Bosutech XXI S.L.
2
- #
3
- # nucliadb is offered under the AGPL v3.0 and as commercial software.
4
- # For commercial licensing, contact us at info@nuclia.com.
5
- #
6
- # AGPL:
7
- # This program is free software: you can redistribute it and/or modify
8
- # it under the terms of the GNU Affero General Public License as
9
- # published by the Free Software Foundation, either version 3 of the
10
- # License, or (at your option) any later version.
11
- #
12
- # This program is distributed in the hope that it will be useful,
13
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
- # GNU Affero General Public License for more details.
16
- #
17
- # You should have received a copy of the GNU Affero General Public License
18
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
- #
20
- from typing import Any, Optional
21
-
22
- from nucliadb.common.cluster.base import AbstractIndexNode
23
- from nucliadb.common.cluster.grpc_node_dummy import DummyReaderStub, DummyWriterStub
24
- from nucliadb.common.cluster.settings import settings as cluster_settings
25
- from nucliadb.common.cluster.standalone import grpc_node_binding
26
- from nucliadb_protos import standalone_pb2, standalone_pb2_grpc
27
- from nucliadb_utils.grpc import get_traced_grpc_channel
28
-
29
-
30
- class StandaloneIndexNode(AbstractIndexNode):
31
- _writer: grpc_node_binding.StandaloneWriterWrapper
32
- _reader: grpc_node_binding.StandaloneReaderWrapper
33
- label: str = "standalone"
34
-
35
- def __init__(
36
- self,
37
- id: str,
38
- address: str,
39
- shard_count: int,
40
- available_disk: int,
41
- dummy: bool = False,
42
- primary_id: Optional[str] = None,
43
- ):
44
- super().__init__(
45
- id=id,
46
- address=address,
47
- shard_count=shard_count,
48
- available_disk=available_disk,
49
- dummy=dummy,
50
- # standalone does not support read replicas
51
- primary_id=None,
52
- )
53
- if dummy:
54
- self._writer = DummyWriterStub() # type: ignore
55
- self._reader = DummyReaderStub() # type: ignore
56
- else:
57
- self._writer = grpc_node_binding.StandaloneWriterWrapper()
58
- self._reader = grpc_node_binding.StandaloneReaderWrapper()
59
-
60
- @property
61
- def reader(self) -> grpc_node_binding.StandaloneReaderWrapper: # type: ignore
62
- return self._reader
63
-
64
- @property
65
- def writer(self) -> grpc_node_binding.StandaloneWriterWrapper: # type: ignore
66
- return self._writer
67
-
68
-
69
- class ProxyCallerWrapper:
70
- def __init__(self, address: str, type: str, original_type: Any):
71
- self._address = address
72
- self._type = type
73
- self._original_type = original_type
74
- if ":" not in address:
75
- grpc_address = f"{address}:{cluster_settings.standalone_node_port}"
76
- else:
77
- grpc_address = address
78
- self._channel = get_traced_grpc_channel(grpc_address, "standalone_proxy")
79
- self._stub = standalone_pb2_grpc.StandaloneClusterServiceStub(self._channel)
80
-
81
- def __getattr__(self, name):
82
- async def call(request):
83
- req = standalone_pb2.NodeActionRequest(
84
- service=self._type, action=name, payload=request.SerializeToString()
85
- )
86
- resp = await self._stub.NodeAction(req)
87
- try:
88
- if self._type == "reader":
89
- _, return_type = grpc_node_binding.READER_METHODS[name]
90
- elif self._type == "writer":
91
- _, return_type = grpc_node_binding.WRITER_METHODS[name]
92
- else:
93
- raise NotImplementedError(f"Unknown type {self._type}")
94
- except KeyError:
95
- raise NotImplementedError(f"Unknown method for type {self._type}: {name}")
96
- return_value = return_type()
97
- return_value.ParseFromString(resp.payload)
98
- return return_value
99
-
100
- return call
101
-
102
-
103
- class ProxyStandaloneIndexNode(StandaloneIndexNode):
104
- label: str = "proxy_standalone"
105
-
106
- def __init__(
107
- self,
108
- id: str,
109
- address: str,
110
- shard_count: int,
111
- available_disk: int,
112
- dummy: bool = False,
113
- ):
114
- super().__init__(id, address, shard_count, available_disk=available_disk, dummy=dummy)
115
- if dummy:
116
- return
117
-
118
- self._writer = ProxyCallerWrapper( # type: ignore
119
- address, "writer", grpc_node_binding.StandaloneWriterWrapper
120
- )
121
- self._reader = ProxyCallerWrapper( # type: ignore
122
- address, "reader", grpc_node_binding.StandaloneReaderWrapper
123
- )
@@ -1,84 +0,0 @@
1
- # Copyright (C) 2021 Bosutech XXI S.L.
2
- #
3
- # nucliadb is offered under the AGPL v3.0 and as commercial software.
4
- # For commercial licensing, contact us at info@nuclia.com.
5
- #
6
- # AGPL:
7
- # This program is free software: you can redistribute it and/or modify
8
- # it under the terms of the GNU Affero General Public License as
9
- # published by the Free Software Foundation, either version 3 of the
10
- # License, or (at your option) any later version.
11
- #
12
- # This program is distributed in the hope that it will be useful,
13
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
- # GNU Affero General Public License for more details.
16
- #
17
- # You should have received a copy of the GNU Affero General Public License
18
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
- #
20
- import os
21
- import shutil
22
-
23
- import backoff
24
- from grpc import aio
25
- from grpc.aio import AioRpcError
26
-
27
- from nucliadb.common.cluster.settings import settings
28
- from nucliadb.common.cluster.settings import settings as cluster_settings
29
- from nucliadb.common.cluster.standalone import grpc_node_binding
30
- from nucliadb.common.cluster.standalone.utils import get_self
31
- from nucliadb_protos import standalone_pb2, standalone_pb2_grpc
32
- from nucliadb_utils.grpc import get_traced_grpc_server
33
-
34
-
35
- class StandaloneClusterServiceServicer(standalone_pb2_grpc.StandaloneClusterServiceServicer):
36
- @backoff.on_exception(backoff.expo, (AioRpcError,), max_time=60)
37
- async def NodeAction( # type: ignore
38
- self, request: standalone_pb2.NodeActionRequest, context
39
- ) -> standalone_pb2.NodeActionResponse:
40
- service = request.service
41
- action = request.action
42
- try:
43
- if service == "reader":
44
- request_type, _ = grpc_node_binding.READER_METHODS[action]
45
- elif service == "writer":
46
- request_type, _ = grpc_node_binding.WRITER_METHODS[action]
47
- else:
48
- raise NotImplementedError(f"Unknown type {service}")
49
- except KeyError:
50
- raise NotImplementedError(f"Unknown method for type {service}: {action}")
51
-
52
- index_node_action = getattr(getattr(get_self(), service), action)
53
- action_request = request_type()
54
- action_request.ParseFromString(request.payload)
55
- response = await index_node_action(action_request)
56
- return standalone_pb2.NodeActionResponse(payload=response.SerializeToString())
57
-
58
- async def NodeInfo( # type: ignore
59
- self, request: standalone_pb2.NodeInfoRequest, context
60
- ) -> standalone_pb2.NodeInfoResponse:
61
- index_node = get_self()
62
- index_node.shard_count = len(os.listdir(os.path.join(cluster_settings.data_path, "shards")))
63
- total_disk, _, available_disk = shutil.disk_usage(cluster_settings.data_path)
64
- return standalone_pb2.NodeInfoResponse(
65
- id=index_node.id,
66
- address=index_node.address,
67
- shard_count=index_node.shard_count,
68
- available_disk=available_disk,
69
- total_disk=total_disk,
70
- )
71
-
72
-
73
- async def start_grpc():
74
- aio.init_grpc_aio()
75
-
76
- server = get_traced_grpc_server("standalone")
77
-
78
- servicer = StandaloneClusterServiceServicer()
79
- server.add_insecure_port(f"0.0.0.0:{settings.standalone_node_port}")
80
- standalone_pb2_grpc.add_StandaloneClusterServiceServicer_to_server(servicer, server)
81
-
82
- await server.start()
83
-
84
- return server