python-rac 0.2.0__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.
@@ -0,0 +1,3453 @@
1
+ """Direct 8.5.1 RAC protocol over asyncio sockets.
2
+
3
+ Read and verified schedule/profile ACL write operations are implemented from
4
+ sanitized captures. Authentication and other mutations remain deliberately unsupported.
5
+ """
6
+ import asyncio
7
+ import io
8
+ import math
9
+ import struct
10
+ import uuid
11
+ # The 8.5.1 endpoint reuses these verified primitive and record layouts from
12
+ # the 1C 8.3.11 codec. Keep protocol-specific messages in this module.
13
+ from ..v8311 import (
14
+ date_from_int64, pack_varint_base128, read_bool, read_bytes, read_cluster,
15
+ read_double, read_infobase, read_infobase_short, read_int32, read_int64,
16
+ read_port_ranges, read_server, read_session, read_string, read_uint16,
17
+ read_uuid, unpack_varint_base128, unpack_varint_base64,
18
+ varint_prefixed_data_base64, write_bool, write_int32, write_int64,
19
+ write_uint16,
20
+ )
21
+ from .v851 import (CONNECT_ACK, ENDPOINT_OPEN_ACK, HELLO, SERVICE_NAME,
22
+ SERVICE_VERSION, decode_frame)
23
+
24
+
25
+ def frame(packet_type, body):
26
+ if type(packet_type) is not int or not 0 <= packet_type <= 255:
27
+ raise ValueError('Invalid frame type')
28
+ if not isinstance(body, bytes):
29
+ raise ValueError('Invalid frame body')
30
+ if len(body) > 4_000_000:
31
+ raise ValueError('Invalid frame length')
32
+ return bytes([packet_type]) + pack_varint_base128(len(body)) + body
33
+
34
+
35
+ class EndpointVersionRejected(ValueError):
36
+ """RAS explicitly rejected opening the 16.0 administration endpoint."""
37
+
38
+
39
+ class V851Client:
40
+ def __init__(self, host, port=1545, timeout=5.0):
41
+ self.host, self.port, self.timeout = host, port, timeout
42
+ self.reader = self.writer = None
43
+ self._request_lock = asyncio.Lock()
44
+ self._request_unusable = False
45
+ self._closing = False
46
+
47
+ async def __aenter__(self):
48
+ try:
49
+ async with asyncio.timeout(self.timeout):
50
+ return await self._open_endpoint()
51
+ except BaseException:
52
+ if self.writer is not None:
53
+ try:
54
+ self.writer.close()
55
+ except Exception:
56
+ # A failed close must not hide the handshake failure.
57
+ pass
58
+ try:
59
+ async with asyncio.timeout(self.timeout):
60
+ await self.writer.wait_closed()
61
+ except Exception:
62
+ # A failed cleanup must not hide the handshake failure.
63
+ pass
64
+ raise
65
+
66
+ async def _open_endpoint(self):
67
+ self.reader, self.writer = await asyncio.wait_for(asyncio.open_connection(self.host, self.port), self.timeout)
68
+ self.writer.write(HELLO + bytes.fromhex('0116010f636f6e6e6563742e74696d656f757404000007d0'))
69
+ await asyncio.wait_for(self.writer.drain(), self.timeout)
70
+ kind, body = await self.read_frame()
71
+ if (kind, body) != decode_frame(CONNECT_ACK):
72
+ raise ValueError('Invalid CONNECT_ACK')
73
+ self.writer.write(frame(11, bytes.fromhex('1876382e736572766963652e41646d696e2e436c75737465720431362e3080')))
74
+ await asyncio.wait_for(self.writer.drain(), self.timeout)
75
+ kind, body = await self.read_frame()
76
+ if kind == 15:
77
+ # Only a complete failure for the endpoint actually requested permits fallback.
78
+ # Never use remote failure text (which may contain private data) in exceptions.
79
+ try:
80
+ failure = io.BytesIO(body)
81
+ service = await read_bytes(failure)
82
+ version = await read_bytes(failure)
83
+ endpoint_id = await unpack_varint_base128(failure)
84
+ await read_bytes(failure) # opaque cause; never surface server text
85
+ await read_bytes(failure) # opaque message
86
+ if (service != SERVICE_NAME or version != SERVICE_VERSION
87
+ or endpoint_id != 1 or failure.read()):
88
+ raise ValueError('Invalid endpoint failure')
89
+ except (ValueError, UnicodeError) as exc:
90
+ raise ValueError('Invalid endpoint failure') from None
91
+ raise EndpointVersionRejected('RAS rejected the 16.0 administration endpoint')
92
+ if (kind, body) != decode_frame(ENDPOINT_OPEN_ACK):
93
+ raise ValueError('Invalid ENDPOINT_OPEN_ACK')
94
+ return self
95
+
96
+ async def __aexit__(self, exc_type, exc_value, traceback):
97
+ shutdown_errors = []
98
+ self._closing = True
99
+ try:
100
+ async with asyncio.timeout(self.timeout):
101
+ async with self._request_lock:
102
+ await self._close_within_deadline(exc_type, shutdown_errors)
103
+ except TimeoutError:
104
+ if self.writer is not None:
105
+ try:
106
+ self.writer.close()
107
+ except Exception:
108
+ # A failed forced close must not conceal a request or shutdown failure.
109
+ pass
110
+ if exc_type is None:
111
+ if shutdown_errors:
112
+ raise shutdown_errors[0] from None
113
+ raise
114
+ except asyncio.CancelledError:
115
+ if self.writer is not None:
116
+ try:
117
+ self.writer.close()
118
+ except Exception:
119
+ # Cancellation must not be replaced by a cleanup failure.
120
+ pass
121
+ raise
122
+
123
+ async def _close_within_deadline(self, exc_type, shutdown_errors):
124
+ if self.writer:
125
+ goodbye_error = None
126
+ try:
127
+ self.writer.write(bytes.fromhex('0d0101'))
128
+ await asyncio.wait_for(self.writer.drain(), self.timeout)
129
+ except Exception as error:
130
+ goodbye_error = error
131
+ shutdown_errors.append(error)
132
+ finally:
133
+ close_error = None
134
+ try:
135
+ self.writer.close()
136
+ except Exception as error:
137
+ close_error = error
138
+ try:
139
+ async with asyncio.timeout(self.timeout):
140
+ await self.writer.wait_closed()
141
+ except Exception:
142
+ if exc_type is None and goodbye_error is None and close_error is None:
143
+ raise
144
+ if exc_type is None and goodbye_error is None and close_error is not None:
145
+ raise close_error
146
+ if exc_type is None and goodbye_error is not None:
147
+ raise goodbye_error
148
+
149
+ async def read_frame(self):
150
+ async with asyncio.timeout(self.timeout):
151
+ return await self._read_frame_unbounded()
152
+
153
+ async def _read_frame_unbounded(self):
154
+ kind = (await self.reader.readexactly(1))[0]
155
+ length = 0
156
+ for shift in (0, 7, 14, 21):
157
+ byte = (await self.reader.readexactly(1))[0]
158
+ length |= (byte & 0x7f) << shift
159
+ if length > 4_000_000:
160
+ raise ValueError('Invalid frame length')
161
+ if not byte & 0x80:
162
+ if shift and byte == 0:
163
+ raise ValueError('Invalid frame length')
164
+ break
165
+ else:
166
+ raise ValueError('Invalid frame length')
167
+ body = await self.reader.readexactly(length)
168
+ return kind, body
169
+
170
+ async def request(self, message_type, value=b''):
171
+ if type(message_type) is not int or not 0 <= message_type <= 255:
172
+ raise ValueError('Invalid RAC message type')
173
+ if not isinstance(value, bytes):
174
+ raise ValueError('Invalid RAC request payload')
175
+ if len(value) > 4_000_000 - 5:
176
+ raise ValueError('Invalid frame length')
177
+ return await self._exchange(self._request_within_deadline, message_type, value)
178
+
179
+ async def _exchange(self, operation, *args):
180
+ if self._closing:
181
+ raise ConnectionError('RAC connection is closing')
182
+ if self._request_unusable:
183
+ raise ConnectionError('RAC connection unusable after interrupted request')
184
+ async with asyncio.timeout(self.timeout):
185
+ async with self._request_lock:
186
+ if self._closing:
187
+ raise ConnectionError('RAC connection is closing')
188
+ if self._request_unusable:
189
+ raise ConnectionError('RAC connection unusable after interrupted request')
190
+ try:
191
+ return await operation(*args)
192
+ except BaseException:
193
+ # The peer may still send this reply. Never assign it to a later request.
194
+ self._request_unusable = True
195
+ if self.writer is not None:
196
+ try:
197
+ self.writer.close()
198
+ except Exception:
199
+ pass
200
+ raise
201
+
202
+ async def _request_within_deadline(self, message_type, value):
203
+ self.writer.write(frame(14, b'\x01\x00\x00\x01' + bytes([message_type]) + value))
204
+ await asyncio.wait_for(self.writer.drain(), self.timeout)
205
+ kind, body = await self.read_frame()
206
+ if kind != 14 or len(body) < 5 or body[:4] != b'\x01\x00\x00\x01':
207
+ raise ValueError('Unexpected RAC response')
208
+ if body[4] == 0xff:
209
+ raise ValueError('RAC endpoint rejected request')
210
+ return body[4], io.BytesIO(body[5:])
211
+
212
+ async def _parse_cluster(self, payload, *, list_record=False):
213
+ cluster = await read_cluster(payload)
214
+ cluster['cluster'] = str(cluster['cluster'])
215
+ if read_int32(payload) != 1:
216
+ raise ValueError('Unexpected cluster audit marker')
217
+ audit = payload.read(1)
218
+ if audit not in (b'\x00', b'\x01'):
219
+ raise ValueError('Unexpected cluster audit flag')
220
+ cluster['allow-access-right-audit-events-recording'] = audit[0]
221
+ cluster['ping-period'] = read_int32(payload)
222
+ cluster['ping-timeout'] = read_int32(payload)
223
+ cluster['restart-schedule'] = await read_string(payload)
224
+ if not list_record and cluster['restart-schedule'] == '' and payload.read(1):
225
+ # The empty string is followed by an extra zero in the captured
226
+ # 8.5.1 response; the next string carries the actual schedule.
227
+ payload.seek(-1, io.SEEK_CUR)
228
+ cluster['restart-schedule'] = await read_string(payload)
229
+ return cluster
230
+
231
+ async def cluster_info(self, cluster_id):
232
+ ident = uuid.UUID(str(cluster_id))
233
+ message_type, payload = await self.request(13, ident.bytes)
234
+ if message_type != 14:
235
+ raise ValueError('Unexpected cluster info response')
236
+ cluster = await self._parse_cluster(payload)
237
+ if cluster['cluster'] != str(ident):
238
+ raise ValueError('Unexpected cluster info identity')
239
+ if payload.read() not in (b'', b'\x00'):
240
+ raise ValueError('Unexpected trailing response data')
241
+ return cluster
242
+
243
+ async def authenticate_cluster(self, cluster_id, cluster_os_user=None):
244
+ ident = uuid.UUID(str(cluster_id))
245
+ if cluster_os_user is not None and (not isinstance(cluster_os_user, str)
246
+ or not cluster_os_user or '\x00' in cluster_os_user
247
+ or len(cluster_os_user.encode('utf-8')) > 1024):
248
+ raise ValueError('Invalid cluster administrator name')
249
+ await self._exchange(self._authenticate_cluster_within_deadline, ident, cluster_os_user)
250
+
251
+ async def _authenticate_cluster_within_deadline(self, cluster_id, cluster_os_user=None):
252
+ if cluster_os_user is None:
253
+ auth = b'\x00\x00'
254
+ else:
255
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
256
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
257
+ raise ValueError('Invalid cluster administrator name')
258
+ auth = varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00'
259
+ self.writer.write(frame(14, b'\x01\x00\x00\x01\x09' + uuid.UUID(str(cluster_id)).bytes + auth))
260
+ await asyncio.wait_for(self.writer.drain(), self.timeout)
261
+ kind, body = await self.read_frame()
262
+ if (kind, body) != (14, b'\x01\x00\x00\x00'):
263
+ raise ValueError('Unexpected cluster auth response')
264
+
265
+ async def _parse_server(self, payload):
266
+ server = await read_server(payload)
267
+ server['server'] = str(server.pop('working_server_id'))
268
+ server['service-principal-name'] = await read_string(payload)
269
+ server['restart-schedule'] = await read_string(payload)
270
+ return server
271
+
272
+ async def list_servers(self, cluster_id, *, cluster_os_user=None, cluster_password=None):
273
+ ident = uuid.UUID(str(cluster_id)).bytes
274
+ if cluster_password is not None:
275
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
276
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
277
+ or not isinstance(cluster_password, str) or not cluster_password
278
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
279
+ raise ValueError('Invalid cluster administrator credentials')
280
+ await self._send_auth(9, ident + varint_prefixed_data_base64(cluster_os_user.encode())
281
+ + varint_prefixed_data_base64(cluster_password.encode()))
282
+ elif cluster_os_user is None:
283
+ await self.authenticate_cluster(cluster_id)
284
+ else:
285
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
286
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
287
+ raise ValueError('Invalid cluster administrator name')
288
+ await self._send_auth(9, ident
289
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
290
+ message_type, payload = await self.request(22, ident)
291
+ if message_type != 23:
292
+ raise ValueError('Unexpected servers response')
293
+ count = await unpack_varint_base128(payload)
294
+ if count is None or count > 1000:
295
+ raise ValueError('Invalid server count')
296
+ rows = [await self._parse_server(payload) for _ in range(count)]
297
+ if len({row['server'] for row in rows}) != count:
298
+ raise ValueError('Unexpected duplicate server identifier')
299
+ if payload.read() != b'\x00':
300
+ raise ValueError('Unexpected server list terminator')
301
+ return rows
302
+
303
+ async def get_server(self, cluster_id, server_id, *, cluster_os_user=None,
304
+ cluster_password=None):
305
+ cluster, server = uuid.UUID(str(cluster_id)), uuid.UUID(str(server_id))
306
+ if cluster_password is not None:
307
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
308
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
309
+ or not isinstance(cluster_password, str) or not cluster_password
310
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
311
+ raise ValueError('Invalid cluster administrator credentials')
312
+ await self._send_auth(9, cluster.bytes
313
+ + varint_prefixed_data_base64(cluster_os_user.encode())
314
+ + varint_prefixed_data_base64(cluster_password.encode()))
315
+ elif cluster_os_user is None:
316
+ await self.authenticate_cluster(cluster)
317
+ else:
318
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
319
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
320
+ raise ValueError('Invalid cluster administrator name')
321
+ await self._send_auth(9, cluster.bytes
322
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
323
+ ident = cluster.bytes + server.bytes
324
+ message_type, payload = await self.request(24, ident)
325
+ if message_type != 25:
326
+ raise ValueError('Unexpected server info response')
327
+ row = await self._parse_server(payload)
328
+ if row['server'] != str(server):
329
+ raise ValueError('Unexpected server info identity')
330
+ remaining = payload.read()
331
+ if remaining not in (b'', b'\x00'):
332
+ if row['restart-schedule'] != '':
333
+ raise ValueError('Unexpected trailing server info data')
334
+ extra = io.BytesIO(remaining)
335
+ row['restart-schedule'] = await read_string(extra)
336
+ if extra.read():
337
+ raise ValueError('Unexpected trailing server info data')
338
+ return row
339
+
340
+ async def list_processes(self, cluster_id, server_id=None, *, cluster_os_user=None,
341
+ cluster_password=None):
342
+ ident = uuid.UUID(str(cluster_id)).bytes
343
+ server = uuid.UUID(str(server_id)).bytes if server_id is not None else b''
344
+ if cluster_password is not None:
345
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
346
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
347
+ or not isinstance(cluster_password, str) or not cluster_password
348
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
349
+ raise ValueError('Invalid cluster administrator credentials')
350
+ await self._send_auth(9, ident + varint_prefixed_data_base64(cluster_os_user.encode())
351
+ + varint_prefixed_data_base64(cluster_password.encode()))
352
+ elif cluster_os_user is None:
353
+ await self.authenticate_cluster(cluster_id)
354
+ else:
355
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
356
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
357
+ raise ValueError('Invalid cluster administrator name')
358
+ await self._send_auth(9, ident + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
359
+ if server_id is None:
360
+ request_type, expected_type = 29, 30
361
+ value = ident
362
+ else:
363
+ request_type, expected_type = 33, 34
364
+ value = ident + server
365
+ message_type, payload = await self.request(request_type, value)
366
+ if message_type != expected_type:
367
+ raise ValueError('Unexpected process response')
368
+ count = await unpack_varint_base128(payload)
369
+ if count is None or count > 10000:
370
+ raise ValueError('Invalid process count')
371
+ rows = [await self._parse_process(payload) for _ in range(count)]
372
+ if len({row['process'] for row in rows}) != count:
373
+ raise ValueError('Unexpected duplicate process identifier')
374
+ remaining = payload.read()
375
+ if remaining != (b'\x00' if count == 0 else b''):
376
+ raise ValueError('Unexpected process list trailing data')
377
+ return rows
378
+
379
+ async def _parse_process(self, payload):
380
+ process = str(read_uuid(payload))
381
+ # Observed contiguous counters: five doubles, one opaque eight-byte
382
+ # metric, then capacity/connections and the host name.
383
+ metrics = [read_double(payload) for _ in range(5)]
384
+ if not all(math.isfinite(metric) for metric in metrics):
385
+ raise ValueError('Invalid process metric')
386
+ opaque_metric = payload.read(8)
387
+ if len(opaque_metric) != 8:
388
+ raise ValueError('Truncated process metrics')
389
+ capacity = read_int32(payload)
390
+ connections = read_int32(payload)
391
+ host = await read_string(payload)
392
+ flags = payload.read(2)
393
+ if len(flags) != 2:
394
+ raise ValueError('Truncated process flags')
395
+ if flags not in (b'\x01\x00', b'\x01\x01'):
396
+ raise ValueError('Unsupported process flags')
397
+ if flags == b'\x01\x01':
398
+ # Populated 8.5.1 records on dev-area-06 carry additional service
399
+ # metadata between the two flags and the usual process port.
400
+ # Its strings are structural only; never expose their contents.
401
+ if payload.read(1) != b'\x00':
402
+ raise ValueError('Unsupported process metadata marker')
403
+ length = await unpack_varint_base64(payload)
404
+ if length is None or length > 1024 or len(payload.read(length)) != length:
405
+ raise ValueError('Invalid process metadata length')
406
+ if payload.read(1) != b'\x01':
407
+ raise ValueError('Unsupported process metadata flag')
408
+ for _ in range(3):
409
+ read_int32(payload)
410
+ if payload.read(1) != b'\x00':
411
+ raise ValueError('Unsupported process metadata separator')
412
+ for _ in range(2):
413
+ length = await unpack_varint_base64(payload)
414
+ if length is None or length > 1024 or len(payload.read(length)) != length:
415
+ raise ValueError('Invalid process metadata length')
416
+ read_int32(payload)
417
+ for _ in range(2):
418
+ length = await unpack_varint_base64(payload)
419
+ if length is None or length > 1024 or len(payload.read(length)) != length:
420
+ raise ValueError('Invalid process metadata length')
421
+ port = read_uint16(payload)
422
+ memory_size = read_int64(payload)
423
+ pid = await read_string(payload)
424
+ memory_excess_time = read_int32(payload)
425
+ selection_size = read_int32(payload)
426
+ try:
427
+ started_at = date_from_int64(read_int64(payload))
428
+ except (OverflowError, OSError, ValueError):
429
+ raise ValueError('Unsupported process record date or layout') from None
430
+ status = payload.read(8)
431
+ if len(status) != 8:
432
+ raise ValueError('Truncated process status')
433
+ reserve = payload.read(1)
434
+ if reserve not in (b'\x00', b'\x01'):
435
+ raise ValueError('Invalid process reserve flag')
436
+ return {'process': process, 'host': host, 'port': port,
437
+ 'reserve': reserve == b'\x01',
438
+ 'pid': pid, 'capacity': capacity, 'connections': connections,
439
+ 'memory-size': memory_size, 'memory-excess-time': memory_excess_time,
440
+ 'selection-size': selection_size, 'started-at': started_at.isoformat() if started_at else None,
441
+ 'raw-flags': flags.hex(), 'raw-status': status.hex(),
442
+ 'metrics': metrics, 'opaque-metric': opaque_metric.hex()}
443
+
444
+ async def turn_off_process(self, cluster_id, process_id):
445
+ """Research-only primitive; not exposed by RacClient without guarded read-back."""
446
+ cluster, process = uuid.UUID(str(cluster_id)), uuid.UUID(str(process_id))
447
+ await self.authenticate_cluster(cluster)
448
+ await self._send_auth(165, cluster.bytes + process.bytes)
449
+
450
+ async def get_process(self, cluster_id, process_id, *, cluster_os_user=None,
451
+ cluster_password=None):
452
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(process_id))
453
+ if cluster_password is not None:
454
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
455
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
456
+ or not isinstance(cluster_password, str) or not cluster_password
457
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
458
+ raise ValueError('Invalid cluster administrator credentials')
459
+ await self._send_auth(9, cluster.bytes
460
+ + varint_prefixed_data_base64(cluster_os_user.encode())
461
+ + varint_prefixed_data_base64(cluster_password.encode()))
462
+ elif cluster_os_user is None:
463
+ await self.authenticate_cluster(cluster_id)
464
+ else:
465
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
466
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
467
+ raise ValueError('Invalid cluster administrator name')
468
+ await self._send_auth(9, cluster.bytes
469
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
470
+ kind, payload = await self.request(31, cluster.bytes + ident.bytes)
471
+ if kind != 32:
472
+ raise ValueError('Unexpected process info response')
473
+ row = await self._parse_process(payload)
474
+ if row['process'] != str(ident) or payload.read() not in (b'', b'\x00'):
475
+ raise ValueError('Unexpected process info data')
476
+ return row
477
+
478
+ async def _parse_connection(self, payload):
479
+ ident = str(read_uuid(payload))
480
+ application = await read_string(payload)
481
+ blocked = read_int32(payload)
482
+ connected = date_from_int64(read_int64(payload))
483
+ connection_number = read_int32(payload)
484
+ host = await read_string(payload)
485
+ infobase = str(read_uuid(payload))
486
+ process = str(read_uuid(payload))
487
+ session_number = read_int32(payload)
488
+ return {'connection': ident, 'application': application,
489
+ 'blocked-by-ls': blocked, 'connected-at': connected.isoformat() if connected else None,
490
+ 'conn-id': connection_number, 'host': host,
491
+ 'infobase': infobase, 'process': process, 'session-number': session_number}
492
+
493
+ async def list_connections(self, cluster_id, infobase_id=None, *, cluster_os_user=None,
494
+ cluster_password=None):
495
+ value = uuid.UUID(str(cluster_id)).bytes
496
+ infobase = uuid.UUID(str(infobase_id)).bytes if infobase_id is not None else b''
497
+ if cluster_password is not None:
498
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
499
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
500
+ or not isinstance(cluster_password, str) or not cluster_password
501
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
502
+ raise ValueError('Invalid cluster administrator credentials')
503
+ await self._send_auth(9, value + varint_prefixed_data_base64(cluster_os_user.encode())
504
+ + varint_prefixed_data_base64(cluster_password.encode()))
505
+ elif cluster_os_user is None:
506
+ await self.authenticate_cluster(cluster_id)
507
+ else:
508
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
509
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
510
+ raise ValueError('Invalid cluster administrator name')
511
+ await self._send_auth(9, value
512
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
513
+ if infobase_id is None:
514
+ request_type = 50
515
+ else:
516
+ request_type = 52
517
+ value += infobase
518
+ kind, payload = await self.request(request_type, value)
519
+ if kind != request_type + 1:
520
+ raise ValueError('Unexpected connection list response')
521
+ count = await unpack_varint_base128(payload)
522
+ if count is None or count > 10000:
523
+ raise ValueError('Invalid connection count')
524
+ rows = [await self._parse_connection(payload) for _ in range(count)]
525
+ if len({row['connection'] for row in rows}) != count:
526
+ raise ValueError('Unexpected duplicate connection identifier')
527
+ if infobase_id is not None and any(
528
+ row.get('infobase') != str(uuid.UUID(bytes=infobase)) for row in rows):
529
+ raise ValueError('Unexpected connection infobase')
530
+ if payload.read() not in (b'', b'\x00'):
531
+ raise ValueError('Unexpected connection list tail')
532
+ return rows
533
+
534
+ async def get_connection(self, cluster_id, connection_id, *, cluster_os_user=None,
535
+ cluster_password=None):
536
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(connection_id))
537
+ if cluster_password is not None:
538
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
539
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
540
+ or not isinstance(cluster_password, str) or not cluster_password
541
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
542
+ raise ValueError('Invalid cluster administrator credentials')
543
+ await self._send_auth(9, cluster.bytes
544
+ + varint_prefixed_data_base64(cluster_os_user.encode())
545
+ + varint_prefixed_data_base64(cluster_password.encode()))
546
+ elif cluster_os_user is None:
547
+ await self.authenticate_cluster(cluster_id)
548
+ else:
549
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
550
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
551
+ raise ValueError('Invalid cluster administrator name')
552
+ await self._send_auth(9, cluster.bytes
553
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
554
+ kind, payload = await self.request(54, cluster.bytes + ident.bytes)
555
+ if kind != 55:
556
+ raise ValueError('Unexpected connection info response')
557
+ row = await self._parse_connection(payload)
558
+ if row['connection'] != str(ident) or payload.read() not in (b'', b'\x00'):
559
+ raise ValueError('Unexpected connection info tail')
560
+ return row
561
+
562
+ async def _parse_locks(self, payload):
563
+ count = await unpack_varint_base128(payload)
564
+ if count is None or count > 100000:
565
+ raise ValueError('Invalid lock count')
566
+ rows = []
567
+ for _ in range(count):
568
+ connection = str(read_uuid(payload))
569
+ description = await read_string(payload)
570
+ locked_at = date_from_int64(read_int64(payload))
571
+ obj = str(read_uuid(payload))
572
+ sid = str(read_uuid(payload))
573
+ rows.append({'connection': connection, 'lock-descr': description,
574
+ 'locked-at': locked_at.isoformat() if locked_at else None,
575
+ 'object': obj, 'sid': sid})
576
+ if payload.read() not in (b'', b'\x00'):
577
+ raise ValueError('Unexpected lock response tail')
578
+ return rows
579
+
580
+ async def list_locks(self, cluster_id, infobase_id=None, connection_id=None, session_id=None,
581
+ *, cluster_os_user=None, cluster_password=None):
582
+ filters = (infobase_id, connection_id, session_id)
583
+ if sum(value is not None for value in filters) > 1:
584
+ raise ValueError('Select at most one lock filter')
585
+ cluster = uuid.UUID(str(cluster_id)).bytes
586
+ infobase = uuid.UUID(str(infobase_id)).bytes if infobase_id is not None else b''
587
+ connection = uuid.UUID(str(connection_id)).bytes if connection_id is not None else b''
588
+ if session_id is not None:
589
+ uuid.UUID(str(session_id))
590
+ if cluster_password is not None:
591
+ if connection_id is not None or session_id is not None:
592
+ raise ValueError('Unverified password-authenticated lock filter')
593
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
594
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
595
+ or not isinstance(cluster_password, str) or not cluster_password
596
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
597
+ raise ValueError('Invalid cluster administrator credentials')
598
+ await self._send_auth(9, cluster
599
+ + varint_prefixed_data_base64(cluster_os_user.encode())
600
+ + varint_prefixed_data_base64(cluster_password.encode()))
601
+ elif cluster_os_user is None:
602
+ await self.authenticate_cluster(cluster_id)
603
+ else:
604
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
605
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
606
+ raise ValueError('Invalid cluster administrator name')
607
+ await self._send_auth(9, cluster
608
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
609
+ if connection_id is not None:
610
+ request_type = 76
611
+ value = cluster + connection
612
+ elif session_id is not None:
613
+ # 8.5.1 rac accepts --session but sends the cluster-wide lock request.
614
+ request_type = 72
615
+ value = cluster
616
+ elif infobase_id is not None:
617
+ request_type = 74
618
+ value = cluster + infobase
619
+ else:
620
+ request_type = 72
621
+ value = cluster
622
+ kind, payload = await self.request(request_type, value)
623
+ if kind != request_type + 1:
624
+ raise ValueError('Unexpected lock response')
625
+ rows = await self._parse_locks(payload)
626
+ if connection_id is not None and any(
627
+ row['connection'] != str(uuid.UUID(str(connection_id))) for row in rows):
628
+ raise ValueError('Unexpected lock connection')
629
+ return rows
630
+
631
+ async def _parse_rule(self, payload):
632
+ ident = str(read_uuid(payload))
633
+ object_type = await read_string(payload)
634
+ infobase_name = await read_string(payload)
635
+ rule_type = read_int32(payload)
636
+ application_ext = await read_string(payload)
637
+ priority = read_int32(payload)
638
+ if rule_type not in (0, 1, 2):
639
+ raise ValueError('Invalid assignment rule type')
640
+ return {'rule': ident, 'object-type': object_type,
641
+ 'infobase-name': infobase_name, 'raw-rule-type': rule_type,
642
+ 'rule-type': {0: 'never', 1: 'auto', 2: 'always'}[rule_type],
643
+ 'application-ext': application_ext, 'priority': priority}
644
+
645
+ async def insert_rule(self, cluster_id, server_id, position, *, object_type='',
646
+ infobase_name='', rule_type='auto', application_ext='', priority=0,
647
+ cluster_os_user=None):
648
+ cluster, server = uuid.UUID(str(cluster_id)), uuid.UUID(str(server_id))
649
+ if type(position) is not int or not 0 <= position < 2**31:
650
+ raise ValueError('Invalid rule position')
651
+ if type(priority) is not int or not -(2**31) <= priority < 2**31:
652
+ raise ValueError('Invalid rule priority')
653
+ if rule_type not in ('never', 'auto', 'always'):
654
+ raise ValueError('Invalid rule type')
655
+ for value in (object_type, infobase_name, application_ext):
656
+ if not isinstance(value, str) or '\x00' in value or len(value.encode('utf-8')) > 1024:
657
+ raise ValueError('Invalid rule field')
658
+ text = lambda value: varint_prefixed_data_base64(value.encode('utf-8'))
659
+ value = (cluster.bytes + server.bytes + uuid.UUID(int=0).bytes
660
+ + text(object_type) + text(infobase_name)
661
+ + write_int32(('never', 'auto', 'always').index(rule_type))
662
+ + text(application_ext) + write_int32(priority)
663
+ + write_int32(position))
664
+ if cluster_os_user is None:
665
+ await self.authenticate_cluster(cluster)
666
+ else:
667
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
668
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
669
+ raise ValueError('Invalid cluster administrator name')
670
+ await self._send_auth(9, cluster.bytes
671
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
672
+ kind, payload = await self.request(82, value)
673
+ if kind != 83:
674
+ raise ValueError('Unexpected assignment rule insert response')
675
+ ident = read_uuid(payload)
676
+ if ident.int == 0 or payload.read():
677
+ raise ValueError('Unexpected assignment rule insert data')
678
+ return ident
679
+
680
+ async def update_rule(self, cluster_id, server_id, position, row, *, cluster_os_user=None):
681
+ cluster, server = uuid.UUID(str(cluster_id)), uuid.UUID(str(server_id))
682
+ rule = uuid.UUID(row['rule'])
683
+ if type(position) is not int or not 0 <= position < 2**31:
684
+ raise ValueError('Invalid rule position')
685
+ if type(row['priority']) is not int or not -(2**31) <= row['priority'] < 2**31:
686
+ raise ValueError('Invalid rule priority')
687
+ if row['rule-type'] not in ('never', 'auto', 'always') or row['raw-rule-type'] != {
688
+ 'never': 0, 'auto': 1, 'always': 2}[row['rule-type']]:
689
+ raise ValueError('Invalid rule type')
690
+ for key in ('object-type', 'infobase-name', 'application-ext'):
691
+ value = row[key]
692
+ if not isinstance(value, str) or '\x00' in value or len(value.encode('utf-8')) > 1024:
693
+ raise ValueError('Invalid rule field')
694
+ text = lambda key: varint_prefixed_data_base64(row[key].encode('utf-8'))
695
+ value = (cluster.bytes + server.bytes + rule.bytes
696
+ + text('object-type') + text('infobase-name')
697
+ + write_int32(row['raw-rule-type']) + text('application-ext')
698
+ + write_int32(row['priority']) + write_int32(position))
699
+ if cluster_os_user is None:
700
+ await self.authenticate_cluster(cluster)
701
+ else:
702
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
703
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
704
+ raise ValueError('Invalid cluster administrator name')
705
+ await self._send_auth(9, cluster.bytes
706
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
707
+ kind, payload = await self.request(82, value)
708
+ if kind != 83 or read_uuid(payload) != rule or payload.read():
709
+ raise ValueError('Unexpected assignment rule update response')
710
+ return rule
711
+
712
+ async def apply_rules(self, cluster_id, *, full=True, cluster_os_user=None):
713
+ if type(full) is not bool:
714
+ raise ValueError('Invalid assignment rule apply mode')
715
+ cluster = uuid.UUID(str(cluster_id))
716
+ if cluster_os_user is None:
717
+ await self.authenticate_cluster(cluster)
718
+ else:
719
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
720
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
721
+ raise ValueError('Invalid cluster administrator name')
722
+ await self._send_auth(9, cluster.bytes
723
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
724
+ await self._send_auth(81, cluster.bytes + write_int32(int(full)))
725
+
726
+ async def remove_rule(self, cluster_id, server_id, rule_id, *, cluster_os_user=None):
727
+ cluster, server, rule = (uuid.UUID(str(value)) for value in
728
+ (cluster_id, server_id, rule_id))
729
+ if cluster_os_user is None:
730
+ await self.authenticate_cluster(cluster)
731
+ else:
732
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
733
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
734
+ raise ValueError('Invalid cluster administrator name')
735
+ await self._send_auth(9, cluster.bytes
736
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
737
+ await self._send_auth(84, cluster.bytes + server.bytes + rule.bytes)
738
+
739
+ async def list_rules(self, cluster_id, server_id, *, cluster_os_user=None):
740
+ cluster = uuid.UUID(str(cluster_id)).bytes
741
+ server = uuid.UUID(str(server_id)).bytes
742
+ if cluster_os_user is None:
743
+ await self.authenticate_cluster(cluster_id)
744
+ else:
745
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
746
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
747
+ raise ValueError('Invalid cluster administrator name')
748
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
749
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
750
+ kind, payload = await self.request(85, cluster + server)
751
+ if kind != 86:
752
+ raise ValueError('Unexpected assignment rules response')
753
+ count = await unpack_varint_base128(payload)
754
+ if count is None or count > 10000:
755
+ raise ValueError('Invalid assignment rules count')
756
+ rows = [await self._parse_rule(payload) for _ in range(count)]
757
+ if len({row['rule'] for row in rows}) != count:
758
+ raise ValueError('Unexpected duplicate assignment rule identifier')
759
+ if payload.read():
760
+ raise ValueError('Unexpected assignment rules tail')
761
+ return rows
762
+
763
+ async def get_rule(self, cluster_id, server_id, rule_id, *, cluster_os_user=None):
764
+ cluster, server, ident = (uuid.UUID(str(item)) for item in (cluster_id, server_id, rule_id))
765
+ if cluster_os_user is None:
766
+ await self.authenticate_cluster(cluster)
767
+ else:
768
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
769
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
770
+ raise ValueError('Invalid cluster administrator name')
771
+ await self._send_auth(9, cluster.bytes
772
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
773
+ kind, payload = await self.request(87, cluster.bytes + server.bytes + ident.bytes)
774
+ if kind != 88:
775
+ raise ValueError('Unexpected assignment rule info response')
776
+ row = await self._parse_rule(payload)
777
+ if row['rule'] != str(ident) or payload.read():
778
+ raise ValueError('Unexpected assignment rule info data')
779
+ return row
780
+
781
+ async def list_binary_storages(self, cluster_id, infobase_id, *, cluster_os_user=None):
782
+ cluster = uuid.UUID(str(cluster_id))
783
+ infobase = uuid.UUID(str(infobase_id))
784
+ if cluster_os_user is not None and (not isinstance(cluster_os_user, str)
785
+ or not cluster_os_user or '\x00' in cluster_os_user
786
+ or len(cluster_os_user.encode('utf-8')) > 1024):
787
+ raise ValueError('Invalid cluster administrator name')
788
+ auth = (b'\x00\x00' if cluster_os_user is None else
789
+ varint_prefixed_data_base64(cluster_os_user.encode('utf-8')) + b'\x00')
790
+ await self._send_auth(9, cluster.bytes + auth)
791
+ await self._send_auth(10, cluster.bytes + auth)
792
+ kind, payload = await self.request(153, cluster.bytes + infobase.bytes)
793
+ if kind != 160:
794
+ raise ValueError('Unexpected binary storage list response')
795
+ count = await unpack_varint_base128(payload)
796
+ if count is None or count > 10000:
797
+ raise ValueError('Invalid binary storage count')
798
+ if count:
799
+ raise ValueError('Unsupported populated binary storage response format')
800
+ if payload.read():
801
+ raise ValueError('Unexpected binary storage tail')
802
+ return []
803
+
804
+ async def create_infobase(self, cluster_id, *, name, db_name, db_server, db_user, locale,
805
+ create_database, descr='', scheduled_jobs_denied=False,
806
+ license_distribution='deny', date_offset=0, security_level=0):
807
+ if type(security_level) is not int or security_level not in (0, 1):
808
+ raise ValueError('Invalid security_level')
809
+ if type(date_offset) is not int or date_offset not in (0, 2000):
810
+ raise ValueError('Invalid date_offset')
811
+ if license_distribution not in ('allow', 'deny') or type(license_distribution) is not str:
812
+ raise ValueError('Invalid license_distribution')
813
+ if type(scheduled_jobs_denied) is not bool:
814
+ raise ValueError('Invalid scheduled_jobs_denied')
815
+ if type(create_database) is not bool:
816
+ raise ValueError('Explicit boolean create_database policy required')
817
+ for label, value in (('name', name), ('db_name', db_name), ('db_server', db_server),
818
+ ('db_user', db_user), ('locale', locale)):
819
+ if (not isinstance(value, str) or not value or '\x00' in value
820
+ or len(value.encode('utf-8')) > 255):
821
+ raise ValueError('Invalid infobase ' + label)
822
+ if not isinstance(descr, str) or '\x00' in descr or len(descr.encode('utf-8')) > 1024:
823
+ raise ValueError('Invalid infobase description')
824
+ cluster = uuid.UUID(str(cluster_id))
825
+ await self._send_auth(9, cluster.bytes + b'\x00\x00')
826
+ text = lambda value: varint_prefixed_data_base64(value.encode('utf-8'))
827
+ # Verified 8.5.1 PostgreSQL record: nil infobase ID, zero-valued
828
+ # optional fields, and the trailing explicit database-creation flag.
829
+ record = (uuid.UUID(int=0).bytes + write_int32(date_offset) + text('PostgreSQL')
830
+ + text(db_name) + b'\x00' + text(db_server) + text(db_user)
831
+ + b'\x00' * 8 + text('') + text('') + b'\x00' * 8
832
+ + text(descr) + text(locale) + text(name) + text('')
833
+ + write_bool(scheduled_jobs_denied) + write_int32(security_level) + b'\x00'
834
+ + write_int32(1 if license_distribution == 'allow' else 0)
835
+ + text('') + b'\x00' + text('') + text('') + b'\x00'
836
+ + b'\x00' * 16 + (b'\x01' if create_database else b'\x00'))
837
+ kind, payload = await self.request(37, cluster.bytes + record)
838
+ if kind != 38 or len(payload.getvalue()) != 16:
839
+ raise ValueError('Unexpected infobase create response')
840
+ result = uuid.UUID(bytes=payload.read(16))
841
+ if result.int == 0:
842
+ raise ValueError('Unexpected infobase create response')
843
+ return result
844
+
845
+ async def drop_infobase(self, cluster_id, infobase_id, *, drop_database,
846
+ clear_database=False, cluster_os_user=None):
847
+ if type(drop_database) is not bool or type(clear_database) is not bool:
848
+ raise ValueError('Explicit boolean drop_database/clear_database policy required')
849
+ if drop_database and clear_database:
850
+ raise ValueError('Conflicting infobase database policy')
851
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(infobase_id))
852
+ if cluster_os_user is None:
853
+ auth = cluster.bytes + b'\x00\x00'
854
+ else:
855
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
856
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode()) > 1024):
857
+ raise ValueError('Invalid cluster administrator name')
858
+ auth = (cluster.bytes + varint_prefixed_data_base64(cluster_os_user.encode())
859
+ + b'\x00')
860
+ await self._send_auth(9, auth)
861
+ await self._send_auth(10, auth)
862
+ await self._exchange(self._send_ack_within_deadline, 41,
863
+ cluster.bytes + ident.bytes + b'\x00\x00\x00'
864
+ + bytes([2 if clear_database else int(drop_database)]),
865
+ 'Unexpected infobase drop response')
866
+
867
+ async def _send_ack_within_deadline(self, message_type, value, error):
868
+ self.writer.write(frame(14, b'\x01\x00\x00\x01' + bytes([message_type]) + value))
869
+ await self.writer.drain()
870
+ if await self.read_frame() != (14, b'\x01\x00\x00\x00'):
871
+ raise ValueError(error)
872
+
873
+ async def _send_infobase_update(self, cluster, updated):
874
+ """Share one deadline across a verified full-record update and its ACK."""
875
+ await self._exchange(self._send_ack_within_deadline, 40, cluster.bytes + updated,
876
+ 'Unexpected infobase update response')
877
+
878
+ async def update_infobase_safe_mode_security_profile(self, cluster_id, infobase_id, name):
879
+ if (not isinstance(name, str) or '\x00' in name or len(name.encode('utf-8')) > 1024):
880
+ raise ValueError('Invalid security profile name')
881
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(infobase_id))
882
+ await self._send_auth(9, cluster.bytes + b'\x00\x00')
883
+ await self._send_auth(10, cluster.bytes + b'\x00\x00')
884
+ kind, payload = await self.request(48, cluster.bytes + ident.bytes)
885
+ if kind != 49:
886
+ raise ValueError('Unexpected infobase info response before update')
887
+ raw = payload.getvalue()
888
+ reader = io.BytesIO(raw)
889
+ row = await read_infobase(reader)
890
+ extension = reader.read()
891
+ if (row['infobase'] != ident or len(extension) != 13
892
+ or extension[0] not in (0, 1) or extension[1:4] != b'\x00' * 3):
893
+ raise ValueError('Unexpected infobase info extension before update')
894
+ fields = io.BytesIO(raw)
895
+ fields.read(16 + 4)
896
+ for _ in range(5):
897
+ await read_bytes(fields)
898
+ fields.read(8)
899
+ for _ in range(2):
900
+ await read_bytes(fields)
901
+ fields.read(8)
902
+ for _ in range(4):
903
+ await read_bytes(fields)
904
+ fields.read(1 + 4 + 1 + 4)
905
+ await read_bytes(fields)
906
+ fields.read(1)
907
+ await read_bytes(fields)
908
+ offset = fields.tell()
909
+ old = varint_prefixed_data_base64(row['safe_mode_securirty_profile'].encode('utf-8'))
910
+ if raw[offset:offset + len(old)] != old:
911
+ raise ValueError('Unexpected safe mode security profile field')
912
+ updated = (raw[:offset] + varint_prefixed_data_base64(name.encode('utf-8'))
913
+ + raw[offset + len(old):])
914
+ await self._send_infobase_update(cluster, updated)
915
+
916
+ async def update_infobase_security_profile(self, cluster_id, infobase_id, name):
917
+ if (not isinstance(name, str) or '\x00' in name or len(name.encode('utf-8')) > 1024):
918
+ raise ValueError('Invalid security profile name')
919
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(infobase_id))
920
+ await self._send_auth(9, cluster.bytes + b'\x00\x00')
921
+ await self._send_auth(10, cluster.bytes + b'\x00\x00')
922
+ kind, payload = await self.request(48, cluster.bytes + ident.bytes)
923
+ if kind != 49:
924
+ raise ValueError('Unexpected infobase info response before update')
925
+ raw = payload.getvalue()
926
+ reader = io.BytesIO(raw)
927
+ row = await read_infobase(reader)
928
+ extension = reader.read()
929
+ if (row['infobase'] != ident or len(extension) != 13
930
+ or extension[0] not in (0, 1) or extension[1:4] != b'\x00' * 3):
931
+ raise ValueError('Unexpected infobase info extension before update')
932
+ fields = io.BytesIO(raw)
933
+ fields.read(16 + 4)
934
+ for _ in range(5):
935
+ await read_bytes(fields)
936
+ fields.read(8)
937
+ for _ in range(2):
938
+ await read_bytes(fields)
939
+ fields.read(8)
940
+ for _ in range(4):
941
+ await read_bytes(fields)
942
+ fields.read(1 + 4 + 1 + 4)
943
+ await read_bytes(fields)
944
+ fields.read(1)
945
+ offset = fields.tell()
946
+ old = varint_prefixed_data_base64(row['securirty_profile'].encode('utf-8'))
947
+ if raw[offset:offset + len(old)] != old:
948
+ raise ValueError('Unexpected security profile field')
949
+ updated = (raw[:offset] + varint_prefixed_data_base64(name.encode('utf-8'))
950
+ + raw[offset + len(old):])
951
+ await self._send_infobase_update(cluster, updated)
952
+
953
+ async def update_infobase_maximum_scheduled_jobs_start_shift(self, cluster_id, infobase_id, shift):
954
+ if type(shift) is not int or not 0 <= shift <= 255:
955
+ raise ValueError('Invalid maximum scheduled jobs start shift')
956
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(infobase_id))
957
+ await self._send_auth(9, cluster.bytes + b'\x00\x00')
958
+ await self._send_auth(10, cluster.bytes + b'\x00\x00')
959
+ kind, payload = await self.request(48, cluster.bytes + ident.bytes)
960
+ if kind != 49:
961
+ raise ValueError('Unexpected infobase info response before update')
962
+ raw = payload.getvalue()
963
+ reader = io.BytesIO(raw)
964
+ row = await read_infobase(reader)
965
+ extension = reader.read()
966
+ if (row['infobase'] != ident or len(extension) != 13
967
+ or extension[0] not in (0, 1) or extension[1:4] != b'\x00' * 3):
968
+ raise ValueError('Unexpected infobase info extension before update')
969
+ updated = raw[:-1] + bytes((shift,))
970
+ await self._send_infobase_update(cluster, updated)
971
+
972
+ async def update_infobase_minimum_scheduled_jobs_start_period(self, cluster_id, infobase_id, period):
973
+ if type(period) is not int or not 0 <= period <= 0xffffffff:
974
+ raise ValueError('Invalid minimum scheduled jobs start period')
975
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(infobase_id))
976
+ await self._send_auth(9, cluster.bytes + b'\x00\x00')
977
+ await self._send_auth(10, cluster.bytes + b'\x00\x00')
978
+ kind, payload = await self.request(48, cluster.bytes + ident.bytes)
979
+ if kind != 49:
980
+ raise ValueError('Unexpected infobase info response before update')
981
+ raw = payload.getvalue()
982
+ reader = io.BytesIO(raw)
983
+ row = await read_infobase(reader)
984
+ extension = reader.read()
985
+ if (row['infobase'] != ident or len(extension) != 13
986
+ or extension[0] not in (0, 1)
987
+ or extension[1:4] != b'\x00' * 3):
988
+ raise ValueError('Unexpected infobase info extension before update')
989
+ updated = raw[:-5] + period.to_bytes(4, 'little') + raw[-1:]
990
+ await self._send_infobase_update(cluster, updated)
991
+
992
+ async def update_infobase_configuration_unload_delay(self, cluster_id, infobase_id, delay):
993
+ if type(delay) is not int or not 0 <= delay <= 0xffffffff:
994
+ raise ValueError('Invalid configuration unload delay')
995
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(infobase_id))
996
+ await self._send_auth(9, cluster.bytes + b'\x00\x00')
997
+ await self._send_auth(10, cluster.bytes + b'\x00\x00')
998
+ kind, payload = await self.request(48, cluster.bytes + ident.bytes)
999
+ if kind != 49:
1000
+ raise ValueError('Unexpected infobase info response before update')
1001
+ raw = payload.getvalue()
1002
+ reader = io.BytesIO(raw)
1003
+ row = await read_infobase(reader)
1004
+ extension = reader.read()
1005
+ if (row['infobase'] != ident or len(extension) != 13
1006
+ or extension[0] not in (0, 1)
1007
+ or extension[1:4] != b'\x00' * 3):
1008
+ raise ValueError('Unexpected infobase info extension before update')
1009
+ updated = raw[:-9] + delay.to_bytes(4, 'little') + raw[-5:]
1010
+ await self._send_infobase_update(cluster, updated)
1011
+
1012
+ async def update_infobase_disable_local_speech_to_text(self, cluster_id, infobase_id, disabled):
1013
+ if type(disabled) is not bool:
1014
+ raise ValueError('Disable local speech to text must be boolean')
1015
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(infobase_id))
1016
+ await self._send_auth(9, cluster.bytes + b'\x00\x00')
1017
+ await self._send_auth(10, cluster.bytes + b'\x00\x00')
1018
+ kind, payload = await self.request(48, cluster.bytes + ident.bytes)
1019
+ if kind != 49:
1020
+ raise ValueError('Unexpected infobase info response before update')
1021
+ raw = payload.getvalue()
1022
+ reader = io.BytesIO(raw)
1023
+ row = await read_infobase(reader)
1024
+ extension = reader.read()
1025
+ if (row['infobase'] != ident or len(extension) != 13
1026
+ or extension[0] not in (0, 1) or extension[1:4] != b'\x00' * 3):
1027
+ raise ValueError('Unexpected infobase info extension before update')
1028
+ updated = raw[:-13] + write_bool(disabled) + raw[-12:]
1029
+ await self._send_infobase_update(cluster, updated)
1030
+
1031
+ async def update_infobase_external_session_manager_required(self, cluster_id, infobase_id, required):
1032
+ if type(required) is not bool:
1033
+ raise ValueError('External session manager required must be boolean')
1034
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(infobase_id))
1035
+ await self._send_auth(9, cluster.bytes + b'\x00\x00')
1036
+ await self._send_auth(10, cluster.bytes + b'\x00\x00')
1037
+ kind, payload = await self.request(48, cluster.bytes + ident.bytes)
1038
+ if kind != 49:
1039
+ raise ValueError('Unexpected infobase info response before update')
1040
+ raw = payload.getvalue()
1041
+ reader = io.BytesIO(raw)
1042
+ row = await read_infobase(reader)
1043
+ extension = reader.read()
1044
+ if (row['infobase'] != ident or len(extension) != 13
1045
+ or extension[0] not in (0, 1) or extension[1:4] != b'\x00' * 3):
1046
+ raise ValueError('Unexpected infobase info extension before update')
1047
+ fields = io.BytesIO(raw)
1048
+ fields.read(16 + 4)
1049
+ for _ in range(5):
1050
+ await read_bytes(fields)
1051
+ fields.read(8)
1052
+ for _ in range(2):
1053
+ await read_bytes(fields)
1054
+ fields.read(8)
1055
+ for _ in range(4):
1056
+ await read_bytes(fields)
1057
+ fields.read(1 + 4 + 1 + 4)
1058
+ await read_bytes(fields)
1059
+ offset = fields.tell()
1060
+ if raw[offset:offset + 1] != write_bool(row['external_session_manager_required']):
1061
+ raise ValueError('Unexpected external session manager required field')
1062
+ updated = raw[:offset] + write_bool(required) + raw[offset + 1:]
1063
+ await self._send_infobase_update(cluster, updated)
1064
+
1065
+ async def update_infobase_reserve_working_processes(self, cluster_id, infobase_id, reserve):
1066
+ if type(reserve) is not bool:
1067
+ raise ValueError('Reserve working processes must be boolean')
1068
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(infobase_id))
1069
+ await self._send_auth(9, cluster.bytes + b'\x00\x00')
1070
+ await self._send_auth(10, cluster.bytes + b'\x00\x00')
1071
+ kind, payload = await self.request(48, cluster.bytes + ident.bytes)
1072
+ if kind != 49:
1073
+ raise ValueError('Unexpected infobase info response before update')
1074
+ raw = payload.getvalue()
1075
+ reader = io.BytesIO(raw)
1076
+ row = await read_infobase(reader)
1077
+ extension = reader.read()
1078
+ if (row['infobase'] != ident or len(extension) != 13
1079
+ or extension[0] not in (0, 1) or extension[1:4] != b'\x00' * 3):
1080
+ raise ValueError('Unexpected infobase info extension before update')
1081
+ fields = io.BytesIO(raw)
1082
+ fields.read(16 + 4)
1083
+ for _ in range(5):
1084
+ await read_bytes(fields)
1085
+ fields.read(8)
1086
+ for _ in range(2):
1087
+ await read_bytes(fields)
1088
+ fields.read(8)
1089
+ for _ in range(4):
1090
+ await read_bytes(fields)
1091
+ fields.read(1 + 4 + 1 + 4)
1092
+ for _ in range(3):
1093
+ await read_bytes(fields)
1094
+ fields.read(1)
1095
+ offset = fields.tell()
1096
+ if raw[offset:offset + 1] != write_bool(row['reserve_working_processes']):
1097
+ raise ValueError('Unexpected reserve working processes field')
1098
+ updated = raw[:offset] + write_bool(reserve) + raw[offset + 1:]
1099
+ await self._send_infobase_update(cluster, updated)
1100
+
1101
+ async def update_infobase_denied_message(self, cluster_id, infobase_id, message):
1102
+ if not isinstance(message, str) or '\x00' in message:
1103
+ raise ValueError('Invalid denied message')
1104
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(infobase_id))
1105
+ await self._send_auth(9, cluster.bytes + b'\x00\x00')
1106
+ await self._send_auth(10, cluster.bytes + b'\x00\x00')
1107
+ kind, payload = await self.request(48, cluster.bytes + ident.bytes)
1108
+ if kind != 49:
1109
+ raise ValueError('Unexpected infobase info response before update')
1110
+ raw = payload.getvalue()
1111
+ reader = io.BytesIO(raw)
1112
+ row = await read_infobase(reader)
1113
+ extension = reader.read()
1114
+ if (row['infobase'] != ident or len(extension) != 13
1115
+ or extension[0] not in (0, 1) or extension[1:4] != b'\x00' * 3):
1116
+ raise ValueError('Unexpected infobase info extension before update')
1117
+ fields = io.BytesIO(raw)
1118
+ fields.read(16 + 4)
1119
+ for _ in range(5):
1120
+ await read_bytes(fields)
1121
+ fields.read(8)
1122
+ offset = fields.tell()
1123
+ old = varint_prefixed_data_base64(row['denied_message'].encode())
1124
+ if raw[offset:offset + len(old)] != old:
1125
+ raise ValueError('Unexpected denied message field')
1126
+ updated = (raw[:offset] + varint_prefixed_data_base64(message.encode())
1127
+ + raw[offset + len(old):])
1128
+ await self._send_infobase_update(cluster, updated)
1129
+
1130
+ async def update_infobase_license_distribution(self, cluster_id, infobase_id, distribution):
1131
+ if distribution not in ('allow', 'deny'):
1132
+ raise ValueError('Invalid license distribution')
1133
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(infobase_id))
1134
+ await self._send_auth(9, cluster.bytes + b'\x00\x00')
1135
+ await self._send_auth(10, cluster.bytes + b'\x00\x00')
1136
+ kind, payload = await self.request(48, cluster.bytes + ident.bytes)
1137
+ if kind != 49:
1138
+ raise ValueError('Unexpected infobase info response before update')
1139
+ raw = payload.getvalue()
1140
+ reader = io.BytesIO(raw)
1141
+ row = await read_infobase(reader)
1142
+ extension = reader.read()
1143
+ if (row['infobase'] != ident or len(extension) != 13
1144
+ or extension[0] not in (0, 1) or extension[1:4] != b'\x00' * 3):
1145
+ raise ValueError('Unexpected infobase info extension before update')
1146
+ fields = io.BytesIO(raw)
1147
+ fields.read(16 + 4)
1148
+ for _ in range(5):
1149
+ await read_bytes(fields)
1150
+ fields.read(8)
1151
+ for _ in range(2):
1152
+ await read_bytes(fields)
1153
+ fields.read(8)
1154
+ for _ in range(4):
1155
+ await read_bytes(fields)
1156
+ fields.read(1 + 4 + 1)
1157
+ offset = fields.tell()
1158
+ if raw[offset:offset + 4] != write_int32(row['license_distribution']):
1159
+ raise ValueError('Unexpected license distribution field')
1160
+ updated = (raw[:offset] + write_int32(1 if distribution == 'allow' else 0)
1161
+ + raw[offset + 4:])
1162
+ await self._send_infobase_update(cluster, updated)
1163
+
1164
+ async def update_infobase_sessions_deny(self, cluster_id, infobase_id, denied):
1165
+ if type(denied) is not bool:
1166
+ raise ValueError('Sessions deny must be boolean')
1167
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(infobase_id))
1168
+ await self._send_auth(9, cluster.bytes + b'\x00\x00')
1169
+ await self._send_auth(10, cluster.bytes + b'\x00\x00')
1170
+ kind, payload = await self.request(48, cluster.bytes + ident.bytes)
1171
+ if kind != 49:
1172
+ raise ValueError('Unexpected infobase info response before update')
1173
+ raw = payload.getvalue()
1174
+ reader = io.BytesIO(raw)
1175
+ row = await read_infobase(reader)
1176
+ extension = reader.read()
1177
+ if (row['infobase'] != ident or len(extension) != 13
1178
+ or extension[0] not in (0, 1) or extension[1:4] != b'\x00' * 3):
1179
+ raise ValueError('Unexpected infobase info extension before update')
1180
+ fields = io.BytesIO(raw)
1181
+ fields.read(16 + 4)
1182
+ for _ in range(5):
1183
+ await read_bytes(fields)
1184
+ fields.read(8)
1185
+ for _ in range(2):
1186
+ await read_bytes(fields)
1187
+ fields.read(8)
1188
+ for _ in range(4):
1189
+ await read_bytes(fields)
1190
+ fields.read(1 + 4)
1191
+ offset = fields.tell()
1192
+ if raw[offset:offset + 1] != write_bool(row['sessions_denied']):
1193
+ raise ValueError('Unexpected sessions deny field')
1194
+ updated = raw[:offset] + write_bool(denied) + raw[offset + 1:]
1195
+ await self._send_infobase_update(cluster, updated)
1196
+
1197
+ async def update_infobase_scheduled_jobs_deny(self, cluster_id, infobase_id, denied):
1198
+ if type(denied) is not bool:
1199
+ raise ValueError('Scheduled jobs deny must be boolean')
1200
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(infobase_id))
1201
+ await self._send_auth(9, cluster.bytes + b'\x00\x00')
1202
+ await self._send_auth(10, cluster.bytes + b'\x00\x00')
1203
+ kind, payload = await self.request(48, cluster.bytes + ident.bytes)
1204
+ if kind != 49:
1205
+ raise ValueError('Unexpected infobase info response before update')
1206
+ raw = payload.getvalue()
1207
+ reader = io.BytesIO(raw)
1208
+ row = await read_infobase(reader)
1209
+ extension = reader.read()
1210
+ if (row['infobase'] != ident or len(extension) != 13
1211
+ or extension[0] not in (0, 1) or extension[1:4] != b'\x00' * 3):
1212
+ raise ValueError('Unexpected infobase info extension before update')
1213
+ fields = io.BytesIO(raw)
1214
+ fields.read(16 + 4)
1215
+ for _ in range(5):
1216
+ await read_bytes(fields)
1217
+ fields.read(8)
1218
+ for _ in range(2):
1219
+ await read_bytes(fields)
1220
+ fields.read(8)
1221
+ for _ in range(4):
1222
+ await read_bytes(fields)
1223
+ offset = fields.tell()
1224
+ if raw[offset:offset + 1] != write_bool(row['scheduled_jobs_denied']):
1225
+ raise ValueError('Unexpected scheduled jobs deny field')
1226
+ updated = raw[:offset] + write_bool(denied) + raw[offset + 1:]
1227
+ await self._send_infobase_update(cluster, updated)
1228
+
1229
+ async def update_infobase_description(self, cluster_id, infobase_id, description):
1230
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(infobase_id))
1231
+ if (not isinstance(description, str) or '\x00' in description
1232
+ or len(description.encode('utf-8')) > 1024):
1233
+ raise ValueError('Invalid infobase description')
1234
+ await self._send_auth(9, cluster.bytes + b'\x00\x00')
1235
+ await self._send_auth(10, cluster.bytes + b'\x00\x00')
1236
+ kind, payload = await self.request(48, cluster.bytes + ident.bytes)
1237
+ if kind != 49:
1238
+ raise ValueError('Unexpected infobase info response before update')
1239
+ raw = payload.getvalue()
1240
+ reader = io.BytesIO(raw)
1241
+ row = await read_infobase(reader)
1242
+ if row['infobase'] != ident:
1243
+ raise ValueError('Unexpected infobase info identifier before update')
1244
+ extension = reader.read()
1245
+ if (len(extension) != 13 or extension[0] not in (0, 1)
1246
+ or extension[1:4] != b'\x00' * 3):
1247
+ raise ValueError('Unexpected infobase info extension before update')
1248
+ fields = io.BytesIO(raw)
1249
+ fields.read(16 + 4)
1250
+ for _ in range(5):
1251
+ await read_bytes(fields)
1252
+ fields.read(8)
1253
+ for _ in range(2):
1254
+ await read_bytes(fields)
1255
+ fields.read(8)
1256
+ start = fields.tell()
1257
+ original = varint_prefixed_data_base64(row['descr'].encode('utf-8'))
1258
+ if raw[start:start + len(original)] != original:
1259
+ raise ValueError('Unexpected infobase description field')
1260
+ updated = (raw[:start] + varint_prefixed_data_base64(description.encode('utf-8'))
1261
+ + raw[start + len(original):])
1262
+ await self._send_infobase_update(cluster, updated)
1263
+
1264
+ async def update_infobase_summary_description(self, cluster_id, infobase_id, description,
1265
+ *, cluster_os_user=None):
1266
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(infobase_id))
1267
+ if (not isinstance(description, str) or '\x00' in description
1268
+ or len(description.encode('utf-8')) > 1024):
1269
+ raise ValueError('Invalid infobase summary description')
1270
+ if cluster_os_user is None:
1271
+ await self.authenticate_cluster(cluster)
1272
+ else:
1273
+ await self.authenticate_cluster(cluster, cluster_os_user)
1274
+ value = (cluster.bytes + ident.bytes
1275
+ + varint_prefixed_data_base64(description.encode('utf-8')) + b'\x00')
1276
+ await self._exchange(self._send_ack_within_deadline, 39, value,
1277
+ 'Unexpected infobase summary update response')
1278
+
1279
+ async def list_infobases(self, cluster_id, *, cluster_os_user=None, cluster_password=None):
1280
+ ident = uuid.UUID(str(cluster_id)).bytes
1281
+ if cluster_password is not None:
1282
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
1283
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
1284
+ or not isinstance(cluster_password, str) or not cluster_password
1285
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
1286
+ raise ValueError('Invalid cluster administrator credentials')
1287
+ await self._send_auth(9, ident
1288
+ + varint_prefixed_data_base64(cluster_os_user.encode())
1289
+ + varint_prefixed_data_base64(cluster_password.encode()))
1290
+ elif cluster_os_user is None:
1291
+ await self.authenticate_cluster(cluster_id)
1292
+ else:
1293
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
1294
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
1295
+ raise ValueError('Invalid cluster administrator name')
1296
+ await self._send_auth(9, ident
1297
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
1298
+ message_type, payload = await self.request(42, ident)
1299
+ if message_type != 43:
1300
+ raise ValueError('Unexpected infobases response')
1301
+ count = await unpack_varint_base128(payload)
1302
+ if count is None or count > 10000:
1303
+ raise ValueError('Invalid infobase count')
1304
+ rows = []
1305
+ seen = set()
1306
+ for _ in range(count):
1307
+ row = await read_infobase_short(payload)
1308
+ row['infobase'] = str(row['infobase'])
1309
+ if row['infobase'] in seen:
1310
+ raise ValueError('Unexpected duplicate infobase identifier')
1311
+ seen.add(row['infobase'])
1312
+ rows.append(row)
1313
+ if payload.read(1):
1314
+ raise ValueError('Unexpected trailing infobase data')
1315
+ return rows
1316
+
1317
+ async def get_infobase_info(self, cluster_id, infobase_id, *, cluster_os_user=None):
1318
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(infobase_id))
1319
+ if cluster_os_user is None:
1320
+ await self._send_auth(9, cluster.bytes + b'\x00\x00')
1321
+ await self._send_auth(10, cluster.bytes + b'\x00\x00')
1322
+ else:
1323
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
1324
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode()) > 1024):
1325
+ raise ValueError('Invalid cluster administrator name')
1326
+ auth = cluster.bytes + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00'
1327
+ await self._send_auth(9, auth)
1328
+ await self._send_auth(10, auth)
1329
+ kind, payload = await self.request(48, cluster.bytes + ident.bytes)
1330
+ if kind != 49:
1331
+ raise ValueError('Unexpected infobase info response')
1332
+ row = await read_infobase(payload)
1333
+ if row['infobase'] != ident:
1334
+ raise ValueError('Unexpected infobase info identifier')
1335
+ extension = payload.read()
1336
+ if (len(extension) != 13 or extension[0] not in (0, 1)
1337
+ or extension[1:4] != b'\x00' * 3):
1338
+ raise ValueError('Unexpected infobase info extension')
1339
+ row['disable_local_speech_to_text'] = bool(extension[0])
1340
+ row['configuration_unload_delay_by_working_process_without_active_users'] = int.from_bytes(
1341
+ extension[4:8], 'little')
1342
+ row['minimum_scheduled_jobs_start_period_without_active_users'] = int.from_bytes(
1343
+ extension[8:12], 'little')
1344
+ row['maximum_scheduled_jobs_start_shift_without_active_users'] = extension[12]
1345
+ for sensitive in ('db_password', 'permission_code', 'denied_parameter',
1346
+ 'external_connection_string'):
1347
+ row.pop(sensitive, None) # Do not return credentials or access parameters.
1348
+ row['infobase'] = str(ident)
1349
+ return row
1350
+
1351
+ async def get_infobase_summary(self, cluster_id, infobase_id, *, cluster_os_user=None,
1352
+ cluster_password=None):
1353
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(infobase_id))
1354
+ if cluster_password is not None:
1355
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
1356
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
1357
+ or not isinstance(cluster_password, str) or not cluster_password
1358
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
1359
+ raise ValueError('Invalid cluster administrator credentials')
1360
+ await self._send_auth(9, cluster.bytes
1361
+ + varint_prefixed_data_base64(cluster_os_user.encode())
1362
+ + varint_prefixed_data_base64(cluster_password.encode()))
1363
+ elif cluster_os_user is None:
1364
+ await self.authenticate_cluster(cluster)
1365
+ else:
1366
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
1367
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
1368
+ raise ValueError('Invalid cluster administrator name')
1369
+ await self._send_auth(9, cluster.bytes
1370
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
1371
+ kind, payload = await self.request(46, cluster.bytes + ident.bytes)
1372
+ if kind != 47:
1373
+ raise ValueError('Unexpected infobase summary info response')
1374
+ row = await read_infobase_short(payload)
1375
+ if row['infobase'] != ident or payload.read():
1376
+ raise ValueError('Unexpected infobase summary info data')
1377
+ row['infobase'] = str(ident)
1378
+ return row
1379
+
1380
+ async def update_cluster_kill_by_memory_with_dump(self, cluster_id, enabled, *, agent_os_user=None):
1381
+ if type(enabled) is not bool:
1382
+ raise ValueError('Invalid kill by memory dump flag')
1383
+ ident = uuid.UUID(str(cluster_id)).bytes
1384
+ kind, original = await self.request(13, ident)
1385
+ if kind != 14:
1386
+ raise ValueError('Unexpected cluster info response')
1387
+ raw = original.getvalue()
1388
+ reader = io.BytesIO(raw)
1389
+ await self._parse_cluster(reader)
1390
+ if reader.read() not in (b'', b'\x00') or raw[:16] != ident:
1391
+ raise ValueError('Unknown cluster fields; refusing update')
1392
+ reader.seek(0)
1393
+ read_uuid(reader)
1394
+ read_int32(reader)
1395
+ await read_string(reader) # host
1396
+ read_int32(reader)
1397
+ read_uint16(reader)
1398
+ read_int32(reader)
1399
+ read_int32(reader)
1400
+ await read_string(reader) # name
1401
+ for _ in range(4):
1402
+ read_int32(reader) # security, fault, balancing, errors
1403
+ read_bool(reader) # kill problem processes
1404
+ offset = reader.tell()
1405
+ value = raw[:offset] + bytes([int(enabled)]) + raw[offset+1:]
1406
+ auth = (b'\x00\x00' if agent_os_user is None else
1407
+ varint_prefixed_data_base64(agent_os_user.encode()) + b'\x00')
1408
+ await self._send_auth(8, auth)
1409
+ kind, payload = await self.request(15, value)
1410
+ if kind != 16 or payload.read() != ident:
1411
+ raise ValueError('Unexpected cluster update response')
1412
+
1413
+ async def update_cluster_kill_problem_processes(self, cluster_id, enabled, *, agent_os_user=None):
1414
+ if type(enabled) is not bool:
1415
+ raise ValueError('Invalid kill problem processes flag')
1416
+ ident = uuid.UUID(str(cluster_id)).bytes
1417
+ kind, original = await self.request(13, ident)
1418
+ if kind != 14:
1419
+ raise ValueError('Unexpected cluster info response')
1420
+ raw = original.getvalue()
1421
+ reader = io.BytesIO(raw)
1422
+ await self._parse_cluster(reader)
1423
+ if reader.read() not in (b'', b'\x00') or raw[:16] != ident:
1424
+ raise ValueError('Unknown cluster fields; refusing update')
1425
+ reader.seek(0)
1426
+ read_uuid(reader)
1427
+ read_int32(reader)
1428
+ await read_string(reader) # host
1429
+ read_int32(reader)
1430
+ read_uint16(reader)
1431
+ read_int32(reader)
1432
+ read_int32(reader)
1433
+ await read_string(reader) # name
1434
+ for _ in range(4):
1435
+ read_int32(reader) # security, fault, balancing, errors
1436
+ offset = reader.tell()
1437
+ value = raw[:offset] + bytes([int(enabled)]) + raw[offset+1:]
1438
+ auth = (b'\x00\x00' if agent_os_user is None else
1439
+ varint_prefixed_data_base64(agent_os_user.encode()) + b'\x00')
1440
+ await self._send_auth(8, auth)
1441
+ kind, payload = await self.request(15, value)
1442
+ if kind != 16 or payload.read() != ident:
1443
+ raise ValueError('Unexpected cluster update response')
1444
+
1445
+ async def update_cluster_load_balancing_mode(self, cluster_id, mode, *, agent_os_user=None):
1446
+ if mode not in ('performance', 'memory'):
1447
+ raise ValueError('Invalid cluster load balancing mode')
1448
+ ident = uuid.UUID(str(cluster_id)).bytes
1449
+ kind, original = await self.request(13, ident)
1450
+ if kind != 14:
1451
+ raise ValueError('Unexpected cluster info response')
1452
+ raw = original.getvalue()
1453
+ reader = io.BytesIO(raw)
1454
+ await self._parse_cluster(reader)
1455
+ if reader.read() not in (b'', b'\x00') or raw[:16] != ident:
1456
+ raise ValueError('Unknown cluster fields; refusing update')
1457
+ reader.seek(0)
1458
+ read_uuid(reader)
1459
+ read_int32(reader)
1460
+ await read_string(reader) # host
1461
+ read_int32(reader)
1462
+ read_uint16(reader)
1463
+ read_int32(reader)
1464
+ read_int32(reader)
1465
+ await read_string(reader) # name
1466
+ read_int32(reader) # security level
1467
+ read_int32(reader) # fault tolerance level
1468
+ offset = reader.tell()
1469
+ value = raw[:offset] + write_int32(int(mode == 'memory')) + raw[offset+4:]
1470
+ auth = (b'\x00\x00' if agent_os_user is None else
1471
+ varint_prefixed_data_base64(agent_os_user.encode()) + b'\x00')
1472
+ await self._send_auth(8, auth)
1473
+ kind, payload = await self.request(15, value)
1474
+ if kind != 16 or payload.read() != ident:
1475
+ raise ValueError('Unexpected cluster update response')
1476
+
1477
+ async def remove_cluster(self, cluster_id, cluster_os_user=None):
1478
+ ident = uuid.UUID(str(cluster_id)).bytes
1479
+ if cluster_os_user is None:
1480
+ await self.authenticate_cluster(cluster_id)
1481
+ else:
1482
+ await self.authenticate_cluster(cluster_id, cluster_os_user=cluster_os_user)
1483
+ await self._exchange(self._send_ack_within_deadline, 17, ident,
1484
+ 'Unexpected cluster remove response')
1485
+
1486
+ async def insert_cluster(self, host, port, name='', *, expiration_timeout=60, security_level=0,
1487
+ session_fault_tolerance_level=0, load_balancing_mode='performance',
1488
+ kill_problem_processes=True, kill_by_memory_with_dump=False,
1489
+ restart_schedule='', ping_period=0, ping_timeout=0, agent_os_user=None,
1490
+ audit=False):
1491
+ if type(audit) is not bool:
1492
+ raise ValueError('Invalid cluster audit flag')
1493
+ value = (bytes(16) + write_int32(expiration_timeout)
1494
+ + varint_prefixed_data_base64(host.encode('utf-8'))
1495
+ + write_int32(0) + write_uint16(port)
1496
+ + write_int32(0) * 2
1497
+ + varint_prefixed_data_base64(name.encode('utf-8'))
1498
+ + write_int32(security_level)
1499
+ + write_int32(session_fault_tolerance_level)
1500
+ + write_int32(0 if load_balancing_mode == 'performance' else 1)
1501
+ + write_int32(0) + (b'\x01' if kill_problem_processes else b'\x00')
1502
+ + (b'\x01' if kill_by_memory_with_dump else b'\x00')
1503
+ + write_int32(0) + bytes([audit])
1504
+ + write_int32(ping_period) + write_int32(ping_timeout)
1505
+ + varint_prefixed_data_base64(restart_schedule.encode('utf-8')))
1506
+ auth = (b'\x00\x00' if agent_os_user is None else
1507
+ varint_prefixed_data_base64(agent_os_user.encode()) + b'\x00')
1508
+ await self._send_auth(8, auth)
1509
+ kind, payload = await self.request(15, value)
1510
+ ident = payload.read()
1511
+ if kind != 16 or len(ident) != 16 or ident == bytes(16):
1512
+ raise ValueError('Unexpected cluster insert response')
1513
+ return uuid.UUID(bytes=ident)
1514
+
1515
+ async def update_cluster_audit(self, cluster_id, enabled, *, agent_os_user=None):
1516
+ ident = uuid.UUID(str(cluster_id)).bytes
1517
+ kind, original = await self.request(13, ident)
1518
+ if kind != 14:
1519
+ raise ValueError('Unexpected cluster info response')
1520
+ raw = original.getvalue()
1521
+ reader = io.BytesIO(raw)
1522
+ await self._parse_cluster(reader)
1523
+ if reader.read() not in (b'', b'\x00') or raw[:16] != ident:
1524
+ raise ValueError('Unknown cluster fields; refusing update')
1525
+ reader.seek(0)
1526
+ await read_cluster(reader)
1527
+ if read_int32(reader) != 1:
1528
+ raise ValueError('Unexpected cluster audit marker')
1529
+ offset = reader.tell()
1530
+ if raw[offset:offset + 1] not in (b'\x00', b'\x01'):
1531
+ raise ValueError('Unexpected cluster audit flag')
1532
+ auth = (b'\x00\x00' if agent_os_user is None else
1533
+ varint_prefixed_data_base64(agent_os_user.encode()) + b'\x00')
1534
+ await self._send_auth(8, auth)
1535
+ kind, payload = await self.request(15, raw[:offset] + bytes([enabled]) + raw[offset + 1:])
1536
+ if kind != 16 or payload.read() != ident:
1537
+ raise ValueError('Unexpected cluster update response')
1538
+
1539
+ async def update_cluster_ping(self, cluster_id, period, timeout, *, agent_os_user=None):
1540
+ ident = uuid.UUID(str(cluster_id)).bytes
1541
+ kind, original = await self.request(13, ident)
1542
+ if kind != 14:
1543
+ raise ValueError('Unexpected cluster info response')
1544
+ raw = original.getvalue()
1545
+ reader = io.BytesIO(raw)
1546
+ await self._parse_cluster(reader)
1547
+ if reader.read() not in (b'', b'\x00') or raw[:16] != ident:
1548
+ raise ValueError('Unknown cluster fields; refusing update')
1549
+ reader.seek(0)
1550
+ await read_cluster(reader)
1551
+ if read_int32(reader) != 1:
1552
+ raise ValueError('Unexpected cluster audit marker')
1553
+ if reader.read(1) not in (b'\x00', b'\x01'):
1554
+ raise ValueError('Unexpected cluster audit flag')
1555
+ offset = reader.tell()
1556
+ value = (raw[:offset] + write_int32(period) + write_int32(timeout)
1557
+ + raw[offset+8:])
1558
+ auth = (b'\x00\x00' if agent_os_user is None else
1559
+ varint_prefixed_data_base64(agent_os_user.encode()) + b'\x00')
1560
+ await self._send_auth(8, auth)
1561
+ kind, payload = await self.request(15, value)
1562
+ if kind != 16 or payload.read() != ident:
1563
+ raise ValueError('Unexpected cluster update response')
1564
+
1565
+ async def update_cluster_session_fault_tolerance_level(self, cluster_id, level, *, agent_os_user=None):
1566
+ ident = uuid.UUID(str(cluster_id)).bytes
1567
+ kind, original = await self.request(13, ident)
1568
+ if kind != 14:
1569
+ raise ValueError('Unexpected cluster info response')
1570
+ raw = original.getvalue()
1571
+ reader = io.BytesIO(raw)
1572
+ await self._parse_cluster(reader)
1573
+ if reader.read() not in (b'', b'\x00') or raw[:16] != ident:
1574
+ raise ValueError('Unknown cluster fields; refusing update')
1575
+ reader.seek(0)
1576
+ read_uuid(reader)
1577
+ read_int32(reader)
1578
+ await read_string(reader) # host
1579
+ read_int32(reader)
1580
+ read_uint16(reader)
1581
+ read_int32(reader)
1582
+ read_int32(reader)
1583
+ await read_string(reader) # name
1584
+ read_int32(reader) # security level
1585
+ offset = reader.tell()
1586
+ value = raw[:offset] + write_int32(level) + raw[offset+4:]
1587
+ auth = (b'\x00\x00' if agent_os_user is None else
1588
+ varint_prefixed_data_base64(agent_os_user.encode()) + b'\x00')
1589
+ await self._send_auth(8, auth)
1590
+ kind, payload = await self.request(15, value)
1591
+ if kind != 16 or payload.read() != ident:
1592
+ raise ValueError('Unexpected cluster update response')
1593
+
1594
+ async def update_cluster_security_level(self, cluster_id, level, *, agent_os_user=None):
1595
+ ident = uuid.UUID(str(cluster_id)).bytes
1596
+ kind, original = await self.request(13, ident)
1597
+ if kind != 14:
1598
+ raise ValueError('Unexpected cluster info response')
1599
+ raw = original.getvalue()
1600
+ reader = io.BytesIO(raw)
1601
+ await self._parse_cluster(reader)
1602
+ if reader.read() not in (b'', b'\x00') or raw[:16] != ident:
1603
+ raise ValueError('Unknown cluster fields; refusing update')
1604
+ reader.seek(0)
1605
+ read_uuid(reader)
1606
+ read_int32(reader)
1607
+ await read_string(reader) # host
1608
+ read_int32(reader)
1609
+ read_uint16(reader)
1610
+ read_int32(reader)
1611
+ read_int32(reader)
1612
+ await read_string(reader) # name
1613
+ offset = reader.tell()
1614
+ value = raw[:offset] + write_int32(level) + raw[offset+4:]
1615
+ auth = (b'\x00\x00' if agent_os_user is None else
1616
+ varint_prefixed_data_base64(agent_os_user.encode()) + b'\x00')
1617
+ await self._send_auth(8, auth)
1618
+ kind, payload = await self.request(15, value)
1619
+ if kind != 16 or payload.read() != ident:
1620
+ raise ValueError('Unexpected cluster update response')
1621
+
1622
+ async def update_cluster_expiration_timeout(self, cluster_id, seconds, *, agent_os_user=None):
1623
+ ident = uuid.UUID(str(cluster_id)).bytes
1624
+ kind, original = await self.request(13, ident)
1625
+ if kind != 14:
1626
+ raise ValueError('Unexpected cluster info response')
1627
+ raw = original.getvalue()
1628
+ reader = io.BytesIO(raw)
1629
+ await self._parse_cluster(reader)
1630
+ if reader.read() not in (b'', b'\x00'):
1631
+ raise ValueError('Unknown cluster fields; refusing update')
1632
+ if raw[:16] != ident:
1633
+ raise ValueError('Unexpected cluster identifier')
1634
+ value = raw[:16] + write_int32(seconds) + raw[20:]
1635
+ auth = (b'\x00\x00' if agent_os_user is None else
1636
+ varint_prefixed_data_base64(agent_os_user.encode()) + b'\x00')
1637
+ await self._send_auth(8, auth)
1638
+ kind, payload = await self.request(15, value)
1639
+ if kind != 16 or payload.read() != ident:
1640
+ raise ValueError('Unexpected cluster update response')
1641
+
1642
+ async def update_cluster_name(self, cluster_id, name, *, agent_os_user=None):
1643
+ ident = uuid.UUID(str(cluster_id)).bytes
1644
+ kind, original = await self.request(13, ident)
1645
+ if kind != 14:
1646
+ raise ValueError('Unexpected cluster info response')
1647
+ raw = original.getvalue()
1648
+ reader = io.BytesIO(raw)
1649
+ await self._parse_cluster(reader)
1650
+ if reader.read() not in (b'', b'\x00'):
1651
+ raise ValueError('Unknown cluster fields; refusing update')
1652
+ reader.seek(16)
1653
+ read_int32(reader) # expiration timeout
1654
+ await read_string(reader) # host
1655
+ read_int32(reader) # lifetime limit
1656
+ read_uint16(reader) # port
1657
+ read_int32(reader) # max memory size
1658
+ read_int32(reader) # max memory time limit
1659
+ start = reader.tell()
1660
+ await read_string(reader) # old name
1661
+ end = reader.tell()
1662
+ value = raw[:start] + varint_prefixed_data_base64(name.encode('utf-8')) + raw[end:]
1663
+ auth = (b'\x00\x00' if agent_os_user is None else
1664
+ varint_prefixed_data_base64(agent_os_user.encode()) + b'\x00')
1665
+ await self._send_auth(8, auth)
1666
+ kind, payload = await self.request(15, value)
1667
+ if kind != 16 or payload.read() != ident:
1668
+ raise ValueError('Unexpected cluster update response')
1669
+
1670
+ async def update_cluster_schedule(self, cluster_id, schedule, *, agent_os_user=None):
1671
+ ident = uuid.UUID(str(cluster_id)).bytes
1672
+ kind, original = await self.request(13, ident)
1673
+ if kind != 14:
1674
+ raise ValueError('Unexpected cluster info response')
1675
+ raw = original.getvalue()
1676
+ reader = io.BytesIO(raw)
1677
+ await self._parse_cluster(reader)
1678
+ if reader.read() not in (b'', b'\x00'):
1679
+ raise ValueError('Unknown cluster fields; refusing update')
1680
+ auth = (b'\x00\x00' if agent_os_user is None else
1681
+ varint_prefixed_data_base64(agent_os_user.encode()) + b'\x00')
1682
+ await self._send_auth(8, auth)
1683
+ reader.seek(0)
1684
+ await read_cluster(reader)
1685
+ if read_int32(reader) != 1 or reader.read(1) not in (b'\x00', b'\x01'):
1686
+ raise ValueError('Unexpected cluster audit fields')
1687
+ read_int32(reader) # ping period
1688
+ read_int32(reader) # ping timeout
1689
+ start = reader.tell()
1690
+ old_schedule = await read_string(reader)
1691
+ remaining = reader.read()
1692
+ if remaining not in (b'', b'\x00'):
1693
+ extra = io.BytesIO(remaining)
1694
+ await read_string(extra)
1695
+ if extra.read():
1696
+ raise ValueError('Unknown cluster tail; refusing update')
1697
+ value = raw[:start] + b'\x00' + varint_prefixed_data_base64(schedule.encode())
1698
+ kind, payload = await self.request(15, value)
1699
+ if kind != 16 or payload.read() != ident:
1700
+ raise ValueError('Unexpected cluster update response')
1701
+
1702
+ async def _send_auth(self, message_type, data):
1703
+ if type(message_type) is not int or not 0 <= message_type <= 255:
1704
+ raise ValueError('Invalid RAC message type')
1705
+ if not isinstance(data, bytes):
1706
+ raise ValueError('Invalid RAC request payload')
1707
+ if len(data) > 4_000_000 - 5:
1708
+ raise ValueError('Invalid frame length')
1709
+ await self._exchange(self._send_auth_within_deadline, message_type, data)
1710
+
1711
+ async def _send_auth_within_deadline(self, message_type, data):
1712
+ self.writer.write(frame(14, b'\x01\x00\x00\x01' + bytes([message_type]) + data))
1713
+ await asyncio.wait_for(self.writer.drain(), self.timeout)
1714
+ if await self.read_frame() != (14, b'\x01\x00\x00\x00'):
1715
+ raise ValueError('Authentication rejected')
1716
+
1717
+ async def update_server_port_ranges(self, cluster_id, server_id, ranges):
1718
+ cluster = uuid.UUID(str(cluster_id)).bytes
1719
+ server = uuid.UUID(str(server_id)).bytes
1720
+ await self.authenticate_cluster(cluster_id)
1721
+ kind, original = await self.request(24, cluster + server)
1722
+ if kind != 25:
1723
+ raise ValueError('Unexpected server info response')
1724
+ raw = original.getvalue()
1725
+ reader = io.BytesIO(raw)
1726
+ await self._parse_server(reader)
1727
+ if reader.read() not in (b'', b'\x00') or raw[:16] != server:
1728
+ raise ValueError('Unknown server fields; refusing update')
1729
+ reader.seek(16)
1730
+ await read_string(reader) # host
1731
+ read_uint16(reader)
1732
+ await read_string(reader) # name
1733
+ read_bool(reader)
1734
+ read_int64(reader)
1735
+ read_int64(reader)
1736
+ read_int32(reader)
1737
+ read_int64(reader)
1738
+ read_int32(reader)
1739
+ read_uint16(reader)
1740
+ read_bool(reader)
1741
+ start = reader.tell()
1742
+ await read_port_ranges(reader)
1743
+ end = reader.tell()
1744
+ encoded = pack_varint_base128(len(ranges))
1745
+ encoded += b''.join(write_uint16(maximum) + write_uint16(minimum)
1746
+ for minimum, maximum in ranges)
1747
+ value = cluster + raw[:start] + encoded + raw[end:] + b'\x00'
1748
+ kind, payload = await self.request(26, value)
1749
+ if kind != 27 or payload.read() != server:
1750
+ raise ValueError('Unexpected server update response')
1751
+
1752
+ async def update_server_dedicate_managers(self, cluster_id, server_id, choice):
1753
+ cluster = uuid.UUID(str(cluster_id)).bytes
1754
+ server = uuid.UUID(str(server_id)).bytes
1755
+ await self.authenticate_cluster(cluster_id)
1756
+ kind, original = await self.request(24, cluster + server)
1757
+ if kind != 25:
1758
+ raise ValueError('Unexpected server info response')
1759
+ raw = original.getvalue()
1760
+ reader = io.BytesIO(raw)
1761
+ await self._parse_server(reader)
1762
+ if reader.read() not in (b'', b'\x00') or raw[:16] != server:
1763
+ raise ValueError('Unknown server fields; refusing update')
1764
+ reader.seek(16)
1765
+ await read_string(reader) # host
1766
+ read_uint16(reader)
1767
+ await read_string(reader) # name
1768
+ read_bool(reader)
1769
+ read_int64(reader)
1770
+ read_int64(reader)
1771
+ read_int32(reader)
1772
+ read_int64(reader)
1773
+ read_int32(reader)
1774
+ read_uint16(reader)
1775
+ offset = reader.tell()
1776
+ value = (cluster + raw[:offset] + (b'\x01' if choice == 'all' else b'\x00')
1777
+ + raw[offset+1:] + b'\x00')
1778
+ kind, payload = await self.request(26, value)
1779
+ if kind != 27 or payload.read() != server:
1780
+ raise ValueError('Unexpected server update response')
1781
+
1782
+ async def update_server_temporary_allowed_total_memory_time_limit(self, cluster_id, server_id, limit):
1783
+ cluster = uuid.UUID(str(cluster_id)).bytes
1784
+ server = uuid.UUID(str(server_id)).bytes
1785
+ await self.authenticate_cluster(cluster_id)
1786
+ kind, original = await self.request(24, cluster + server)
1787
+ if kind != 25:
1788
+ raise ValueError('Unexpected server info response')
1789
+ raw = original.getvalue()
1790
+ reader = io.BytesIO(raw)
1791
+ await self._parse_server(reader)
1792
+ if reader.read() not in (b'', b'\x00') or raw[:16] != server:
1793
+ raise ValueError('Unknown server fields; refusing update')
1794
+ reader.seek(16)
1795
+ await read_string(reader) # host
1796
+ read_uint16(reader)
1797
+ await read_string(reader) # name
1798
+ read_bool(reader)
1799
+ read_int64(reader)
1800
+ read_int64(reader)
1801
+ read_int32(reader)
1802
+ read_int64(reader)
1803
+ read_int32(reader)
1804
+ read_uint16(reader)
1805
+ read_bool(reader)
1806
+ await read_port_ranges(reader)
1807
+ read_int64(reader) # critical processes total memory
1808
+ read_int64(reader) # temporary allowed processes total memory
1809
+ offset = reader.tell()
1810
+ value = (cluster + raw[:offset] + write_int64(limit)
1811
+ + raw[offset+8:] + b'\x00')
1812
+ kind, payload = await self.request(26, value)
1813
+ if kind != 27 or payload.read() != server:
1814
+ raise ValueError('Unexpected server update response')
1815
+
1816
+ async def update_server_temporary_allowed_total_memory(self, cluster_id, server_id, limit):
1817
+ cluster = uuid.UUID(str(cluster_id)).bytes
1818
+ server = uuid.UUID(str(server_id)).bytes
1819
+ await self.authenticate_cluster(cluster_id)
1820
+ kind, original = await self.request(24, cluster + server)
1821
+ if kind != 25:
1822
+ raise ValueError('Unexpected server info response')
1823
+ raw = original.getvalue()
1824
+ reader = io.BytesIO(raw)
1825
+ await self._parse_server(reader)
1826
+ if reader.read() not in (b'', b'\x00') or raw[:16] != server:
1827
+ raise ValueError('Unknown server fields; refusing update')
1828
+ reader.seek(16)
1829
+ await read_string(reader) # host
1830
+ read_uint16(reader)
1831
+ await read_string(reader) # name
1832
+ read_bool(reader)
1833
+ read_int64(reader)
1834
+ read_int64(reader)
1835
+ read_int32(reader)
1836
+ read_int64(reader)
1837
+ read_int32(reader)
1838
+ read_uint16(reader)
1839
+ read_bool(reader)
1840
+ await read_port_ranges(reader)
1841
+ read_int64(reader) # critical processes total memory
1842
+ offset = reader.tell()
1843
+ value = (cluster + raw[:offset] + write_int64(limit)
1844
+ + raw[offset+8:] + b'\x00')
1845
+ kind, payload = await self.request(26, value)
1846
+ if kind != 27 or payload.read() != server:
1847
+ raise ValueError('Unexpected server update response')
1848
+
1849
+ async def update_server_critical_total_memory(self, cluster_id, server_id, limit):
1850
+ cluster = uuid.UUID(str(cluster_id)).bytes
1851
+ server = uuid.UUID(str(server_id)).bytes
1852
+ await self.authenticate_cluster(cluster_id)
1853
+ kind, original = await self.request(24, cluster + server)
1854
+ if kind != 25:
1855
+ raise ValueError('Unexpected server info response')
1856
+ raw = original.getvalue()
1857
+ reader = io.BytesIO(raw)
1858
+ await self._parse_server(reader)
1859
+ if reader.read() not in (b'', b'\x00') or raw[:16] != server:
1860
+ raise ValueError('Unknown server fields; refusing update')
1861
+ reader.seek(0)
1862
+ await read_server(reader)
1863
+ reader.seek(16)
1864
+ await read_string(reader) # host
1865
+ read_uint16(reader)
1866
+ await read_string(reader) # name
1867
+ read_bool(reader)
1868
+ read_int64(reader)
1869
+ read_int64(reader)
1870
+ read_int32(reader)
1871
+ read_int64(reader)
1872
+ read_int32(reader)
1873
+ read_uint16(reader)
1874
+ read_bool(reader)
1875
+ await read_port_ranges(reader)
1876
+ offset = reader.tell()
1877
+ value = (cluster + raw[:offset] + write_int64(limit)
1878
+ + raw[offset+8:] + b'\x00')
1879
+ kind, payload = await self.request(26, value)
1880
+ if kind != 27 or payload.read() != server:
1881
+ raise ValueError('Unexpected server update response')
1882
+
1883
+ async def update_server_service_principal_name(self, cluster_id, server_id, name):
1884
+ cluster = uuid.UUID(str(cluster_id)).bytes
1885
+ server = uuid.UUID(str(server_id)).bytes
1886
+ await self.authenticate_cluster(cluster_id)
1887
+ kind, original = await self.request(24, cluster + server)
1888
+ if kind != 25:
1889
+ raise ValueError('Unexpected server info response')
1890
+ raw = original.getvalue()
1891
+ reader = io.BytesIO(raw)
1892
+ await self._parse_server(reader)
1893
+ if reader.read() not in (b'', b'\x00') or raw[:16] != server:
1894
+ raise ValueError('Unknown server fields; refusing update')
1895
+ reader.seek(0)
1896
+ await read_server(reader)
1897
+ start = reader.tell()
1898
+ await read_string(reader)
1899
+ end = reader.tell()
1900
+ value = (cluster + raw[:start] + varint_prefixed_data_base64(name.encode())
1901
+ + raw[end:] + b'\x00')
1902
+ kind, payload = await self.request(26, value)
1903
+ if kind != 27 or payload.read() != server:
1904
+ raise ValueError('Unexpected server update response')
1905
+
1906
+ async def update_server_safe_call_memory_limit(self, cluster_id, server_id, limit):
1907
+ cluster = uuid.UUID(str(cluster_id)).bytes
1908
+ server = uuid.UUID(str(server_id)).bytes
1909
+ await self.authenticate_cluster(cluster_id)
1910
+ kind, original = await self.request(24, cluster + server)
1911
+ if kind != 25:
1912
+ raise ValueError('Unexpected server info response')
1913
+ raw = original.getvalue()
1914
+ reader = io.BytesIO(raw)
1915
+ await self._parse_server(reader)
1916
+ if reader.read() not in (b'', b'\x00') or raw[:16] != server:
1917
+ raise ValueError('Unknown server fields; refusing update')
1918
+ reader.seek(16)
1919
+ await read_string(reader) # host
1920
+ read_uint16(reader)
1921
+ await read_string(reader) # name
1922
+ read_bool(reader)
1923
+ read_int64(reader) # safe working process memory limit
1924
+ offset = reader.tell()
1925
+ value = (cluster + raw[:offset] + write_int64(limit)
1926
+ + raw[offset+8:] + b'\x00')
1927
+ kind, payload = await self.request(26, value)
1928
+ if kind != 27 or payload.read() != server:
1929
+ raise ValueError('Unexpected server update response')
1930
+
1931
+ async def update_server_connections_limit(self, cluster_id, server_id, limit):
1932
+ cluster = uuid.UUID(str(cluster_id)).bytes
1933
+ server = uuid.UUID(str(server_id)).bytes
1934
+ await self.authenticate_cluster(cluster_id)
1935
+ kind, original = await self.request(24, cluster + server)
1936
+ if kind != 25:
1937
+ raise ValueError('Unexpected server info response')
1938
+ raw = original.getvalue()
1939
+ reader = io.BytesIO(raw)
1940
+ await self._parse_server(reader)
1941
+ if reader.read() not in (b'', b'\x00') or raw[:16] != server:
1942
+ raise ValueError('Unknown server fields; refusing update')
1943
+ reader.seek(16)
1944
+ await read_string(reader) # host
1945
+ read_uint16(reader)
1946
+ await read_string(reader) # name
1947
+ read_bool(reader)
1948
+ read_int64(reader)
1949
+ read_int64(reader)
1950
+ read_int32(reader) # infobases per process
1951
+ read_int64(reader) # working process memory limit
1952
+ offset = reader.tell()
1953
+ value = (cluster + raw[:offset] + write_int32(limit)
1954
+ + raw[offset+4:] + b'\x00')
1955
+ kind, payload = await self.request(26, value)
1956
+ if kind != 27 or payload.read() != server:
1957
+ raise ValueError('Unexpected server update response')
1958
+
1959
+ async def update_server_infobases_limit(self, cluster_id, server_id, limit):
1960
+ cluster = uuid.UUID(str(cluster_id)).bytes
1961
+ server = uuid.UUID(str(server_id)).bytes
1962
+ await self.authenticate_cluster(cluster_id)
1963
+ kind, original = await self.request(24, cluster + server)
1964
+ if kind != 25:
1965
+ raise ValueError('Unexpected server info response')
1966
+ raw = original.getvalue()
1967
+ reader = io.BytesIO(raw)
1968
+ await self._parse_server(reader)
1969
+ if reader.read() not in (b'', b'\x00') or raw[:16] != server:
1970
+ raise ValueError('Unknown server fields; refusing update')
1971
+ reader.seek(16)
1972
+ await read_string(reader) # host
1973
+ read_uint16(reader)
1974
+ await read_string(reader) # name
1975
+ read_bool(reader)
1976
+ read_int64(reader) # safe working processes memory limit
1977
+ read_int64(reader) # safe call memory limit
1978
+ offset = reader.tell()
1979
+ value = (cluster + raw[:offset] + write_int32(limit)
1980
+ + raw[offset+4:] + b'\x00')
1981
+ kind, payload = await self.request(26, value)
1982
+ if kind != 27 or payload.read() != server:
1983
+ raise ValueError('Unexpected server update response')
1984
+
1985
+ async def update_server_schedule(self, cluster_id, server_id, schedule):
1986
+ cluster = uuid.UUID(str(cluster_id)).bytes
1987
+ server = uuid.UUID(str(server_id)).bytes
1988
+ await self.authenticate_cluster(cluster_id)
1989
+ kind, original = await self.request(24, cluster + server)
1990
+ if kind != 25:
1991
+ raise ValueError('Unexpected server info response')
1992
+ raw = original.getvalue()
1993
+ reader = io.BytesIO(raw)
1994
+ await self._parse_server(reader)
1995
+ remaining = reader.read()
1996
+ if remaining not in (b'', b'\x00'):
1997
+ extra = io.BytesIO(remaining)
1998
+ await read_string(extra)
1999
+ if extra.read():
2000
+ raise ValueError('Unknown server fields; refusing update')
2001
+ reader.seek(0)
2002
+ await read_server(reader)
2003
+ await read_string(reader)
2004
+ start = reader.tell()
2005
+ old_schedule = await read_string(reader)
2006
+ remaining = reader.read()
2007
+ if remaining not in (b'', b'\x00'):
2008
+ extra = io.BytesIO(remaining)
2009
+ await read_string(extra)
2010
+ if extra.read():
2011
+ raise ValueError('Unknown server tail; refusing update')
2012
+ value = cluster + raw[:start] + b'\x00' + varint_prefixed_data_base64(schedule.encode()) + b'\x00'
2013
+ kind, payload = await self.request(26, value)
2014
+ if kind != 27 or payload.read() != server:
2015
+ raise ValueError('Unexpected server update response')
2016
+
2017
+ async def _parse_admins(self, payload):
2018
+ count = await unpack_varint_base128(payload)
2019
+ if count is None or count > 1000:
2020
+ raise ValueError('Invalid administrator count')
2021
+ rows = []
2022
+ names, os_users = set(), set()
2023
+ for _ in range(count):
2024
+ name = await read_string(payload)
2025
+ description = await read_string(payload)
2026
+ # The Java 10.0 decoder places an opaque password field here.
2027
+ # Consume it, but never expose its contents in results or errors.
2028
+ await read_bytes(payload)
2029
+ password_auth = payload.read(1)
2030
+ os_auth = payload.read(1)
2031
+ if password_auth not in (b'\x00', b'\x01') or os_auth not in (b'\x00', b'\x01'):
2032
+ raise ValueError('Invalid administrator authentication flags')
2033
+ os_user = await read_string(payload)
2034
+ if name in names or os_user and os_user in os_users:
2035
+ raise ValueError('Unexpected duplicate administrator identity')
2036
+ names.add(name)
2037
+ if os_user:
2038
+ os_users.add(os_user)
2039
+ rows.append({'name': name, 'descr': description,
2040
+ 'password-auth-allowed': password_auth == b'\x01',
2041
+ 'os-auth-allowed': os_auth == b'\x01', 'os-user': os_user})
2042
+ if payload.read():
2043
+ raise ValueError('Unexpected administrator response tail')
2044
+ return rows
2045
+
2046
+ async def list_agent_admins(self, agent_os_user=None, *, agent_password=None):
2047
+ if agent_password is not None:
2048
+ if (not isinstance(agent_os_user, str) or not agent_os_user
2049
+ or '\x00' in agent_os_user or len(agent_os_user.encode('utf-8')) > 1024
2050
+ or not isinstance(agent_password, str) or not agent_password
2051
+ or '\x00' in agent_password or len(agent_password.encode('utf-8')) > 1024):
2052
+ raise ValueError('Invalid agent administrator credentials')
2053
+ auth = (varint_prefixed_data_base64(agent_os_user.encode())
2054
+ + varint_prefixed_data_base64(agent_password.encode()))
2055
+ elif agent_os_user is None:
2056
+ auth = b'\x00\x00'
2057
+ else:
2058
+ if (not isinstance(agent_os_user, str) or not agent_os_user
2059
+ or '\x00' in agent_os_user or len(agent_os_user.encode('utf-8')) > 1024):
2060
+ raise ValueError('Invalid agent administrator name')
2061
+ auth = varint_prefixed_data_base64(agent_os_user.encode()) + b'\x00'
2062
+ await self._send_auth(8, auth)
2063
+ kind, payload = await self.request(0)
2064
+ if kind != 1:
2065
+ raise ValueError('Unexpected agent admin response')
2066
+ return await self._parse_admins(payload)
2067
+
2068
+ async def _send_admin_mutation(self, opcode, record, error):
2069
+ await self._exchange(self._send_ack_within_deadline, opcode, record, error)
2070
+
2071
+ async def register_agent_admin(self, name, os_user, descr='', agent_os_user=None):
2072
+ if agent_os_user is None:
2073
+ auth = b'\x00\x00'
2074
+ else:
2075
+ auth = varint_prefixed_data_base64(agent_os_user.encode()) + b'\x00'
2076
+ await self._send_auth(8, auth)
2077
+ record = (varint_prefixed_data_base64(name.encode())
2078
+ + varint_prefixed_data_base64(descr.encode())
2079
+ + b'\x00\x01\x01'
2080
+ + varint_prefixed_data_base64(os_user.encode()))
2081
+ await self._send_admin_mutation(4, record, 'Unexpected agent administrator registration response')
2082
+
2083
+ async def remove_agent_admin(self, name, agent_os_user):
2084
+ await self._send_auth(8, varint_prefixed_data_base64(agent_os_user.encode()) + b'\x00')
2085
+ await self._send_admin_mutation(6, varint_prefixed_data_base64(name.encode()),
2086
+ 'Unexpected agent administrator removal response')
2087
+
2088
+ async def list_cluster_admins(self, cluster_id, cluster_os_user=None, *, cluster_password=None):
2089
+ cluster = uuid.UUID(str(cluster_id))
2090
+ if cluster_password is not None:
2091
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2092
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
2093
+ or not isinstance(cluster_password, str) or not cluster_password
2094
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
2095
+ raise ValueError('Invalid cluster administrator credentials')
2096
+ await self._send_auth(9, cluster.bytes
2097
+ + varint_prefixed_data_base64(cluster_os_user.encode())
2098
+ + varint_prefixed_data_base64(cluster_password.encode()))
2099
+ elif cluster_os_user is None:
2100
+ await self.authenticate_cluster(cluster)
2101
+ else:
2102
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2103
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2104
+ raise ValueError('Invalid cluster administrator name')
2105
+ await self._send_auth(9, cluster.bytes
2106
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2107
+ kind, payload = await self.request(2, cluster.bytes)
2108
+ if kind != 3:
2109
+ raise ValueError('Unexpected cluster admin response')
2110
+ return await self._parse_admins(payload)
2111
+
2112
+ async def register_cluster_admin(self, cluster_id, name, os_user, descr='', cluster_os_user=None):
2113
+ cluster = uuid.UUID(str(cluster_id)).bytes
2114
+ if cluster_os_user is None:
2115
+ await self.authenticate_cluster(cluster_id)
2116
+ else:
2117
+ await self._send_auth(9, cluster + varint_prefixed_data_base64(
2118
+ cluster_os_user.encode()) + b'\x00')
2119
+ record = (cluster + varint_prefixed_data_base64(name.encode())
2120
+ + varint_prefixed_data_base64(descr.encode())
2121
+ + b'\x00\x01\x01'
2122
+ + varint_prefixed_data_base64(os_user.encode()))
2123
+ await self._send_admin_mutation(5, record, 'Unexpected cluster administrator registration response')
2124
+
2125
+ async def remove_cluster_admin(self, cluster_id, name, cluster_os_user):
2126
+ cluster = uuid.UUID(str(cluster_id)).bytes
2127
+ await self._send_auth(9, cluster + varint_prefixed_data_base64(
2128
+ cluster_os_user.encode()) + b'\x00')
2129
+ await self._send_admin_mutation(7, cluster + varint_prefixed_data_base64(name.encode()),
2130
+ 'Unexpected cluster administrator removal response')
2131
+
2132
+ async def list_empty_only(self, cluster_id, request_id, response_id, operation, infobase_id=None):
2133
+ await self.authenticate_cluster(cluster_id)
2134
+ value = uuid.UUID(str(cluster_id)).bytes
2135
+ if infobase_id is not None:
2136
+ value += uuid.UUID(str(infobase_id)).bytes
2137
+ kind, payload = await self.request(request_id, value)
2138
+ if kind != response_id:
2139
+ raise ValueError('Unexpected ' + operation + ' response')
2140
+ count = await unpack_varint_base128(payload)
2141
+ if count != 0 or payload.read():
2142
+ raise ValueError('Nonempty ' + operation + ' records not decoded')
2143
+ return []
2144
+
2145
+ async def list_sessions(self, cluster_id, infobase_id=None, *, cluster_os_user=None,
2146
+ cluster_password=None):
2147
+ cluster = uuid.UUID(str(cluster_id))
2148
+ infobase = uuid.UUID(str(infobase_id)).bytes if infobase_id is not None else b''
2149
+ if cluster_password is not None:
2150
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2151
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
2152
+ or not isinstance(cluster_password, str) or not cluster_password
2153
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
2154
+ raise ValueError('Invalid cluster administrator credentials')
2155
+ await self._send_auth(9, cluster.bytes
2156
+ + varint_prefixed_data_base64(cluster_os_user.encode())
2157
+ + varint_prefixed_data_base64(cluster_password.encode()))
2158
+ elif cluster_os_user is None:
2159
+ await self.authenticate_cluster(cluster)
2160
+ else:
2161
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2162
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2163
+ raise ValueError('Invalid cluster administrator name')
2164
+ await self._send_auth(9, cluster.bytes
2165
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2166
+ if infobase_id is None:
2167
+ request_type, value = 65, cluster.bytes
2168
+ else:
2169
+ request_type = 67
2170
+ value = cluster.bytes + infobase
2171
+ kind, payload = await self.request(request_type, value)
2172
+ if kind != request_type + 1:
2173
+ raise ValueError('Unexpected session list response')
2174
+ count = await unpack_varint_base128(payload)
2175
+ if count is None or count > 10000:
2176
+ raise ValueError('Invalid session count')
2177
+ rows = [await self._parse_session(payload) for _ in range(count)]
2178
+ if len({row['session_id'] for row in rows}) != count:
2179
+ raise ValueError('Unexpected duplicate session identifier')
2180
+ if infobase_id is not None and any(
2181
+ row.get('infobase_id') != str(uuid.UUID(bytes=infobase)) for row in rows):
2182
+ raise ValueError('Unexpected session infobase')
2183
+ if payload.read():
2184
+ raise ValueError('Unexpected session list tail')
2185
+ return rows
2186
+
2187
+ async def _parse_session(self, payload):
2188
+ try:
2189
+ row = await read_session(payload)
2190
+ except (EOFError, ValueError, TypeError, UnicodeError, OverflowError, IndexError,
2191
+ struct.error):
2192
+ raise ValueError('Invalid session data') from None
2193
+ for key in ('session_id', 'connection_id', 'infobase_id', 'process_id'):
2194
+ row[key] = str(row[key])
2195
+ return row
2196
+
2197
+ async def get_session(self, cluster_id, session_id, *, cluster_os_user=None,
2198
+ cluster_password=None):
2199
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(session_id))
2200
+ if cluster_password is not None:
2201
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2202
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
2203
+ or not isinstance(cluster_password, str) or not cluster_password
2204
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
2205
+ raise ValueError('Invalid cluster administrator credentials')
2206
+ await self._send_auth(9, cluster.bytes
2207
+ + varint_prefixed_data_base64(cluster_os_user.encode())
2208
+ + varint_prefixed_data_base64(cluster_password.encode()))
2209
+ elif cluster_os_user is None:
2210
+ await self.authenticate_cluster(cluster)
2211
+ else:
2212
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2213
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2214
+ raise ValueError('Invalid cluster administrator name')
2215
+ await self._send_auth(9, cluster.bytes
2216
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2217
+ kind, payload = await self.request(69, cluster.bytes + ident.bytes)
2218
+ if kind != 70:
2219
+ raise ValueError('Unexpected session info response')
2220
+ row = await self._parse_session(payload)
2221
+ if row['session_id'] != str(ident) or payload.read():
2222
+ raise ValueError('Unexpected session info data')
2223
+ return row
2224
+
2225
+ async def _parse_profile(self, payload):
2226
+ name = await read_string(payload)
2227
+ descr = await read_string(payload)
2228
+ if not name:
2229
+ raise ValueError('Invalid security profile name')
2230
+ flags = payload.read(10)
2231
+ if len(flags) != 10 or any(flag not in (0, 1) for flag in flags):
2232
+ raise ValueError('Invalid security profile flags')
2233
+ row = {'name': name, 'descr': descr}
2234
+ for key, flag in zip(('directory', 'com', 'addin', 'app', 'inet',
2235
+ 'config', 'priv', 'raw-external-module-access',
2236
+ 'crypto', 'right-extension'), flags):
2237
+ row[key] = bool(flag)
2238
+ row['right-extension-definition-roles'] = await read_string(payload)
2239
+ all_modules = payload.read(1)
2240
+ if all_modules not in (b'\x00', b'\x01'):
2241
+ raise ValueError('Invalid all-modules-extension flag')
2242
+ row['all-modules-extension'] = all_modules == b'\x01'
2243
+ for key in ('modules-available-for-extension',
2244
+ 'modules-not-available-for-extension', 'privileged-mode-roles'):
2245
+ row[key] = await read_string(payload)
2246
+ return row
2247
+
2248
+ async def list_profiles(self, cluster_id, *, cluster_os_user=None, cluster_password=None):
2249
+ cluster = uuid.UUID(str(cluster_id))
2250
+ if cluster_password is not None:
2251
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2252
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
2253
+ or not isinstance(cluster_password, str) or not cluster_password
2254
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
2255
+ raise ValueError('Invalid cluster administrator credentials')
2256
+ await self._send_auth(9, cluster.bytes
2257
+ + varint_prefixed_data_base64(cluster_os_user.encode())
2258
+ + varint_prefixed_data_base64(cluster_password.encode()))
2259
+ elif cluster_os_user is None:
2260
+ await self.authenticate_cluster(cluster_id)
2261
+ else:
2262
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2263
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2264
+ raise ValueError('Invalid cluster administrator name')
2265
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2266
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2267
+ kind, payload = await self.request(89, cluster.bytes)
2268
+ if kind != 90:
2269
+ raise ValueError('Unexpected profile response')
2270
+ count = await unpack_varint_base128(payload)
2271
+ if count is None or count > 10000:
2272
+ raise ValueError('Invalid profile count')
2273
+ rows = [await self._parse_profile(payload) for _ in range(count)]
2274
+ if len({row['name'] for row in rows}) != count:
2275
+ raise ValueError('Unexpected duplicate profile name')
2276
+ if payload.read():
2277
+ raise ValueError('Unexpected profile response tail')
2278
+ return rows
2279
+
2280
+ async def update_profile(self, cluster_id, row, *, cluster_os_user=None):
2281
+ name = row.get('name') if isinstance(row, dict) else None
2282
+ if (not isinstance(name, str) or not name.strip()
2283
+ or '\x00' in name or len(name.encode('utf-8')) > 1024):
2284
+ raise ValueError('Invalid security profile name')
2285
+ flags = ('directory', 'com', 'addin', 'app', 'inet', 'config', 'priv',
2286
+ 'raw-external-module-access', 'crypto', 'right-extension')
2287
+ strings = ('descr', 'right-extension-definition-roles',
2288
+ 'modules-available-for-extension',
2289
+ 'modules-not-available-for-extension', 'privileged-mode-roles')
2290
+ if set(row) - {'name', *flags, *strings, 'all-modules-extension'}:
2291
+ raise ValueError('Unknown security profile field')
2292
+ if any(type(row.get(key, False)) is not bool for key in (*flags, 'all-modules-extension')):
2293
+ raise ValueError('Invalid security profile flag')
2294
+ if any(not isinstance(row.get(key, ''), str) or '\x00' in row.get(key, '')
2295
+ or len(row.get(key, '').encode('utf-8')) > 1024 for key in strings):
2296
+ raise ValueError('Invalid security profile string')
2297
+ def text(key):
2298
+ return varint_prefixed_data_base64(row.get(key, '').encode('utf-8'))
2299
+ value = (uuid.UUID(str(cluster_id)).bytes + text('name') + text('descr')
2300
+ + bytes(int(row.get(key, False)) for key in flags)
2301
+ + text('right-extension-definition-roles')
2302
+ + bytes([int(row.get('all-modules-extension', False))])
2303
+ + text('modules-available-for-extension')
2304
+ + text('modules-not-available-for-extension')
2305
+ + text('privileged-mode-roles'))
2306
+ if cluster_os_user is None:
2307
+ await self.authenticate_cluster(cluster_id)
2308
+ else:
2309
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2310
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2311
+ raise ValueError('Invalid cluster administrator name')
2312
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2313
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2314
+ await self._send_auth(91, value)
2315
+
2316
+ async def remove_profile(self, cluster_id, profile_name, *, cluster_os_user=None):
2317
+ uuid.UUID(str(cluster_id))
2318
+ if (not isinstance(profile_name, str) or not profile_name.strip()
2319
+ or '\x00' in profile_name or len(profile_name.encode('utf-8')) > 1024):
2320
+ raise ValueError('Invalid security profile name')
2321
+ if cluster_os_user is None:
2322
+ await self.authenticate_cluster(cluster_id)
2323
+ else:
2324
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2325
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2326
+ raise ValueError('Invalid cluster administrator name')
2327
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2328
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2329
+ value = (uuid.UUID(str(cluster_id)).bytes
2330
+ + varint_prefixed_data_base64(profile_name.encode('utf-8')))
2331
+ await self._send_auth(92, value)
2332
+
2333
+ async def update_profile_directory(self, cluster_id, row, *, cluster_os_user=None):
2334
+ if not isinstance(row, dict) or set(row) != {
2335
+ 'profile', 'alias', 'physicalPath', 'allowedRead', 'allowedWrite', 'descr'}:
2336
+ raise ValueError('Invalid profile directory record')
2337
+ for key in ('profile', 'alias', 'physicalPath', 'descr'):
2338
+ value = row[key]
2339
+ if (not isinstance(value, str) or '\x00' in value
2340
+ or len(value.encode('utf-8')) > 1024
2341
+ or key in ('profile', 'alias') and not value.strip()):
2342
+ raise ValueError('Invalid profile directory field: ' + key)
2343
+ if any(type(row[key]) is not bool for key in ('allowedRead', 'allowedWrite')):
2344
+ raise ValueError('Invalid profile directory access flags')
2345
+ def text(key):
2346
+ return varint_prefixed_data_base64(row[key].encode('utf-8'))
2347
+ value = (uuid.UUID(str(cluster_id)).bytes + text('profile') + text('alias')
2348
+ + text('physicalPath')
2349
+ + bytes([int(row['allowedRead']), int(row['allowedWrite'])]) + text('descr'))
2350
+ if cluster_os_user is None:
2351
+ await self.authenticate_cluster(cluster_id)
2352
+ else:
2353
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2354
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2355
+ raise ValueError('Invalid cluster administrator name')
2356
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2357
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2358
+ await self._send_auth(95, value)
2359
+
2360
+ async def remove_profile_directory(self, cluster_id, profile_name, alias, *, cluster_os_user=None):
2361
+ uuid.UUID(str(cluster_id))
2362
+ for value in (profile_name, alias):
2363
+ if (not isinstance(value, str) or not value.strip()
2364
+ or '\x00' in value or len(value.encode('utf-8')) > 1024):
2365
+ raise ValueError('Invalid profile or directory alias')
2366
+ if cluster_os_user is None:
2367
+ await self.authenticate_cluster(cluster_id)
2368
+ else:
2369
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2370
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2371
+ raise ValueError('Invalid cluster administrator name')
2372
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2373
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2374
+ value = (uuid.UUID(str(cluster_id)).bytes
2375
+ + varint_prefixed_data_base64(profile_name.encode('utf-8'))
2376
+ + varint_prefixed_data_base64(alias.encode('utf-8')))
2377
+ await self._send_auth(96, value)
2378
+
2379
+ async def list_profile_directories(self, cluster_id, profile_name, *, cluster_os_user=None,
2380
+ cluster_password=None):
2381
+ cluster = uuid.UUID(str(cluster_id))
2382
+ if (not isinstance(profile_name, str) or not profile_name or '\x00' in profile_name
2383
+ or len(profile_name.encode('utf-8')) > 1024):
2384
+ raise ValueError('Invalid security profile name')
2385
+ if cluster_password is not None:
2386
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2387
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
2388
+ or not isinstance(cluster_password, str) or not cluster_password
2389
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
2390
+ raise ValueError('Invalid cluster administrator credentials')
2391
+ await self._send_auth(9, cluster.bytes
2392
+ + varint_prefixed_data_base64(cluster_os_user.encode())
2393
+ + varint_prefixed_data_base64(cluster_password.encode()))
2394
+ elif cluster_os_user is None:
2395
+ await self.authenticate_cluster(cluster_id)
2396
+ else:
2397
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2398
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2399
+ raise ValueError('Invalid cluster administrator name')
2400
+ await self._send_auth(9, cluster.bytes
2401
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2402
+ value = (cluster.bytes + varint_prefixed_data_base64(profile_name.encode('utf-8')))
2403
+ kind, payload = await self.request(93, value)
2404
+ if kind != 94:
2405
+ raise ValueError('Unexpected profile directory response')
2406
+ count = await unpack_varint_base128(payload)
2407
+ if count is None or count > 10000:
2408
+ raise ValueError('Invalid profile directory count')
2409
+ rows = []
2410
+ for _ in range(count):
2411
+ profile = await read_string(payload)
2412
+ alias = await read_string(payload)
2413
+ path = await read_string(payload)
2414
+ flags = payload.read(2)
2415
+ if profile != profile_name or not alias or len(flags) != 2 or any(x not in (0, 1) for x in flags):
2416
+ raise ValueError('Invalid profile directory record')
2417
+ descr = await read_string(payload)
2418
+ rows.append({'profile': profile, 'alias': alias, 'physicalPath': path,
2419
+ 'allowedRead': bool(flags[0]), 'allowedWrite': bool(flags[1]),
2420
+ 'descr': descr})
2421
+ if len({row['alias'] for row in rows}) != count:
2422
+ raise ValueError('Unexpected duplicate profile directory alias')
2423
+ if payload.read():
2424
+ raise ValueError('Unexpected profile directory tail')
2425
+ return rows
2426
+
2427
+ async def _authenticate_profile_acl_read(self, cluster, cluster_os_user, cluster_password):
2428
+ if cluster_password is not None:
2429
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2430
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
2431
+ or not isinstance(cluster_password, str) or not cluster_password
2432
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
2433
+ raise ValueError('Invalid cluster administrator credentials')
2434
+ await self._send_auth(9, cluster.bytes
2435
+ + varint_prefixed_data_base64(cluster_os_user.encode())
2436
+ + varint_prefixed_data_base64(cluster_password.encode()))
2437
+ elif cluster_os_user is None:
2438
+ await self.authenticate_cluster(cluster)
2439
+ else:
2440
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2441
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2442
+ raise ValueError('Invalid cluster administrator name')
2443
+ await self._send_auth(9, cluster.bytes
2444
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2445
+
2446
+ async def list_profile_com_classes(self, cluster_id, profile_name, *, cluster_os_user=None,
2447
+ cluster_password=None):
2448
+ cluster = uuid.UUID(str(cluster_id))
2449
+ if (not isinstance(profile_name, str) or not profile_name or '\x00' in profile_name
2450
+ or len(profile_name.encode('utf-8')) > 1024):
2451
+ raise ValueError('Invalid security profile name')
2452
+ await self._authenticate_profile_acl_read(cluster, cluster_os_user, cluster_password)
2453
+ value = cluster.bytes + varint_prefixed_data_base64(profile_name.encode('utf-8'))
2454
+ kind, payload = await self.request(97, value)
2455
+ if kind != 98:
2456
+ raise ValueError('Unexpected profile COM response')
2457
+ count = await unpack_varint_base128(payload)
2458
+ if count is None or count > 10000:
2459
+ raise ValueError('Invalid profile COM count')
2460
+ rows = []
2461
+ for _ in range(count):
2462
+ profile = await read_string(payload)
2463
+ name = await read_string(payload)
2464
+ filename = await read_string(payload)
2465
+ ident = await read_string(payload)
2466
+ host = await read_string(payload)
2467
+ descr = await read_string(payload)
2468
+ if profile != profile_name or not name:
2469
+ raise ValueError('Invalid profile COM record')
2470
+ rows.append({'profile': profile, 'name': name, 'fileName': filename,
2471
+ 'id': ident, 'host': host, 'descr': descr})
2472
+ if len({row['name'] for row in rows}) != count:
2473
+ raise ValueError('Unexpected duplicate profile COM name')
2474
+ if payload.read():
2475
+ raise ValueError('Unexpected profile COM tail')
2476
+ return rows
2477
+
2478
+ async def update_profile_com_class(self, cluster_id, row, *, cluster_os_user=None):
2479
+ keys = ('profile', 'name', 'fileName', 'id', 'host', 'descr')
2480
+ if not isinstance(row, dict) or set(row) != set(keys):
2481
+ raise ValueError('Invalid profile COM record')
2482
+ for key in keys:
2483
+ value = row[key]
2484
+ if (not isinstance(value, str) or '\x00' in value
2485
+ or len(value.encode('utf-8')) > 1024
2486
+ or key in ('profile', 'name') and not value.strip()):
2487
+ raise ValueError('Invalid profile COM field: ' + key)
2488
+ def text(key):
2489
+ return varint_prefixed_data_base64(row[key].encode('utf-8'))
2490
+ value = uuid.UUID(str(cluster_id)).bytes + b''.join(text(key) for key in keys)
2491
+ if cluster_os_user is None:
2492
+ await self.authenticate_cluster(cluster_id)
2493
+ else:
2494
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2495
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2496
+ raise ValueError('Invalid cluster administrator name')
2497
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2498
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2499
+ await self._send_auth(99, value)
2500
+
2501
+ async def remove_profile_com_class(self, cluster_id, profile_name, class_name, *,
2502
+ cluster_os_user=None):
2503
+ uuid.UUID(str(cluster_id))
2504
+ for value in (profile_name, class_name):
2505
+ if (not isinstance(value, str) or not value.strip()
2506
+ or '\x00' in value or len(value.encode('utf-8')) > 1024):
2507
+ raise ValueError('Invalid profile or COM class name')
2508
+ if cluster_os_user is None:
2509
+ await self.authenticate_cluster(cluster_id)
2510
+ else:
2511
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2512
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2513
+ raise ValueError('Invalid cluster administrator name')
2514
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2515
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2516
+ value = (uuid.UUID(str(cluster_id)).bytes
2517
+ + varint_prefixed_data_base64(profile_name.encode('utf-8'))
2518
+ + varint_prefixed_data_base64(class_name.encode('utf-8')))
2519
+ await self._send_auth(100, value)
2520
+
2521
+ async def list_profile_addins(self, cluster_id, profile_name, *, cluster_os_user=None,
2522
+ cluster_password=None):
2523
+ return await self._list_profile_hash_acl(cluster_id, profile_name, 101, 'addin',
2524
+ cluster_os_user=cluster_os_user,
2525
+ cluster_password=cluster_password)
2526
+
2527
+ async def update_profile_addin(self, cluster_id, row, *, cluster_os_user=None):
2528
+ if not isinstance(row, dict) or set(row) != {'profile', 'name', 'hash', 'descr'}:
2529
+ raise ValueError('Invalid profile addin record')
2530
+ for key in ('profile', 'name', 'hash', 'descr'):
2531
+ value = row[key]
2532
+ if (not isinstance(value, str) or '\x00' in value
2533
+ or len(value.encode('utf-8')) > 1024
2534
+ or key in ('profile', 'name') and not value.strip()):
2535
+ raise ValueError('Invalid profile addin field: ' + key)
2536
+ def text(key):
2537
+ return varint_prefixed_data_base64(row[key].encode('utf-8'))
2538
+ value = (uuid.UUID(str(cluster_id)).bytes + text('profile') + text('name')
2539
+ + text('hash') + text('descr'))
2540
+ if cluster_os_user is None:
2541
+ await self.authenticate_cluster(cluster_id)
2542
+ else:
2543
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2544
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2545
+ raise ValueError('Invalid cluster administrator name')
2546
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2547
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2548
+ await self._send_auth(103, value)
2549
+
2550
+ async def remove_profile_addin(self, cluster_id, profile_name, addin_name, *,
2551
+ cluster_os_user=None):
2552
+ uuid.UUID(str(cluster_id))
2553
+ for value in (profile_name, addin_name):
2554
+ if (not isinstance(value, str) or not value.strip()
2555
+ or '\x00' in value or len(value.encode('utf-8')) > 1024):
2556
+ raise ValueError('Invalid profile or addin name')
2557
+ if cluster_os_user is None:
2558
+ await self.authenticate_cluster(cluster_id)
2559
+ else:
2560
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2561
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2562
+ raise ValueError('Invalid cluster administrator name')
2563
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2564
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2565
+ value = (uuid.UUID(str(cluster_id)).bytes
2566
+ + varint_prefixed_data_base64(profile_name.encode('utf-8'))
2567
+ + varint_prefixed_data_base64(addin_name.encode('utf-8')))
2568
+ await self._send_auth(104, value)
2569
+
2570
+ async def list_profile_modules(self, cluster_id, profile_name, *, cluster_os_user=None,
2571
+ cluster_password=None):
2572
+ return await self._list_profile_hash_acl(cluster_id, profile_name, 105, 'module',
2573
+ cluster_os_user=cluster_os_user,
2574
+ cluster_password=cluster_password)
2575
+
2576
+ async def update_profile_module(self, cluster_id, row, *, cluster_os_user=None):
2577
+ if not isinstance(row, dict) or set(row) != {'profile', 'name', 'hash', 'descr'}:
2578
+ raise ValueError('Invalid profile module record')
2579
+ for key in ('profile', 'name', 'hash', 'descr'):
2580
+ value = row[key]
2581
+ if (not isinstance(value, str) or '\x00' in value
2582
+ or len(value.encode('utf-8')) > 1024
2583
+ or key in ('profile', 'name') and not value.strip()):
2584
+ raise ValueError('Invalid profile module field: ' + key)
2585
+ def text(key):
2586
+ return varint_prefixed_data_base64(row[key].encode('utf-8'))
2587
+ value = (uuid.UUID(str(cluster_id)).bytes + text('profile') + text('name')
2588
+ + text('hash') + text('descr'))
2589
+ if cluster_os_user is None:
2590
+ await self.authenticate_cluster(cluster_id)
2591
+ else:
2592
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2593
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2594
+ raise ValueError('Invalid cluster administrator name')
2595
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2596
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2597
+ await self._send_auth(107, value)
2598
+
2599
+ async def remove_profile_module(self, cluster_id, profile_name, module_name, *,
2600
+ cluster_os_user=None):
2601
+ uuid.UUID(str(cluster_id))
2602
+ for value in (profile_name, module_name):
2603
+ if (not isinstance(value, str) or not value.strip()
2604
+ or '\x00' in value or len(value.encode('utf-8')) > 1024):
2605
+ raise ValueError('Invalid profile or module name')
2606
+ if cluster_os_user is None:
2607
+ await self.authenticate_cluster(cluster_id)
2608
+ else:
2609
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2610
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2611
+ raise ValueError('Invalid cluster administrator name')
2612
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2613
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2614
+ value = (uuid.UUID(str(cluster_id)).bytes
2615
+ + varint_prefixed_data_base64(profile_name.encode('utf-8'))
2616
+ + varint_prefixed_data_base64(module_name.encode('utf-8')))
2617
+ await self._send_auth(108, value)
2618
+
2619
+ async def _list_profile_hash_acl(self, cluster_id, profile_name, request_type, acl_type,
2620
+ *, cluster_os_user=None, cluster_password=None):
2621
+ cluster = uuid.UUID(str(cluster_id))
2622
+ if (not isinstance(profile_name, str) or not profile_name or '\x00' in profile_name
2623
+ or len(profile_name.encode('utf-8')) > 1024):
2624
+ raise ValueError('Invalid security profile name')
2625
+ await self._authenticate_profile_acl_read(cluster, cluster_os_user, cluster_password)
2626
+ value = cluster.bytes + varint_prefixed_data_base64(profile_name.encode('utf-8'))
2627
+ kind, payload = await self.request(request_type, value)
2628
+ if kind != request_type + 1:
2629
+ raise ValueError('Unexpected profile ' + acl_type + ' response')
2630
+ count = await unpack_varint_base128(payload)
2631
+ if count is None or count > 10000:
2632
+ raise ValueError('Invalid profile ' + acl_type + ' count')
2633
+ rows = []
2634
+ for _ in range(count):
2635
+ profile = await read_string(payload)
2636
+ name = await read_string(payload)
2637
+ hash_value = await read_string(payload)
2638
+ descr = await read_string(payload)
2639
+ if profile != profile_name or not name:
2640
+ raise ValueError('Invalid profile ' + acl_type + ' record')
2641
+ rows.append({'profile': profile, 'name': name, 'hash': hash_value,
2642
+ 'descr': descr})
2643
+ if len({row['name'] for row in rows}) != count:
2644
+ raise ValueError('Unexpected duplicate profile ' + acl_type + ' name')
2645
+ if payload.read():
2646
+ raise ValueError('Unexpected profile ' + acl_type + ' tail')
2647
+ return rows
2648
+
2649
+ async def list_profile_apps(self, cluster_id, profile_name, *, cluster_os_user=None,
2650
+ cluster_password=None):
2651
+ cluster = uuid.UUID(str(cluster_id))
2652
+ if (not isinstance(profile_name, str) or not profile_name or '\x00' in profile_name
2653
+ or len(profile_name.encode('utf-8')) > 1024):
2654
+ raise ValueError('Invalid security profile name')
2655
+ await self._authenticate_profile_acl_read(cluster, cluster_os_user, cluster_password)
2656
+ value = cluster.bytes + varint_prefixed_data_base64(profile_name.encode('utf-8'))
2657
+ kind, payload = await self.request(109, value)
2658
+ if kind != 110:
2659
+ raise ValueError('Unexpected profile app response')
2660
+ count = await unpack_varint_base128(payload)
2661
+ if count is None or count > 10000:
2662
+ raise ValueError('Invalid profile app count')
2663
+ rows = []
2664
+ for _ in range(count):
2665
+ profile = await read_string(payload)
2666
+ name = await read_string(payload)
2667
+ wild = await read_string(payload)
2668
+ descr = await read_string(payload)
2669
+ if profile != profile_name or not name:
2670
+ raise ValueError('Invalid profile app record')
2671
+ rows.append({'profile': profile, 'name': name, 'wild': wild,
2672
+ 'descr': descr})
2673
+ if len({row['name'] for row in rows}) != count:
2674
+ raise ValueError('Unexpected duplicate profile app name')
2675
+ if payload.read():
2676
+ raise ValueError('Unexpected profile app tail')
2677
+ return rows
2678
+
2679
+ async def update_profile_app(self, cluster_id, row, *, cluster_os_user=None):
2680
+ if not isinstance(row, dict) or set(row) != {'profile', 'name', 'wild', 'descr'}:
2681
+ raise ValueError('Invalid profile application record')
2682
+ for key in ('profile', 'name', 'wild', 'descr'):
2683
+ value = row[key]
2684
+ if (not isinstance(value, str) or '\x00' in value
2685
+ or len(value.encode('utf-8')) > 1024
2686
+ or key in ('profile', 'name') and not value.strip()):
2687
+ raise ValueError('Invalid profile application field: ' + key)
2688
+ def text(key):
2689
+ return varint_prefixed_data_base64(row[key].encode('utf-8'))
2690
+ value = (uuid.UUID(str(cluster_id)).bytes + text('profile') + text('name')
2691
+ + text('wild') + text('descr'))
2692
+ if cluster_os_user is None:
2693
+ await self.authenticate_cluster(cluster_id)
2694
+ else:
2695
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2696
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2697
+ raise ValueError('Invalid cluster administrator name')
2698
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2699
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2700
+ await self._send_auth(111, value)
2701
+
2702
+ async def remove_profile_app(self, cluster_id, profile_name, app_name, *,
2703
+ cluster_os_user=None):
2704
+ uuid.UUID(str(cluster_id))
2705
+ for value in (profile_name, app_name):
2706
+ if (not isinstance(value, str) or not value.strip()
2707
+ or '\x00' in value or len(value.encode('utf-8')) > 1024):
2708
+ raise ValueError('Invalid profile or application name')
2709
+ if cluster_os_user is None:
2710
+ await self.authenticate_cluster(cluster_id)
2711
+ else:
2712
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2713
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2714
+ raise ValueError('Invalid cluster administrator name')
2715
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2716
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2717
+ value = (uuid.UUID(str(cluster_id)).bytes
2718
+ + varint_prefixed_data_base64(profile_name.encode('utf-8'))
2719
+ + varint_prefixed_data_base64(app_name.encode('utf-8')))
2720
+ await self._send_auth(112, value)
2721
+
2722
+ async def update_profile_inet(self, cluster_id, row, *, cluster_os_user=None):
2723
+ if not isinstance(row, dict) or set(row) != {
2724
+ 'profile', 'name', 'url', 'port', 'protocol', 'descr'}:
2725
+ raise ValueError('Invalid profile inet record')
2726
+ for key in ('profile', 'name', 'url', 'protocol', 'descr'):
2727
+ value = row[key]
2728
+ if (not isinstance(value, str) or '\x00' in value
2729
+ or len(value.encode('utf-8')) > 1024
2730
+ or key in ('profile', 'name') and not value.strip()):
2731
+ raise ValueError('Invalid profile inet field: ' + key)
2732
+ port = row['port']
2733
+ if type(port) is not int or not 0 <= port <= 65535:
2734
+ raise ValueError('Invalid profile inet port')
2735
+ def text(key):
2736
+ return varint_prefixed_data_base64(row[key].encode('utf-8'))
2737
+ value = (uuid.UUID(str(cluster_id)).bytes + text('profile') + text('name')
2738
+ + text('url') + write_int32(port) + text('protocol') + text('descr'))
2739
+ if cluster_os_user is None:
2740
+ await self.authenticate_cluster(cluster_id)
2741
+ else:
2742
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2743
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2744
+ raise ValueError('Invalid cluster administrator name')
2745
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2746
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2747
+ await self._send_auth(115, value)
2748
+
2749
+ async def remove_profile_inet(self, cluster_id, profile_name, inet_name, *,
2750
+ cluster_os_user=None):
2751
+ uuid.UUID(str(cluster_id))
2752
+ for value in (profile_name, inet_name):
2753
+ if (not isinstance(value, str) or not value.strip()
2754
+ or '\x00' in value or len(value.encode('utf-8')) > 1024):
2755
+ raise ValueError('Invalid profile or inet resource name')
2756
+ if cluster_os_user is None:
2757
+ await self.authenticate_cluster(cluster_id)
2758
+ else:
2759
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2760
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2761
+ raise ValueError('Invalid cluster administrator name')
2762
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2763
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2764
+ value = (uuid.UUID(str(cluster_id)).bytes
2765
+ + varint_prefixed_data_base64(profile_name.encode('utf-8'))
2766
+ + varint_prefixed_data_base64(inet_name.encode('utf-8')))
2767
+ await self._send_auth(116, value)
2768
+
2769
+ async def list_profile_inet(self, cluster_id, profile_name, *, cluster_os_user=None,
2770
+ cluster_password=None):
2771
+ cluster = uuid.UUID(str(cluster_id))
2772
+ if (not isinstance(profile_name, str) or not profile_name or '\x00' in profile_name
2773
+ or len(profile_name.encode('utf-8')) > 1024):
2774
+ raise ValueError('Invalid security profile name')
2775
+ await self._authenticate_profile_acl_read(cluster, cluster_os_user, cluster_password)
2776
+ value = cluster.bytes + varint_prefixed_data_base64(profile_name.encode('utf-8'))
2777
+ kind, payload = await self.request(113, value)
2778
+ if kind != 114:
2779
+ raise ValueError('Unexpected profile inet response')
2780
+ count = await unpack_varint_base128(payload)
2781
+ if count is None or count > 10000:
2782
+ raise ValueError('Invalid profile inet count')
2783
+
2784
+ async def exact_string():
2785
+ length = await unpack_varint_base64(payload)
2786
+ if length is None or length > 65536:
2787
+ raise ValueError('Invalid profile inet string length')
2788
+ data = payload.read(length)
2789
+ if len(data) != length:
2790
+ raise ValueError('Truncated profile inet string')
2791
+ return data.decode('utf-8')
2792
+
2793
+ rows = []
2794
+ for _ in range(count):
2795
+ profile = await exact_string()
2796
+ name = await exact_string()
2797
+ url = await exact_string()
2798
+ port = read_int32(payload)
2799
+ protocol = await exact_string()
2800
+ descr = await exact_string()
2801
+ if profile != profile_name or not name or not 0 <= port <= 65535:
2802
+ raise ValueError('Invalid profile inet record')
2803
+ rows.append({'profile': profile, 'name': name, 'url': url,
2804
+ 'port': port, 'protocol': protocol, 'descr': descr})
2805
+ if len({row['name'] for row in rows}) != count:
2806
+ raise ValueError('Unexpected duplicate profile inet name')
2807
+ if payload.read():
2808
+ raise ValueError('Unexpected profile inet tail')
2809
+ return rows
2810
+
2811
+ async def _parse_service_setting(self, payload):
2812
+ row = {'setting': str(read_uuid(payload)),
2813
+ 'service-name': await read_string(payload),
2814
+ 'infobase-name': await read_string(payload),
2815
+ 'service-data-dir': await read_string(payload)}
2816
+ active = payload.read(1)
2817
+ if active not in (b'\x00', b'\x01'):
2818
+ raise ValueError('Invalid service setting active flag')
2819
+ row['active'] = active == b'\x01'
2820
+ terminator = payload.read(1)
2821
+ if terminator != b'\x00':
2822
+ raise ValueError('Invalid service setting terminator')
2823
+ return row
2824
+
2825
+ async def insert_service_setting(self, cluster_id, server_id, service_name,
2826
+ infobase_name='', service_data_dir='', *, cluster_os_user=None):
2827
+ for key, value in (('service name', service_name), ('infobase name', infobase_name),
2828
+ ('service data dir', service_data_dir)):
2829
+ if (not isinstance(value, str) or '\x00' in value
2830
+ or len(value.encode('utf-8')) > 1024
2831
+ or key == 'service name' and not value.strip()):
2832
+ raise ValueError('Invalid ' + key)
2833
+ def text(value):
2834
+ return varint_prefixed_data_base64(value.encode('utf-8'))
2835
+ value = (uuid.UUID(str(cluster_id)).bytes + uuid.UUID(str(server_id)).bytes
2836
+ + uuid.UUID(int=0).bytes + text(service_name) + text(infobase_name)
2837
+ + text(service_data_dir) + b'\x00\x00')
2838
+ if cluster_os_user is None:
2839
+ await self.authenticate_cluster(cluster_id)
2840
+ else:
2841
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2842
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2843
+ raise ValueError('Invalid cluster administrator name')
2844
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2845
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2846
+ kind, payload = await self.request(141, value)
2847
+ if kind != 142:
2848
+ raise ValueError('Unexpected service setting insert response')
2849
+ ident = read_uuid(payload)
2850
+ if payload.read():
2851
+ raise ValueError('Unexpected service setting insert tail')
2852
+ return ident
2853
+
2854
+ async def update_service_setting(self, cluster_id, server_id, row, *, cluster_os_user=None):
2855
+ if not isinstance(row, dict) or set(row) != {
2856
+ 'setting', 'service-name', 'infobase-name', 'service-data-dir', 'active'}:
2857
+ raise ValueError('Invalid service setting record')
2858
+ for key in ('service-name', 'infobase-name', 'service-data-dir'):
2859
+ value = row[key]
2860
+ if (not isinstance(value, str) or '\x00' in value
2861
+ or len(value.encode('utf-8')) > 1024
2862
+ or key == 'service-name' and not value.strip()):
2863
+ raise ValueError('Invalid ' + key)
2864
+ if type(row['active']) is not bool:
2865
+ raise ValueError('Invalid service setting active flag')
2866
+ def text(key):
2867
+ return varint_prefixed_data_base64(row[key].encode('utf-8'))
2868
+ value = (uuid.UUID(str(cluster_id)).bytes + uuid.UUID(str(server_id)).bytes
2869
+ + uuid.UUID(row['setting']).bytes + text('service-name')
2870
+ + text('infobase-name') + text('service-data-dir')
2871
+ + bytes([int(row['active']), 0]))
2872
+ if cluster_os_user is None:
2873
+ await self.authenticate_cluster(cluster_id)
2874
+ else:
2875
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2876
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2877
+ raise ValueError('Invalid cluster administrator name')
2878
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2879
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2880
+ kind, payload = await self.request(141, value)
2881
+ if kind != 142 or read_uuid(payload) != uuid.UUID(row['setting']) or payload.read():
2882
+ raise ValueError('Unexpected service setting update response')
2883
+
2884
+ async def remove_service_setting(self, cluster_id, server_id, setting_id, *, cluster_os_user=None):
2885
+ cluster, server, setting = (uuid.UUID(str(value)) for value in
2886
+ (cluster_id, server_id, setting_id))
2887
+ if cluster_os_user is None:
2888
+ await self.authenticate_cluster(cluster)
2889
+ else:
2890
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2891
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2892
+ raise ValueError('Invalid cluster administrator name')
2893
+ await self._send_auth(9, cluster.bytes
2894
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2895
+ await self._send_auth(143, cluster.bytes + server.bytes + setting.bytes)
2896
+
2897
+ async def apply_service_settings(self, cluster_id, server_id, *, cluster_os_user=None):
2898
+ cluster, server = uuid.UUID(str(cluster_id)), uuid.UUID(str(server_id))
2899
+ if cluster_os_user is None:
2900
+ await self.authenticate_cluster(cluster)
2901
+ else:
2902
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2903
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2904
+ raise ValueError('Invalid cluster administrator name')
2905
+ await self._send_auth(9, cluster.bytes
2906
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2907
+ await self._send_auth(144, cluster.bytes + server.bytes)
2908
+
2909
+ async def list_service_settings(self, cluster_id, server_id, *, cluster_os_user=None):
2910
+ value = uuid.UUID(str(cluster_id)).bytes + uuid.UUID(str(server_id)).bytes
2911
+ if cluster_os_user is None:
2912
+ await self.authenticate_cluster(cluster_id)
2913
+ else:
2914
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2915
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2916
+ raise ValueError('Invalid cluster administrator name')
2917
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2918
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2919
+ kind, payload = await self.request(139, value)
2920
+ if kind != 140:
2921
+ raise ValueError('Unexpected service setting list response')
2922
+ count = await unpack_varint_base128(payload)
2923
+ if count is None or count > 10000:
2924
+ raise ValueError('Invalid service setting count')
2925
+ rows = [await self._parse_service_setting(payload) for _ in range(count)]
2926
+ if len({row['setting'] for row in rows}) != count:
2927
+ raise ValueError('Unexpected duplicate service setting identifier')
2928
+ if payload.read():
2929
+ raise ValueError('Unexpected service setting list tail')
2930
+ return rows
2931
+
2932
+ async def get_service_setting(self, cluster_id, server_id, setting_id, *, cluster_os_user=None):
2933
+ ident = uuid.UUID(str(setting_id))
2934
+ value = (uuid.UUID(str(cluster_id)).bytes + uuid.UUID(str(server_id)).bytes
2935
+ + ident.bytes)
2936
+ if cluster_os_user is None:
2937
+ await self.authenticate_cluster(cluster_id)
2938
+ else:
2939
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2940
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2941
+ raise ValueError('Invalid cluster administrator name')
2942
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2943
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2944
+ kind, payload = await self.request(137, value)
2945
+ if kind != 138:
2946
+ raise ValueError('Unexpected service setting info response')
2947
+ row = await self._parse_service_setting(payload)
2948
+ if row['setting'] != str(ident) or payload.read():
2949
+ raise ValueError('Unexpected service setting info data')
2950
+ return row
2951
+
2952
+ async def list_service_data_dirs_for_transfer(self, cluster_id, server_id, service_name='',
2953
+ *, cluster_os_user=None):
2954
+ if (not isinstance(service_name, str) or '\x00' in service_name
2955
+ or len(service_name.encode('utf-8')) > 1024):
2956
+ raise ValueError('Invalid service name')
2957
+ value = (uuid.UUID(str(cluster_id)).bytes + uuid.UUID(str(server_id)).bytes
2958
+ + varint_prefixed_data_base64(service_name.encode('utf-8')))
2959
+ if cluster_os_user is None:
2960
+ await self.authenticate_cluster(cluster_id)
2961
+ else:
2962
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
2963
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
2964
+ raise ValueError('Invalid cluster administrator name')
2965
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
2966
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
2967
+ kind, payload = await self.request(145, value)
2968
+ if kind != 146:
2969
+ raise ValueError('Unexpected service transfer directories response')
2970
+ count = await unpack_varint_base128(payload)
2971
+ if count is None or count > 10000:
2972
+ raise ValueError('Invalid service transfer directory count')
2973
+ rows = []
2974
+ for _ in range(count):
2975
+ try:
2976
+ service = await read_string(payload)
2977
+ infobase = await read_string(payload)
2978
+ source = await read_string(payload)
2979
+ destination = await read_string(payload)
2980
+ except (ValueError, UnicodeError):
2981
+ raise ValueError('Invalid service transfer directory record') from None
2982
+ if not service or not source or not destination:
2983
+ raise ValueError('Invalid service transfer directory record')
2984
+ if service_name and service != service_name:
2985
+ raise ValueError('Unexpected service transfer directory name')
2986
+ rows.append({'service-name': service, 'infobase-name': infobase,
2987
+ 'from-dir': source, 'to-dir': destination})
2988
+ if payload.read():
2989
+ raise ValueError('Unexpected service transfer directory tail')
2990
+ return rows
2991
+
2992
+ async def _parse_counter(self, payload):
2993
+ result = {'name': await read_string(payload),
2994
+ 'collection-time': read_int64(payload)}
2995
+ group_filter = payload.read(2)
2996
+ if len(group_filter) != 2 or group_filter[0] not in (0, 1) or group_filter[1] not in (0, 1, 2):
2997
+ raise ValueError('Invalid counter group or filter type')
2998
+ result['raw-group'], result['raw-filter-type'] = group_filter
2999
+ result['group'] = ('users', 'data-separation')[group_filter[0]]
3000
+ result['filter-type'] = ('all-selected', 'all-but-selected', 'all')[group_filter[1]]
3001
+ result['filter'] = await read_string(payload)
3002
+ fields = ('duration', 'cpu-time', 'memory', 'read', 'write',
3003
+ 'duration-dbms', 'dbms-bytes', 'service', 'call',
3004
+ 'number-of-active-sessions', 'number-of-sessions')
3005
+ for field in fields:
3006
+ flag = payload.read(1)
3007
+ if flag not in (b'\x00', b'\x01'):
3008
+ raise ValueError('Invalid or truncated counter flag')
3009
+ result[field] = flag == b'\x01'
3010
+ result['descr'] = await read_string(payload)
3011
+ return result
3012
+
3013
+ async def update_counter(self, cluster_id, row, *, cluster_os_user=None):
3014
+ flags = ('duration', 'cpu-time', 'memory', 'read', 'write',
3015
+ 'duration-dbms', 'dbms-bytes', 'service', 'call',
3016
+ 'number-of-active-sessions', 'number-of-sessions')
3017
+ keys = {'name', 'collection-time', 'group', 'filter-type', 'filter', 'descr', *flags}
3018
+ if not isinstance(row, dict) or set(row) != keys:
3019
+ raise ValueError('Invalid counter record')
3020
+ for key in ('name', 'filter', 'descr'):
3021
+ value = row[key]
3022
+ if (not isinstance(value, str) or '\x00' in value
3023
+ or len(value.encode('utf-8')) > 1024
3024
+ or key == 'name' and not value.strip()):
3025
+ raise ValueError('Invalid counter ' + key)
3026
+ period = row['collection-time']
3027
+ if type(period) is not int or not 0 <= period < 2**63:
3028
+ raise ValueError('Invalid collection time')
3029
+ if row['group'] not in ('users', 'data-separation') or row['filter-type'] not in (
3030
+ 'all-selected', 'all-but-selected', 'all'):
3031
+ raise ValueError('Invalid counter group or filter type')
3032
+ if any(type(row[key]) is not bool for key in flags):
3033
+ raise ValueError('Invalid counter flags')
3034
+ def text(key):
3035
+ return varint_prefixed_data_base64(row[key].encode('utf-8'))
3036
+ value = (uuid.UUID(str(cluster_id)).bytes + text('name') + write_int64(period)
3037
+ + bytes((('users', 'data-separation').index(row['group']),
3038
+ ('all-selected', 'all-but-selected', 'all').index(row['filter-type'])))
3039
+ + text('filter') + bytes(int(row[key]) for key in flags) + text('descr'))
3040
+ if cluster_os_user is None:
3041
+ await self.authenticate_cluster(cluster_id)
3042
+ else:
3043
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
3044
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
3045
+ raise ValueError('Invalid cluster administrator name')
3046
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
3047
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
3048
+ await self._send_auth(122, value)
3049
+
3050
+ async def remove_counter(self, cluster_id, name, *, cluster_os_user=None):
3051
+ if (not isinstance(name, str) or not name.strip() or '\x00' in name
3052
+ or len(name.encode('utf-8')) > 1024):
3053
+ raise ValueError('Invalid counter name')
3054
+ value = (uuid.UUID(str(cluster_id)).bytes
3055
+ + varint_prefixed_data_base64(name.encode('utf-8')))
3056
+ if cluster_os_user is None:
3057
+ await self.authenticate_cluster(cluster_id)
3058
+ else:
3059
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
3060
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
3061
+ raise ValueError('Invalid cluster administrator name')
3062
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
3063
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
3064
+ await self._send_auth(123, value)
3065
+
3066
+ async def clear_counter(self, cluster_id, name, object_filter='', *, cluster_os_user=None):
3067
+ if (not isinstance(name, str) or not name.strip() or '\x00' in name
3068
+ or len(name.encode('utf-8')) > 1024):
3069
+ raise ValueError('Invalid counter name')
3070
+ if (not isinstance(object_filter, str) or '\x00' in object_filter
3071
+ or len(object_filter.encode('utf-8')) > 4096):
3072
+ raise ValueError('Invalid counter object filter')
3073
+ value = (uuid.UUID(str(cluster_id)).bytes
3074
+ + varint_prefixed_data_base64(name.encode('utf-8'))
3075
+ + varint_prefixed_data_base64(object_filter.encode('utf-8')))
3076
+ if cluster_os_user is None:
3077
+ await self.authenticate_cluster(cluster_id)
3078
+ else:
3079
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
3080
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
3081
+ raise ValueError('Invalid cluster administrator name')
3082
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
3083
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
3084
+ await self._send_auth(132, value)
3085
+
3086
+ async def list_counters(self, cluster_id, *, cluster_os_user=None, cluster_password=None):
3087
+ cluster = uuid.UUID(str(cluster_id))
3088
+ if cluster_password is not None:
3089
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
3090
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
3091
+ or not isinstance(cluster_password, str) or not cluster_password
3092
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
3093
+ raise ValueError('Invalid cluster administrator credentials')
3094
+ await self._send_auth(9, cluster.bytes
3095
+ + varint_prefixed_data_base64(cluster_os_user.encode())
3096
+ + varint_prefixed_data_base64(cluster_password.encode()))
3097
+ elif cluster_os_user is None:
3098
+ await self.authenticate_cluster(cluster)
3099
+ else:
3100
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
3101
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
3102
+ raise ValueError('Invalid cluster administrator name')
3103
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
3104
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
3105
+ kind, payload = await self.request(118, cluster.bytes)
3106
+ if kind != 119:
3107
+ raise ValueError('Unexpected counter list response')
3108
+ count = await unpack_varint_base128(payload)
3109
+ if count is None or count > 10000:
3110
+ raise ValueError('Invalid counter count')
3111
+ rows = [await self._parse_counter(payload) for _ in range(count)]
3112
+ if len({row['name'] for row in rows}) != count:
3113
+ raise ValueError('Unexpected duplicate counter name')
3114
+ if payload.read():
3115
+ raise ValueError('Unexpected counter list tail')
3116
+ return rows
3117
+
3118
+ async def get_counter(self, cluster_id, name, *, cluster_os_user=None):
3119
+ if (not isinstance(name, str) or not name.strip() or '\x00' in name
3120
+ or len(name.encode('utf-8')) > 1024):
3121
+ raise ValueError('Invalid counter name')
3122
+ cluster = uuid.UUID(str(cluster_id))
3123
+ if cluster_os_user is None:
3124
+ await self.authenticate_cluster(cluster)
3125
+ else:
3126
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
3127
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
3128
+ raise ValueError('Invalid cluster administrator name')
3129
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
3130
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
3131
+ value = cluster.bytes + varint_prefixed_data_base64(name.encode('utf-8'))
3132
+ kind, payload = await self.request(120, value)
3133
+ if kind != 121:
3134
+ raise ValueError('Unexpected counter info response')
3135
+ result = await self._parse_counter(payload)
3136
+ if result['name'] != name or payload.read():
3137
+ raise ValueError('Unexpected counter info data')
3138
+ return result
3139
+
3140
+ async def _parse_limit(self, payload):
3141
+ name = await read_string(payload)
3142
+ counter = await read_string(payload)
3143
+ action = payload.read(1)
3144
+ if len(action) != 1 or action[0] not in range(4):
3145
+ raise ValueError('Invalid limit action')
3146
+ fields = ('duration', 'cpu-time', 'duration-dbms', 'duration-service',
3147
+ 'memory', 'read', 'write', 'dbms-bytes', 'calls',
3148
+ 'active-sessions', 'sessions')
3149
+ result = {'name': name, 'counter': counter, 'raw-action': action[0],
3150
+ 'action': ('none', 'set-low-priority-thread',
3151
+ 'interrupt-current-call', 'interrupt-session')[action[0]]}
3152
+ for field in fields:
3153
+ result[field] = read_int64(payload)
3154
+ result['error-message'] = await read_string(payload)
3155
+ result['descr'] = await read_string(payload)
3156
+ return result
3157
+
3158
+ async def list_counter_values(self, cluster_id, counter, object_filter='', *, cluster_os_user=None):
3159
+ return await self._counter_values(cluster_id, 130, 131, 'counter values', counter, object_filter,
3160
+ cluster_os_user=cluster_os_user)
3161
+
3162
+ async def list_accumulated_counter_values(self, cluster_id, counter, object_filter='', *, cluster_os_user=None):
3163
+ return await self._counter_values(cluster_id, 133, 134, 'accumulated counter values', counter, object_filter,
3164
+ cluster_os_user=cluster_os_user)
3165
+
3166
+ async def _parse_counter_value(self, payload):
3167
+ # Derived from the Java 10.0 parseCounterValue(version >= 8) layout.
3168
+ result = {'object': await read_string(payload)}
3169
+ fields = ('collection-time', 'duration', 'cpu-time', 'duration-dbms',
3170
+ 'duration-service', 'memory', 'read', 'write', 'dbms-bytes',
3171
+ 'calls', 'active-sessions', 'sessions', 'time')
3172
+ for field in fields:
3173
+ result[field] = read_int64(payload)
3174
+ return result
3175
+
3176
+ async def _counter_values(self, cluster_id, request_type, response_type, operation, name, object_filter,
3177
+ *, cluster_os_user=None):
3178
+ if (not isinstance(name, str) or not name.strip() or '\x00' in name
3179
+ or len(name.encode('utf-8')) > 1024):
3180
+ raise ValueError('Invalid ' + operation + ' name')
3181
+ if not isinstance(object_filter, str) or '\x00' in object_filter or len(object_filter.encode()) > 4096:
3182
+ raise ValueError('Invalid ' + operation + ' filter')
3183
+ cluster = uuid.UUID(str(cluster_id))
3184
+ if cluster_os_user is None:
3185
+ await self.authenticate_cluster(cluster)
3186
+ else:
3187
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
3188
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
3189
+ raise ValueError('Invalid cluster administrator name')
3190
+ await self._send_auth(9, cluster.bytes
3191
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
3192
+ value = (cluster.bytes + varint_prefixed_data_base64(name.encode())
3193
+ + varint_prefixed_data_base64(object_filter.encode()))
3194
+ kind, payload = await self.request(request_type, value)
3195
+ if kind != response_type:
3196
+ raise ValueError('Unexpected ' + operation + ' response')
3197
+ count = await unpack_varint_base128(payload)
3198
+ if count is None or count > 10000:
3199
+ raise ValueError('Invalid ' + operation + ' count')
3200
+ try:
3201
+ rows = [await self._parse_counter_value(payload) for _ in range(count)]
3202
+ except (EOFError, ValueError, TypeError, UnicodeError, OverflowError, IndexError,
3203
+ struct.error):
3204
+ raise ValueError('Invalid ' + operation + ' data') from None
3205
+ if payload.read():
3206
+ raise ValueError('Unexpected ' + operation + ' tail')
3207
+ return rows
3208
+
3209
+ async def update_limit(self, cluster_id, row, *, cluster_os_user=None):
3210
+ fields = ('duration', 'cpu-time', 'duration-dbms', 'duration-service',
3211
+ 'memory', 'read', 'write', 'dbms-bytes', 'calls',
3212
+ 'active-sessions', 'sessions')
3213
+ actions = ('none', 'set-low-priority-thread', 'interrupt-current-call',
3214
+ 'interrupt-session')
3215
+ if not isinstance(row, dict) or set(row) != {
3216
+ 'name', 'counter', 'action', 'error-message', 'descr', *fields}:
3217
+ raise ValueError('Invalid limit record')
3218
+ for key in ('name', 'counter', 'error-message', 'descr'):
3219
+ value = row[key]
3220
+ if (not isinstance(value, str) or '\x00' in value
3221
+ or len(value.encode('utf-8')) > 1024
3222
+ or key == 'name' and not value.strip()):
3223
+ raise ValueError('Invalid limit ' + key)
3224
+ if row['action'] not in actions:
3225
+ raise ValueError('Invalid limit action')
3226
+ if any(type(row[key]) is not int or not 0 <= row[key] < 2**63 for key in fields):
3227
+ raise ValueError('Invalid limit threshold')
3228
+ def text(key):
3229
+ return varint_prefixed_data_base64(row[key].encode('utf-8'))
3230
+ value = (uuid.UUID(str(cluster_id)).bytes + text('name') + text('counter')
3231
+ + bytes([actions.index(row['action'])])
3232
+ + b''.join(write_int64(row[key]) for key in fields)
3233
+ + text('error-message') + text('descr'))
3234
+ if cluster_os_user is None:
3235
+ await self.authenticate_cluster(cluster_id)
3236
+ else:
3237
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
3238
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
3239
+ raise ValueError('Invalid cluster administrator name')
3240
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
3241
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
3242
+ await self._send_auth(128, value)
3243
+
3244
+ async def remove_limit(self, cluster_id, name, *, cluster_os_user=None):
3245
+ if (not isinstance(name, str) or not name.strip() or '\x00' in name
3246
+ or len(name.encode('utf-8')) > 1024):
3247
+ raise ValueError('Invalid limit name')
3248
+ value = (uuid.UUID(str(cluster_id)).bytes
3249
+ + varint_prefixed_data_base64(name.encode('utf-8')))
3250
+ if cluster_os_user is None:
3251
+ await self.authenticate_cluster(cluster_id)
3252
+ else:
3253
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
3254
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
3255
+ raise ValueError('Invalid cluster administrator name')
3256
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
3257
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
3258
+ await self._send_auth(129, value)
3259
+
3260
+ async def list_limits(self, cluster_id, *, cluster_os_user=None, cluster_password=None):
3261
+ cluster = uuid.UUID(str(cluster_id))
3262
+ if cluster_password is not None:
3263
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
3264
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
3265
+ or not isinstance(cluster_password, str) or not cluster_password
3266
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
3267
+ raise ValueError('Invalid cluster administrator credentials')
3268
+ await self._send_auth(9, cluster.bytes
3269
+ + varint_prefixed_data_base64(cluster_os_user.encode())
3270
+ + varint_prefixed_data_base64(cluster_password.encode()))
3271
+ elif cluster_os_user is None:
3272
+ await self.authenticate_cluster(cluster)
3273
+ else:
3274
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
3275
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
3276
+ raise ValueError('Invalid cluster administrator name')
3277
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
3278
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
3279
+ kind, payload = await self.request(124, cluster.bytes)
3280
+ if kind != 125:
3281
+ raise ValueError('Unexpected limit list response')
3282
+ count = await unpack_varint_base128(payload)
3283
+ if count is None or count > 10000:
3284
+ raise ValueError('Invalid limit count')
3285
+ rows = [await self._parse_limit(payload) for _ in range(count)]
3286
+ if len({row['name'] for row in rows}) != count:
3287
+ raise ValueError('Unexpected duplicate limit name')
3288
+ if payload.read():
3289
+ raise ValueError('Unexpected limit list tail')
3290
+ return rows
3291
+
3292
+ async def get_limit(self, cluster_id, name, *, cluster_os_user=None):
3293
+ if (not isinstance(name, str) or not name.strip() or '\x00' in name
3294
+ or len(name.encode('utf-8')) > 1024):
3295
+ raise ValueError('Invalid limit name')
3296
+ cluster = uuid.UUID(str(cluster_id))
3297
+ if cluster_os_user is None:
3298
+ await self.authenticate_cluster(cluster)
3299
+ else:
3300
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
3301
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
3302
+ raise ValueError('Invalid cluster administrator name')
3303
+ await self._send_auth(9, uuid.UUID(str(cluster_id)).bytes
3304
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
3305
+ value = cluster.bytes + varint_prefixed_data_base64(name.encode('utf-8'))
3306
+ kind, payload = await self.request(126, value)
3307
+ if kind != 127:
3308
+ raise ValueError('Unexpected limit info response')
3309
+ result = await self._parse_limit(payload)
3310
+ if result['name'] != name or payload.read():
3311
+ raise ValueError('Unexpected limit info data')
3312
+ return result
3313
+
3314
+ async def agent_version(self):
3315
+ kind, payload = await self.request(135)
3316
+ if kind != 136:
3317
+ raise ValueError('Unexpected agent version response')
3318
+ version = await read_string(payload)
3319
+ if payload.read():
3320
+ raise ValueError('Unexpected agent version tail')
3321
+ return version
3322
+
3323
+ async def _parse_manager(self, payload):
3324
+ ident = str(read_uuid(payload))
3325
+ description = await read_string(payload)
3326
+ host = await read_string(payload)
3327
+ using = read_int32(payload)
3328
+ if using not in (0, 1):
3329
+ raise ValueError('Unknown manager mode')
3330
+ port = read_uint16(payload)
3331
+ pid = await read_string(payload)
3332
+ return {'manager': ident, 'descr': description, 'host': host,
3333
+ 'using': 'main' if using == 1 else 'normal', 'port': port, 'pid': pid}
3334
+
3335
+ async def list_managers(self, cluster_id, *, cluster_os_user=None, cluster_password=None):
3336
+ cluster = uuid.UUID(str(cluster_id))
3337
+ if cluster_password is not None:
3338
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
3339
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
3340
+ or not isinstance(cluster_password, str) or not cluster_password
3341
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
3342
+ raise ValueError('Invalid cluster administrator credentials')
3343
+ await self._send_auth(9, cluster.bytes
3344
+ + varint_prefixed_data_base64(cluster_os_user.encode())
3345
+ + varint_prefixed_data_base64(cluster_password.encode()))
3346
+ elif cluster_os_user is None:
3347
+ await self.authenticate_cluster(cluster)
3348
+ else:
3349
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
3350
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
3351
+ raise ValueError('Invalid cluster administrator name')
3352
+ await self._send_auth(9, cluster.bytes
3353
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
3354
+ kind, payload = await self.request(18, cluster.bytes)
3355
+ if kind != 19:
3356
+ raise ValueError('Unexpected manager response')
3357
+ count = await unpack_varint_base128(payload)
3358
+ if count is None or count > 1000:
3359
+ raise ValueError('Invalid manager count')
3360
+ rows = [await self._parse_manager(payload) for _ in range(count)]
3361
+ if len({row['manager'] for row in rows}) != count:
3362
+ raise ValueError('Unexpected duplicate manager identifier')
3363
+ if payload.read():
3364
+ raise ValueError('Unexpected manager tail')
3365
+ return rows
3366
+
3367
+ async def get_manager(self, cluster_id, manager_id, *, cluster_os_user=None,
3368
+ cluster_password=None):
3369
+ cluster, ident = uuid.UUID(str(cluster_id)), uuid.UUID(str(manager_id))
3370
+ if cluster_password is not None:
3371
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
3372
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
3373
+ or not isinstance(cluster_password, str) or not cluster_password
3374
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
3375
+ raise ValueError('Invalid cluster administrator credentials')
3376
+ await self._send_auth(9, cluster.bytes
3377
+ + varint_prefixed_data_base64(cluster_os_user.encode())
3378
+ + varint_prefixed_data_base64(cluster_password.encode()))
3379
+ elif cluster_os_user is None:
3380
+ await self.authenticate_cluster(cluster)
3381
+ else:
3382
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
3383
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
3384
+ raise ValueError('Invalid cluster administrator name')
3385
+ await self._send_auth(9, cluster.bytes
3386
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
3387
+ kind, payload = await self.request(20, cluster.bytes + ident.bytes)
3388
+ if kind != 21:
3389
+ raise ValueError('Unexpected manager info response')
3390
+ row = await self._parse_manager(payload)
3391
+ if row['manager'] != str(ident) or payload.read():
3392
+ raise ValueError('Unexpected manager info data')
3393
+ return row
3394
+
3395
+ async def list_services(self, cluster_id, *, cluster_os_user=None, cluster_password=None):
3396
+ cluster = uuid.UUID(str(cluster_id))
3397
+ if cluster_password is not None:
3398
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
3399
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024
3400
+ or not isinstance(cluster_password, str) or not cluster_password
3401
+ or '\x00' in cluster_password or len(cluster_password.encode('utf-8')) > 1024):
3402
+ raise ValueError('Invalid cluster administrator credentials')
3403
+ await self._send_auth(9, cluster.bytes
3404
+ + varint_prefixed_data_base64(cluster_os_user.encode())
3405
+ + varint_prefixed_data_base64(cluster_password.encode()))
3406
+ elif cluster_os_user is None:
3407
+ await self.authenticate_cluster(cluster)
3408
+ else:
3409
+ if (not isinstance(cluster_os_user, str) or not cluster_os_user
3410
+ or '\x00' in cluster_os_user or len(cluster_os_user.encode('utf-8')) > 1024):
3411
+ raise ValueError('Invalid cluster administrator name')
3412
+ await self._send_auth(9, cluster.bytes
3413
+ + varint_prefixed_data_base64(cluster_os_user.encode()) + b'\x00')
3414
+ kind, payload = await self.request(35, cluster.bytes)
3415
+ if kind != 36:
3416
+ raise ValueError('Unexpected service response')
3417
+ count = await unpack_varint_base128(payload)
3418
+ if count is None or count > 1000:
3419
+ raise ValueError('Invalid service count')
3420
+ rows = []
3421
+ for _ in range(count):
3422
+ name = await read_string(payload)
3423
+ description = await read_string(payload)
3424
+ main_only = read_int32(payload)
3425
+ if main_only not in (0, 1):
3426
+ raise ValueError('Unknown service main-only flag')
3427
+ has_manager = payload.read(1)
3428
+ if has_manager not in (b'\x00', b'\x01'):
3429
+ raise ValueError('Unknown service manager flag')
3430
+ manager = str(read_uuid(payload)) if has_manager == b'\x01' else None
3431
+ rows.append({'name': name, 'descr': description, 'main-only': main_only,
3432
+ 'manager': manager})
3433
+ if len({row['name'] for row in rows}) != count:
3434
+ raise ValueError('Unexpected duplicate service name')
3435
+ if payload.read():
3436
+ raise ValueError('Unexpected service tail')
3437
+ return rows
3438
+
3439
+ async def list_clusters(self):
3440
+ message_type, payload = await self.request(11)
3441
+ if message_type != 12:
3442
+ raise ValueError('Unexpected cluster response')
3443
+ count = await unpack_varint_base128(payload)
3444
+ if count is None or count > 1000:
3445
+ raise ValueError('Invalid cluster count')
3446
+ rows = []
3447
+ for _ in range(count):
3448
+ rows.append(await self._parse_cluster(payload, list_record=True))
3449
+ if len({row['cluster'] for row in rows}) != count:
3450
+ raise ValueError('Unexpected duplicate cluster identifier')
3451
+ if payload.read() not in (b'', b'\x00'):
3452
+ raise ValueError('Unexpected trailing response data')
3453
+ return rows