Skip to content

Commit 1d43db6

Browse files
authored
tests: support testenv setup in non-anonymous mode (#15130)
Add support for fully authenticated setup operations to `tests/library/`. Before that library could only execute cluster setup, configuration and database manipulation in anonymous mode or in the mode when every user is a cluster admin. Now `tests/library/` can operate when `administration_allowed_lists` is not empty and `enforce_user_token_requirement=True`.
1 parent 6abead2 commit 1d43db6

File tree

6 files changed

+121
-44
lines changed

6 files changed

+121
-44
lines changed

ydb/tests/library/clients/kikimr_client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,10 @@ def console_request(self, request_text, raise_on_error=True):
254254
raise RuntimeError('console_request failed: %s: %s' % (response.Status.Code, response.Status.Reason))
255255
return response
256256

257-
def add_config_item(self, config, cookie=None, raise_on_error=True):
257+
def add_config_item(self, config, cookie=None, raise_on_error=True, token=None):
258258
request = msgbus.TConsoleRequest()
259+
if token is not None:
260+
request.SecurityToken = token
259261
action = request.ConfigureRequest.Actions.add()
260262
item = action.AddConfigItem.ConfigItem
261263
if isinstance(config, str) or isinstance(config, bytes):

ydb/tests/library/common/protobuf_console.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# -*- coding: utf-8 -*-
33
import ydb.core.protos.msgbus_pb2 as msgbus
44

5-
from ydb.tests.library.common.protobuf import AbstractProtobufBuilder
5+
from ydb.tests.library.common.protobuf import AbstractProtobufBuilder, to_bytes
66

77

88
class CreateTenantRequest(AbstractProtobufBuilder):
@@ -14,6 +14,10 @@ def __init__(self, path):
1414
super(CreateTenantRequest, self).__init__(msgbus.TConsoleRequest())
1515
self.protobuf.CreateTenantRequest.Request.path = path
1616

17+
def set_user_token(self, token):
18+
self.protobuf.SecurityToken = to_bytes(token)
19+
self.protobuf.CreateTenantRequest.UserToken = to_bytes(token)
20+
1721
def add_storage_pool(self, pool_type, pool_size):
1822
pool = self.protobuf.CreateTenantRequest.Request.resources.storage_units.add()
1923
pool.unit_kind = pool_type
@@ -78,6 +82,10 @@ def __init__(self, path):
7882
super(AlterTenantRequest, self).__init__(msgbus.TConsoleRequest())
7983
self.protobuf.AlterTenantRequest.Request.path = path
8084

85+
def set_user_token(self, token):
86+
self.protobuf.SecurityToken = to_bytes(token)
87+
self.protobuf.AlterTenantRequest.UserToken = to_bytes(token)
88+
8189
def set_schema_quotas(self, schema_quotas):
8290
quotas = self.protobuf.AlterTenantRequest.Request.schema_operation_quotas
8391
quotas.SetInParent()
@@ -106,6 +114,10 @@ def __init__(self, path):
106114
super(GetTenantStatusRequest, self).__init__(msgbus.TConsoleRequest())
107115
self.protobuf.GetTenantStatusRequest.Request.path = path
108116

117+
def set_user_token(self, token):
118+
self.protobuf.SecurityToken = to_bytes(token)
119+
self.protobuf.GetTenantStatusRequest.UserToken = to_bytes(token)
120+
109121

110122
class RemoveTenantRequest(AbstractProtobufBuilder):
111123
"""
@@ -116,6 +128,10 @@ def __init__(self, path):
116128
super(RemoveTenantRequest, self).__init__(msgbus.TConsoleRequest())
117129
self.protobuf.RemoveTenantRequest.Request.path = path
118130

131+
def set_user_token(self, token):
132+
self.protobuf.SecurityToken = to_bytes(token)
133+
self.protobuf.RemoveTenantRequest.UserToken = to_bytes(token)
134+
119135

120136
class SetConfigRequest(AbstractProtobufBuilder):
121137
"""
@@ -158,3 +174,6 @@ class GetOperationRequest(AbstractProtobufBuilder):
158174
def __init__(self, op_id):
159175
super(GetOperationRequest, self).__init__(msgbus.TConsoleRequest())
160176
self.protobuf.GetOperationRequest.id = op_id
177+
178+
def set_user_token(self, token):
179+
self.protobuf.SecurityToken = to_bytes(token)

ydb/tests/library/harness/kikimr_cluster_interface.py

Lines changed: 69 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -110,35 +110,41 @@ def scheme_client(self):
110110
)
111111
return self.__scheme_client
112112

113-
def get_database_status(self, database_name):
114-
response = self.client.send_request(
115-
GetTenantStatusRequest(database_name).protobuf,
116-
method='ConsoleRequest'
117-
).GetTenantStatusResponse
113+
def _send_get_tenant_status_request(self, database_name, token=None):
114+
req = GetTenantStatusRequest(database_name)
115+
116+
if token is not None:
117+
req.set_user_token(token)
118+
119+
return self.client.send_request(req.protobuf, method='ConsoleRequest').GetTenantStatusResponse
120+
121+
def get_database_status(self, database_name, token=None):
122+
response = self._send_get_tenant_status_request(database_name, token=token)
118123

119124
if response.Response.operation.status != StatusIds.SUCCESS:
120125
logger.critical("Console response status: %s", str(response.Response.operation.status))
121126
assert False
122-
return False
123127

124128
result = cms_tenants_pb.GetDatabaseStatusResult()
125129
response.Response.operation.result.Unpack(result)
126130
return result
127131

128-
def wait_tenant_up(self, database_name):
132+
def wait_tenant_up(self, database_name, token=None):
129133
self.__wait_tenant_up(
130134
database_name,
131-
expected_computational_units=1
135+
expected_computational_units=1,
136+
token=token,
132137
)
133138

134139
def __wait_tenant_up(
135140
self,
136141
database_name,
137142
expected_computational_units=None,
138-
timeout_seconds=120
143+
timeout_seconds=120,
144+
token=None
139145
):
140146
def predicate():
141-
result = self.get_database_status(database_name)
147+
result = self.get_database_status(database_name, token=token)
142148

143149
if expected_computational_units is None:
144150
expected = set([2])
@@ -154,21 +160,25 @@ def predicate():
154160
)
155161
assert tenant_running
156162

157-
def __get_console_op(self, op_id):
163+
def __get_console_op(self, op_id, token=None):
158164
req = GetOperationRequest(op_id)
165+
166+
if token is not None:
167+
req.set_user_token(token)
168+
159169
response = self.client.send_request(req.protobuf, method='ConsoleRequest')
160170
operation = response.GetOperationResponse.operation
161171
if not operation.ready and response.Status.Code != StatusIds.STATUS_CODE_UNSPECIFIED:
162172
raise RuntimeError('get_console_op failed: %s: %s' % (response.Status.Code, response.Status.Reason))
163173
return operation
164174

165-
def __wait_console_op(self, op_id, timeout_seconds, step_seconds=0.5):
175+
def __wait_console_op(self, op_id, timeout_seconds, step_seconds=0.5, token=None):
166176
deadline = time.time() + timeout_seconds
167177
while True:
168178
time.sleep(step_seconds)
169179
if time.time() >= deadline:
170180
raise RuntimeError('wait_console_op: deadline exceeded')
171-
operation = self.__get_console_op(op_id)
181+
operation = self.__get_console_op(op_id, token=token)
172182
if operation.ready:
173183
return operation
174184

@@ -177,7 +187,8 @@ def create_database(
177187
database_name,
178188
storage_pool_units_count,
179189
disable_external_subdomain=False,
180-
timeout_seconds=120
190+
timeout_seconds=120,
191+
token=None,
181192
):
182193
req = CreateTenantRequest(database_name)
183194
for storage_pool_type_name, units_count in storage_pool_units_count.items():
@@ -189,27 +200,32 @@ def create_database(
189200
if disable_external_subdomain:
190201
req.disable_external_subdomain()
191202

203+
if token is not None:
204+
req.set_user_token(token)
205+
192206
response = self.client.send_request(req.protobuf, method='ConsoleRequest')
193207
operation = response.CreateTenantResponse.Response.operation
194208
if not operation.ready and response.Status.Code != StatusIds.STATUS_CODE_UNSPECIFIED:
195209
raise RuntimeError('create_database failed: %s: %s' % (response.Status.Code, response.Status.Reason))
196210
if not operation.ready:
197-
operation = self.__wait_console_op(operation.id, timeout_seconds=timeout_seconds)
211+
operation = self.__wait_console_op(operation.id, timeout_seconds=timeout_seconds, token=token)
198212
if operation.status != StatusIds.SUCCESS:
199213
raise RuntimeError('create_database failed: %s, %s' % (operation.status, ydb.issues._format_issues(operation.issues)))
200214

201215
self.__wait_tenant_up(
202216
database_name,
203217
expected_computational_units=0,
204-
timeout_seconds=timeout_seconds
218+
timeout_seconds=timeout_seconds,
219+
token=token,
205220
)
206221
return database_name
207222

208223
def create_hostel_database(
209224
self,
210225
database_name,
211226
storage_pool_units_count,
212-
timeout_seconds=120
227+
timeout_seconds=120,
228+
token=None,
213229
):
214230
req = CreateTenantRequest(database_name)
215231
for storage_pool_type_name, units_count in storage_pool_units_count.items():
@@ -218,19 +234,23 @@ def create_hostel_database(
218234
units_count,
219235
)
220236

237+
if token is not None:
238+
req.set_user_token(token)
239+
221240
response = self.client.send_request(req.protobuf, method='ConsoleRequest')
222241
operation = response.CreateTenantResponse.Response.operation
223242
if not operation.ready and response.Status.Code != StatusIds.STATUS_CODE_UNSPECIFIED:
224243
raise RuntimeError('create_hostel_database failed: %s: %s' % (response.Status.Code, response.Status.Reason))
225244
if not operation.ready:
226-
operation = self.__wait_console_op(operation.id, timeout_seconds=timeout_seconds)
245+
operation = self.__wait_console_op(operation.id, timeout_seconds=timeout_seconds, token=token)
227246
if operation.status != StatusIds.SUCCESS:
228247
raise RuntimeError('create_hostel_database failed: %s' % (operation.status,))
229248

230249
self.__wait_tenant_up(
231250
database_name,
232251
expected_computational_units=0,
233-
timeout_seconds=timeout_seconds
252+
timeout_seconds=timeout_seconds,
253+
token=token,
234254
)
235255
return database_name
236256

@@ -241,10 +261,14 @@ def create_serverless_database(
241261
timeout_seconds=120,
242262
schema_quotas=None,
243263
disk_quotas=None,
244-
attributes=None
264+
attributes=None,
265+
token=None,
245266
):
246267
req = CreateTenantRequest(database_name)
247268

269+
if token is not None:
270+
req.set_user_token(token)
271+
248272
req.share_resources_with(hostel_db)
249273

250274
if schema_quotas is not None:
@@ -262,13 +286,14 @@ def create_serverless_database(
262286
if not operation.ready and response.Status.Code != StatusIds.STATUS_CODE_UNSPECIFIED:
263287
raise RuntimeError('create_serverless_database failed: %s: %s' % (response.Status.Code, response.Status.Reason))
264288
if not operation.ready:
265-
operation = self.__wait_console_op(operation.id, timeout_seconds=timeout_seconds)
289+
operation = self.__wait_console_op(operation.id, timeout_seconds=timeout_seconds, token=token)
266290
if operation.status != StatusIds.SUCCESS:
267291
raise RuntimeError('create_serverless_database failed: %s' % (operation.status,))
268292

269293
self.__wait_tenant_up(
270294
database_name,
271-
timeout_seconds=timeout_seconds
295+
timeout_seconds=timeout_seconds,
296+
token=token,
272297
)
273298
return database_name
274299

@@ -278,9 +303,13 @@ def alter_serverless_database(
278303
schema_quotas=None,
279304
disk_quotas=None,
280305
timeout_seconds=120,
306+
token=None,
281307
):
282308
req = AlterTenantRequest(database_name)
283309

310+
if token is not None:
311+
req.set_user_token(token)
312+
284313
assert schema_quotas is not None or disk_quotas is not None
285314

286315
if schema_quotas is not None:
@@ -294,33 +323,39 @@ def alter_serverless_database(
294323
if not operation.ready and response.Status.Code != StatusIds.STATUS_CODE_UNSPECIFIED:
295324
raise RuntimeError('alter_serverless_database failed: %s: %s' % (response.Status.Code, response.Status.Reason))
296325
if not operation.ready:
297-
operation = self.__wait_console_op(operation.id, timeout_seconds=timeout_seconds)
326+
operation = self.__wait_console_op(operation.id, timeout_seconds=timeout_seconds, token=token)
298327
if operation.status != StatusIds.SUCCESS:
299328
raise RuntimeError('alter_serverless_database failed: %s' % (operation.status,))
300329

301330
self.__wait_tenant_up(
302331
database_name,
303-
timeout_seconds=timeout_seconds
332+
timeout_seconds=timeout_seconds,
333+
token=token,
304334
)
305335
return database_name
306336

307337
def remove_database(
308338
self,
309339
database_name,
310-
timeout_seconds=20
340+
timeout_seconds=20,
341+
token=None,
311342
):
312343
logger.debug(database_name)
313344

314-
operation_id = self._remove_database_send_op(database_name)
315-
self._remove_database_wait_op(database_name, operation_id, timeout_seconds=timeout_seconds)
316-
self._remove_database_wait_tenant_gone(database_name, timeout_seconds=timeout_seconds)
345+
operation_id = self._remove_database_send_op(database_name, token=token)
346+
self._remove_database_wait_op(database_name, operation_id, timeout_seconds=timeout_seconds, token=token)
347+
self._remove_database_wait_tenant_gone(database_name, timeout_seconds=timeout_seconds, token=token)
317348

318349
return database_name
319350

320-
def _remove_database_send_op(self, database_name):
321-
logger.debug('%s: send console operation', database_name)
351+
def _remove_database_send_op(self, database_name, token=None):
352+
logger.debug('%s: send console operation, token %s', database_name, token)
322353

323354
req = RemoveTenantRequest(database_name)
355+
356+
if token is not None:
357+
req.set_user_token(token)
358+
324359
response = self.client.send_request(req.protobuf, method='ConsoleRequest')
325360
operation = response.RemoveTenantResponse.Response.operation
326361
logger.debug('%s: response from console: %s', database_name, response)
@@ -330,20 +365,19 @@ def _remove_database_send_op(self, database_name):
330365

331366
return operation.id
332367

333-
def _remove_database_wait_op(self, database_name, operation_id, timeout_seconds=20):
368+
def _remove_database_wait_op(self, database_name, operation_id, timeout_seconds=20, token=None):
334369
logger.debug('%s: wait console operation done', database_name)
335-
operation = self.__wait_console_op(operation_id, timeout_seconds=timeout_seconds)
370+
operation = self.__wait_console_op(operation_id, timeout_seconds=timeout_seconds, token=token)
336371
logger.debug('%s: console operation done', database_name)
337372

338373
if operation.status not in (StatusIds.SUCCESS, StatusIds.NOT_FOUND):
339374
raise RuntimeError('remove_database failed: %s' % (operation.status,))
340375

341-
def _remove_database_wait_tenant_gone(self, database_name, timeout_seconds=20):
376+
def _remove_database_wait_tenant_gone(self, database_name, timeout_seconds=20, token=None):
342377
logger.debug('%s: wait tenant gone', database_name)
343378

344379
def predicate():
345-
response = self.client.send_request(
346-
GetTenantStatusRequest(database_name).protobuf, method='ConsoleRequest').GetTenantStatusResponse
380+
response = self._send_get_tenant_status_request(database_name, token=token)
347381
return response.Response.operation.status == StatusIds.NOT_FOUND
348382

349383
tenant_not_found = wait_for(

ydb/tests/library/harness/kikimr_config.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ def __init__(
164164
column_shard_config=None,
165165
use_config_store=False,
166166
separate_node_configs=False,
167+
default_clusteradmin=None,
168+
enable_resource_pools=None,
167169
):
168170
if extra_feature_flags is None:
169171
extra_feature_flags = []
@@ -270,6 +272,8 @@ def __init__(
270272

271273
# for faster shutdown: there is no reason to wait while tablets are drained before whole cluster is stopping
272274
self.yaml_config["feature_flags"]["enable_drain_on_shutdown"] = False
275+
if enable_resource_pools is not None:
276+
self.yaml_config["feature_flags"]["enable_resource_pools"] = enable_resource_pools
273277
for extra_feature_flag in extra_feature_flags:
274278
self.yaml_config["feature_flags"][extra_feature_flag] = True
275279
if enable_alter_database_create_hive_first:
@@ -457,6 +461,16 @@ def __init__(
457461
self.use_config_store = use_config_store
458462
self.separate_node_configs = separate_node_configs
459463

464+
self.__default_clusteradmin = default_clusteradmin
465+
if self.__default_clusteradmin is not None:
466+
security_config = self.yaml_config["domains_config"]["security_config"]
467+
security_config.setdefault("administration_allowed_sids", []).append(self.__default_clusteradmin)
468+
security_config.setdefault("default_access", []).append('+F:{}'.format(self.__default_clusteradmin))
469+
470+
@property
471+
def default_clusteradmin(self):
472+
return self.__default_clusteradmin
473+
460474
@property
461475
def pdisks_info(self):
462476
return self._pdisks_info

0 commit comments

Comments
 (0)