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.
- pyrac/__init__.py +4 -0
- pyrac/__main__.py +4 -0
- pyrac/cli.py +921 -0
- pyrac/client.py +2382 -0
- pyrac/desktop.py +5003 -0
- pyrac/legacy.py +8 -0
- pyrac/mcp_server.py +3845 -0
- pyrac/protocol/__init__.py +0 -0
- pyrac/protocol/v851.py +32 -0
- pyrac/protocol/v851_client.py +3453 -0
- pyrac/read_service.py +831 -0
- pyrac/v8311.py +1400 -0
- pyrac/web/__init__.py +0 -0
- pyrac/web/__main__.py +355 -0
- pyrac/web/app.py +2835 -0
- pyrac/web/workspace.py +1905 -0
- python_rac-0.2.0.dist-info/METADATA +843 -0
- python_rac-0.2.0.dist-info/RECORD +22 -0
- python_rac-0.2.0.dist-info/WHEEL +5 -0
- python_rac-0.2.0.dist-info/entry_points.txt +5 -0
- python_rac-0.2.0.dist-info/licenses/LICENSE +21 -0
- python_rac-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -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
|