gnetclisdk 0.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.
- gnetclisdk/auth.py +41 -0
- gnetclisdk/client.py +481 -0
- gnetclisdk/exceptions.py +116 -0
- gnetclisdk/interceptors.py +196 -0
- gnetclisdk/proto/server_pb2.py +83 -0
- gnetclisdk/proto/server_pb2.pyi +190 -0
- gnetclisdk/proto/server_pb2_grpc.py +399 -0
- gnetclisdk-0.0.dist-info/METADATA +45 -0
- gnetclisdk-0.0.dist-info/RECORD +11 -0
- gnetclisdk-0.0.dist-info/WHEEL +5 -0
- gnetclisdk-0.0.dist-info/top_level.txt +1 -0
gnetclisdk/auth.py
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
import abc
|
2
|
+
|
3
|
+
|
4
|
+
class ClientAuthentication(abc.ABC):
|
5
|
+
@abc.abstractmethod
|
6
|
+
def get_authentication_header_key(self) -> str:
|
7
|
+
"""
|
8
|
+
Name of the header used for authentication.
|
9
|
+
:return: Header name.
|
10
|
+
"""
|
11
|
+
raise NotImplementedError("abstract method get_authentication_header_key() not implemented")
|
12
|
+
|
13
|
+
@abc.abstractmethod
|
14
|
+
def create_authentication_header_value(self) -> str:
|
15
|
+
"""
|
16
|
+
Creates value for authentication header.
|
17
|
+
:return: Authentication header value.
|
18
|
+
"""
|
19
|
+
raise NotImplementedError("abstract method create_authentication_header_value() not implemented")
|
20
|
+
|
21
|
+
|
22
|
+
class OAuthClientAuthentication(ClientAuthentication):
|
23
|
+
def __init__(self, token: str):
|
24
|
+
self.__token = token
|
25
|
+
|
26
|
+
def get_authentication_header_key(self) -> str:
|
27
|
+
return "authorization"
|
28
|
+
|
29
|
+
def create_authentication_header_value(self) -> str:
|
30
|
+
return f"OAuth {self.__token}"
|
31
|
+
|
32
|
+
|
33
|
+
class BasicClientAuthentication(ClientAuthentication):
|
34
|
+
def __init__(self, token: str):
|
35
|
+
self.__token = token
|
36
|
+
|
37
|
+
def get_authentication_header_key(self) -> str:
|
38
|
+
return "authorization"
|
39
|
+
|
40
|
+
def create_authentication_header_value(self) -> str:
|
41
|
+
return f"Basic {self.__token}"
|
gnetclisdk/client.py
ADDED
@@ -0,0 +1,481 @@
|
|
1
|
+
import asyncio
|
2
|
+
import logging
|
3
|
+
import os.path
|
4
|
+
import uuid
|
5
|
+
from abc import ABC, abstractmethod
|
6
|
+
from contextlib import asynccontextmanager
|
7
|
+
from dataclasses import dataclass, field
|
8
|
+
from functools import partial
|
9
|
+
from typing import Any, AsyncIterator, List, Optional, Tuple, Dict
|
10
|
+
|
11
|
+
import grpc
|
12
|
+
from google.protobuf.message import Message
|
13
|
+
|
14
|
+
from .proto import server_pb2, server_pb2_grpc
|
15
|
+
from .auth import BasicClientAuthentication, ClientAuthentication, OAuthClientAuthentication
|
16
|
+
from .exceptions import parse_grpc_error
|
17
|
+
from .interceptors import get_auth_client_interceptors
|
18
|
+
|
19
|
+
_logger = logging.getLogger(__name__)
|
20
|
+
HEADER_REQUEST_ID = "x-request-id"
|
21
|
+
HEADER_USER_AGENT = "user-agent"
|
22
|
+
DEFAULT_USER_AGENT = "Gnetcli SDK"
|
23
|
+
DEFAULT_SERVER = "localhost:50051"
|
24
|
+
SERVER_ENV = "GNETCLI_SERVER"
|
25
|
+
GRPC_MAX_MESSAGE_LENGTH = 130 * 1024**2
|
26
|
+
|
27
|
+
default_grpc_options: List[Tuple[str, Any]] = [
|
28
|
+
("grpc.max_concurrent_streams", 900),
|
29
|
+
("grpc.max_send_message_length", GRPC_MAX_MESSAGE_LENGTH),
|
30
|
+
("grpc.max_receive_message_length", GRPC_MAX_MESSAGE_LENGTH),
|
31
|
+
]
|
32
|
+
|
33
|
+
|
34
|
+
@dataclass
|
35
|
+
class QA:
|
36
|
+
question: str
|
37
|
+
answer: str
|
38
|
+
|
39
|
+
|
40
|
+
@dataclass
|
41
|
+
class Credentials:
|
42
|
+
login: str
|
43
|
+
password: str
|
44
|
+
|
45
|
+
def make_pb(self) -> Message:
|
46
|
+
pb = server_pb2.Credentials()
|
47
|
+
pb.login = self.login
|
48
|
+
pb.password = self.password
|
49
|
+
return pb
|
50
|
+
|
51
|
+
|
52
|
+
@dataclass
|
53
|
+
class File:
|
54
|
+
content: bytes
|
55
|
+
status: server_pb2.FileStatus
|
56
|
+
|
57
|
+
|
58
|
+
@dataclass
|
59
|
+
class HostParams:
|
60
|
+
device: str
|
61
|
+
port: Optional[int] = None
|
62
|
+
hostname: Optional[str] = None
|
63
|
+
credentials: Optional[Credentials] = None
|
64
|
+
ip: Optional[str] = None
|
65
|
+
|
66
|
+
def make_pb(self) -> Message:
|
67
|
+
pbcmd = server_pb2.HostParams(
|
68
|
+
host=self.hostname,
|
69
|
+
port=self.port,
|
70
|
+
credentials=self.credentials.make_pb(),
|
71
|
+
device=self.device,
|
72
|
+
ip=self.ip,
|
73
|
+
)
|
74
|
+
return pbcmd
|
75
|
+
|
76
|
+
|
77
|
+
def make_auth(auth_token: str) -> ClientAuthentication:
|
78
|
+
if auth_token.lower().startswith("oauth"):
|
79
|
+
authentication = OAuthClientAuthentication(auth_token.split(" ")[1])
|
80
|
+
elif auth_token.lower().startswith("basic"):
|
81
|
+
authentication = BasicClientAuthentication(auth_token.split(" ")[1])
|
82
|
+
else:
|
83
|
+
raise Exception("unknown token type")
|
84
|
+
return authentication
|
85
|
+
|
86
|
+
|
87
|
+
class Gnetcli:
|
88
|
+
def __init__(
|
89
|
+
self,
|
90
|
+
auth_token: Optional[str] = None, # like 'Basic ...'
|
91
|
+
server: Optional[str] = None,
|
92
|
+
target_name_override: Optional[str] = None,
|
93
|
+
cert_file: Optional[str] = None,
|
94
|
+
user_agent: str = DEFAULT_USER_AGENT,
|
95
|
+
insecure_grpc: bool = False,
|
96
|
+
):
|
97
|
+
if server is None:
|
98
|
+
self._server = os.getenv(SERVER_ENV, DEFAULT_SERVER)
|
99
|
+
else:
|
100
|
+
self._server = server
|
101
|
+
self._user_agent = user_agent
|
102
|
+
|
103
|
+
options: List[Tuple[str, Any]] = [
|
104
|
+
*default_grpc_options,
|
105
|
+
("grpc.primary_user_agent", user_agent),
|
106
|
+
]
|
107
|
+
if target_name_override:
|
108
|
+
_logger.warning("set target_name_override %s", target_name_override)
|
109
|
+
options.append(("grpc.ssl_target_name_override", target_name_override))
|
110
|
+
self._target_name_override = target_name_override
|
111
|
+
cert = get_cert(cert_file=cert_file)
|
112
|
+
channel_credentials = grpc.ssl_channel_credentials(root_certificates=cert)
|
113
|
+
interceptors = []
|
114
|
+
if auth_token:
|
115
|
+
authentication: ClientAuthentication
|
116
|
+
authentication = make_auth(auth_token)
|
117
|
+
interceptors = get_auth_client_interceptors(authentication)
|
118
|
+
grpc_channel_fn = partial(grpc.aio.secure_channel, credentials=channel_credentials, interceptors=interceptors)
|
119
|
+
if insecure_grpc:
|
120
|
+
grpc_channel_fn = partial(grpc.aio.insecure_channel, interceptors=interceptors)
|
121
|
+
self._grpc_channel_fn = grpc_channel_fn
|
122
|
+
self._options = options
|
123
|
+
self._channel: Optional[grpc.aio.Channel] = None
|
124
|
+
self._insecure_grpc: bool = insecure_grpc
|
125
|
+
|
126
|
+
async def cmd(
|
127
|
+
self,
|
128
|
+
hostname: str,
|
129
|
+
cmd: str,
|
130
|
+
trace: bool = False,
|
131
|
+
qa: Optional[List[QA]] = None,
|
132
|
+
read_timeout: float = 0.0,
|
133
|
+
cmd_timeout: float = 0.0,
|
134
|
+
host_params: Optional[HostParams] = None,
|
135
|
+
) -> Message:
|
136
|
+
pbcmd = make_cmd(
|
137
|
+
hostname=hostname,
|
138
|
+
cmd=cmd,
|
139
|
+
trace=trace,
|
140
|
+
qa=qa,
|
141
|
+
read_timeout=read_timeout,
|
142
|
+
cmd_timeout=cmd_timeout,
|
143
|
+
host_params=host_params,
|
144
|
+
)
|
145
|
+
if self._channel is None:
|
146
|
+
_logger.debug("connect to %s", self._server)
|
147
|
+
self._channel = self._grpc_channel_fn(self._server, options=self._options)
|
148
|
+
stub = server_pb2_grpc.GnetcliStub(self._channel)
|
149
|
+
response = await grpc_call_wrapper(stub.Exec, pbcmd)
|
150
|
+
return response
|
151
|
+
|
152
|
+
async def add_device(
|
153
|
+
self,
|
154
|
+
name: str,
|
155
|
+
prompt_expression: str,
|
156
|
+
error_expression: Optional[str] = None,
|
157
|
+
pager_expression: Optional[str] = None,
|
158
|
+
) -> Message:
|
159
|
+
pbdev = server_pb2.Device
|
160
|
+
pbdev.name = name
|
161
|
+
pbdev.prompt_expression = prompt_expression
|
162
|
+
if error_expression:
|
163
|
+
pbdev.error_expression = error_expression
|
164
|
+
if pager_expression:
|
165
|
+
pbdev.pager_expression = pager_expression
|
166
|
+
if self._channel is None:
|
167
|
+
_logger.debug("connect to %s", self._server)
|
168
|
+
self._channel = self._grpc_channel_fn(self._server, options=self._options)
|
169
|
+
stub = server_pb2_grpc.GnetcliStub(self._channel)
|
170
|
+
response = await grpc_call_wrapper(stub.AddDevice, pbdev)
|
171
|
+
return response
|
172
|
+
|
173
|
+
def connect(self) -> None:
|
174
|
+
# make connection here will pass it to session
|
175
|
+
if not self._channel:
|
176
|
+
_logger.debug("real connect to %s", self._server)
|
177
|
+
self._channel = self._grpc_channel_fn(self._server, options=self._options)
|
178
|
+
|
179
|
+
async def cmd_netconf(self, hostname: str, cmd: str, json: bool = False, trace: bool = False) -> Message:
|
180
|
+
pbcmd = server_pb2.CMDNetconf(host=hostname, cmd=cmd, json=json, trace=trace)
|
181
|
+
_logger.debug("connect to %s", self._server)
|
182
|
+
async with self._grpc_channel_fn(self._server, options=self._options) as channel:
|
183
|
+
stub = server_pb2_grpc.GnetcliStub(channel)
|
184
|
+
_logger.debug("executing netconf cmd: %r", pbcmd)
|
185
|
+
try:
|
186
|
+
response = await grpc_call_wrapper(stub.ExecNetconf, pbcmd)
|
187
|
+
except Exception as e:
|
188
|
+
_logger.error("error hostname=%s cmd=%r error=%s", hostname, repr(pbcmd), e)
|
189
|
+
raise
|
190
|
+
return response
|
191
|
+
|
192
|
+
@asynccontextmanager
|
193
|
+
async def cmd_session(self, hostname: str) -> AsyncIterator["GnetcliSessionCmd"]:
|
194
|
+
sess = GnetcliSessionCmd(
|
195
|
+
hostname,
|
196
|
+
server=self._server,
|
197
|
+
channel=self._channel,
|
198
|
+
target_name_override=self._target_name_override,
|
199
|
+
user_agent=self._user_agent,
|
200
|
+
insecure_grpc=self._insecure_grpc,
|
201
|
+
)
|
202
|
+
await sess.connect()
|
203
|
+
try:
|
204
|
+
yield sess
|
205
|
+
finally:
|
206
|
+
await sess.close()
|
207
|
+
|
208
|
+
@asynccontextmanager
|
209
|
+
async def netconf_session(self, hostname: str) -> AsyncIterator["GnetcliSessionNetconf"]:
|
210
|
+
sess = GnetcliSessionNetconf(
|
211
|
+
hostname,
|
212
|
+
# self._token,
|
213
|
+
server=self._server,
|
214
|
+
target_name_override=self._target_name_override,
|
215
|
+
user_agent=self._user_agent,
|
216
|
+
insecure_grpc=self._insecure_grpc,
|
217
|
+
)
|
218
|
+
await sess.connect()
|
219
|
+
try:
|
220
|
+
yield sess
|
221
|
+
finally:
|
222
|
+
await sess.close()
|
223
|
+
|
224
|
+
async def set_host_params(self, hostname: str, params: HostParams) -> None:
|
225
|
+
pbcmd = server_pb2.HostParams(
|
226
|
+
host=hostname,
|
227
|
+
port=params.port,
|
228
|
+
credentials=params.credentials.make_pb(),
|
229
|
+
device=params.device)
|
230
|
+
_logger.debug("connect to %s", self._server)
|
231
|
+
async with self._grpc_channel_fn(self._server, options=self._options) as channel:
|
232
|
+
_logger.debug("set params for %s", hostname)
|
233
|
+
stub = server_pb2_grpc.GnetcliStub(channel)
|
234
|
+
await grpc_call_wrapper(stub.SetupHostParams, pbcmd)
|
235
|
+
return
|
236
|
+
|
237
|
+
async def upload(self, hostname: str, files: Dict[str, File]) -> None:
|
238
|
+
pbcmd = server_pb2.FileUploadRequest(host=hostname, files=make_files_request(files))
|
239
|
+
_logger.debug("connect to %s", self._server)
|
240
|
+
async with self._grpc_channel_fn(self._server, options=self._options) as channel:
|
241
|
+
_logger.debug("upload %s to %s", files.keys(), hostname)
|
242
|
+
stub = server_pb2_grpc.GnetcliStub(channel)
|
243
|
+
response: Message = await grpc_call_wrapper(stub.Upload, pbcmd)
|
244
|
+
_logger.debug("upload res %s", response)
|
245
|
+
return
|
246
|
+
|
247
|
+
async def download(self, hostname: str, paths: List[str]) -> Dict[str, File]:
|
248
|
+
pbcmd = server_pb2.FileDownloadRequest(host=hostname, paths=paths)
|
249
|
+
_logger.debug("connect to %s", self._server)
|
250
|
+
async with self._grpc_channel_fn(self._server, options=self._options) as channel:
|
251
|
+
_logger.debug("download %s from %s", paths, hostname)
|
252
|
+
stub = server_pb2_grpc.GnetcliStub(channel)
|
253
|
+
response: server_pb2.FilesResult = await grpc_call_wrapper(stub.Download, pbcmd)
|
254
|
+
res: Dict[str, File] = {}
|
255
|
+
for file in response.files:
|
256
|
+
res[file.path] = File(content=file.data, status=file.status)
|
257
|
+
return res
|
258
|
+
|
259
|
+
|
260
|
+
class GnetcliSession(ABC):
|
261
|
+
def __init__(
|
262
|
+
self,
|
263
|
+
hostname: str,
|
264
|
+
token: str,
|
265
|
+
server: str = DEFAULT_SERVER,
|
266
|
+
target_name_override: Optional[str] = None,
|
267
|
+
cert_file: Optional[str] = None,
|
268
|
+
user_agent: str = DEFAULT_USER_AGENT,
|
269
|
+
insecure_grpc: bool = False,
|
270
|
+
channel: Optional[grpc.aio.Channel] = None,
|
271
|
+
credentials: Optional[Credentials] = None,
|
272
|
+
):
|
273
|
+
self._hostname = hostname
|
274
|
+
self._credentials = credentials
|
275
|
+
self._server = server
|
276
|
+
self._channel: Optional[grpc.aio.Channel] = channel
|
277
|
+
self._stub: Optional[server_pb2_grpc.GnetcliStub] = None
|
278
|
+
self._stream: Optional[grpc.aio.StreamStreamCall] = None
|
279
|
+
self._user_agent = user_agent
|
280
|
+
|
281
|
+
options: List[Tuple[str, Any]] = [
|
282
|
+
("grpc.max_concurrent_streams", 900),
|
283
|
+
("grpc.max_send_message_length", GRPC_MAX_MESSAGE_LENGTH),
|
284
|
+
("grpc.max_receive_message_length", GRPC_MAX_MESSAGE_LENGTH),
|
285
|
+
]
|
286
|
+
if target_name_override:
|
287
|
+
options.append(("grpc.ssl_target_name_override", target_name_override))
|
288
|
+
cert = get_cert(cert_file=cert_file)
|
289
|
+
channel_credentials = grpc.ssl_channel_credentials(root_certificates=cert)
|
290
|
+
authentication: ClientAuthentication
|
291
|
+
if token.startswith("OAuth"):
|
292
|
+
authentication = OAuthClientAuthentication(token.split(" ")[1])
|
293
|
+
elif token.startswith("Basic"):
|
294
|
+
authentication = BasicClientAuthentication(token.split(" ")[1])
|
295
|
+
else:
|
296
|
+
raise Exception("unknown token type")
|
297
|
+
interceptors = get_auth_client_interceptors(authentication)
|
298
|
+
grpc_channel_fn = partial(grpc.aio.secure_channel, credentials=channel_credentials, interceptors=interceptors)
|
299
|
+
if insecure_grpc:
|
300
|
+
grpc_channel_fn = partial(grpc.aio.insecure_channel, interceptors=interceptors)
|
301
|
+
self._grpc_channel_fn = grpc_channel_fn
|
302
|
+
self._options = options
|
303
|
+
self._req_id: Optional[Any] = None
|
304
|
+
|
305
|
+
def _get_metadata(self) -> List[Tuple[str, str]]:
|
306
|
+
req_id = make_req_id()
|
307
|
+
metadata = [
|
308
|
+
(HEADER_REQUEST_ID, req_id),
|
309
|
+
(HEADER_USER_AGENT, self._user_agent),
|
310
|
+
]
|
311
|
+
return metadata
|
312
|
+
|
313
|
+
@abstractmethod
|
314
|
+
async def connect(self) -> None:
|
315
|
+
if self._channel is None:
|
316
|
+
_logger.debug("connect to %s self._channel=%s", self._server, self._channel)
|
317
|
+
self._channel = self._grpc_channel_fn(self._server, options=self._options)
|
318
|
+
self._stub = server_pb2_grpc.GnetcliStub(self._channel)
|
319
|
+
if self._stub is None:
|
320
|
+
raise Exception("empty stub")
|
321
|
+
|
322
|
+
async def _cmd(self, cmdpb: Any) -> Message:
|
323
|
+
# TODO: add connect retry on first cmd
|
324
|
+
if not self._stream:
|
325
|
+
raise Exception("empty self._stream")
|
326
|
+
try:
|
327
|
+
_logger.debug("cmd %r on %r", str(cmdpb).replace("\n", ""), self._stream)
|
328
|
+
await self._stream.write(cmdpb)
|
329
|
+
response: Message = await self._stream.read()
|
330
|
+
except grpc.aio.AioRpcError as e:
|
331
|
+
_logger.debug("caught exception %s %s", e, parse_grpc_error(e))
|
332
|
+
gn_exc, verbose = parse_grpc_error(e)
|
333
|
+
last_exc = gn_exc(
|
334
|
+
message=f"{e.__class__.__name__} {e.details()}",
|
335
|
+
imetadata=e.initial_metadata(), # type: ignore
|
336
|
+
verbose=verbose,
|
337
|
+
)
|
338
|
+
last_exc.__cause__ = e
|
339
|
+
raise last_exc from None
|
340
|
+
_logger.debug("response %s", format_long_msg(str(response), 100))
|
341
|
+
return response
|
342
|
+
|
343
|
+
async def close(self) -> None:
|
344
|
+
_logger.debug("close stream %s", self._stream)
|
345
|
+
if self._stream:
|
346
|
+
await self._stream.done_writing()
|
347
|
+
self._stream.done()
|
348
|
+
self._stream = None
|
349
|
+
|
350
|
+
|
351
|
+
class GnetcliSessionCmd(GnetcliSession):
|
352
|
+
async def cmd(
|
353
|
+
self,
|
354
|
+
cmd: str,
|
355
|
+
trace: bool = False,
|
356
|
+
qa: Optional[List[QA]] = None,
|
357
|
+
cmd_timeout: float = 0.0,
|
358
|
+
read_timeout: float = 0.0,
|
359
|
+
host_params: Optional[HostParams] = None,
|
360
|
+
) -> Message:
|
361
|
+
_logger.debug("session cmd %r", cmd)
|
362
|
+
pbcmd = make_cmd(
|
363
|
+
hostname=self._hostname,
|
364
|
+
cmd=cmd,
|
365
|
+
trace=trace,
|
366
|
+
qa=qa,
|
367
|
+
read_timeout=read_timeout,
|
368
|
+
cmd_timeout=cmd_timeout,
|
369
|
+
host_params=host_params,
|
370
|
+
)
|
371
|
+
return await self._cmd(pbcmd)
|
372
|
+
|
373
|
+
async def connect(self) -> None:
|
374
|
+
await super(GnetcliSessionCmd, self).connect()
|
375
|
+
if self._stub:
|
376
|
+
self._stream = self._stub.ExecChat(metadata=self._get_metadata())
|
377
|
+
else:
|
378
|
+
raise Exception()
|
379
|
+
|
380
|
+
|
381
|
+
class GnetcliSessionNetconf(GnetcliSession):
|
382
|
+
async def cmd(self, cmd: str, trace: bool = False, json: bool = False) -> Message:
|
383
|
+
_logger.debug("netconf session cmd %r", cmd)
|
384
|
+
cmdpb = server_pb2.CMDNetconf(host=self._hostname, cmd=cmd, json=json)
|
385
|
+
return await self._cmd(cmdpb)
|
386
|
+
|
387
|
+
async def connect(self) -> None:
|
388
|
+
await super(GnetcliSessionNetconf, self).connect()
|
389
|
+
if self._stub:
|
390
|
+
self._stream = self._stub.ExecNetconfChat(metadata=self._get_metadata())
|
391
|
+
else:
|
392
|
+
raise Exception()
|
393
|
+
|
394
|
+
|
395
|
+
async def grpc_call_wrapper(stub: grpc.UnaryUnaryMultiCallable, request: Any) -> Message:
|
396
|
+
last_exc: Optional[Exception] = None
|
397
|
+
response: Optional[Message] = None
|
398
|
+
for i in range(5):
|
399
|
+
req_id = make_req_id()
|
400
|
+
metadata = [
|
401
|
+
(HEADER_REQUEST_ID, req_id),
|
402
|
+
]
|
403
|
+
_logger.debug("executing %s: %r, req_id=%s", type(request), repr(request), req_id)
|
404
|
+
await asyncio.sleep(i * 2)
|
405
|
+
try:
|
406
|
+
response = await stub(request=request, metadata=metadata)
|
407
|
+
except grpc.aio.AioRpcError as e:
|
408
|
+
_logger.debug("caught exception %s req_id=%s %s", e, req_id, parse_grpc_error(e))
|
409
|
+
gn_exc, verbose = parse_grpc_error(e)
|
410
|
+
last_exc = gn_exc(
|
411
|
+
message=f"{e.__class__.__name__} {e.details()}",
|
412
|
+
imetadata=e.initial_metadata(), # type: ignore
|
413
|
+
request_id=req_id,
|
414
|
+
verbose=verbose,
|
415
|
+
)
|
416
|
+
last_exc.__cause__ = e
|
417
|
+
raise last_exc from None
|
418
|
+
else:
|
419
|
+
last_exc = None
|
420
|
+
break
|
421
|
+
|
422
|
+
if last_exc is not None:
|
423
|
+
raise last_exc
|
424
|
+
if response is None:
|
425
|
+
raise Exception()
|
426
|
+
else:
|
427
|
+
return response
|
428
|
+
|
429
|
+
|
430
|
+
def make_req_id() -> str:
|
431
|
+
return str(uuid.uuid4())
|
432
|
+
|
433
|
+
|
434
|
+
def get_cert(cert_file: Optional[str]) -> Optional[bytes]:
|
435
|
+
cert: Optional[bytes] = None
|
436
|
+
if cert_file:
|
437
|
+
_logger.debug("open cert_file %s", cert_file)
|
438
|
+
with open(cert_file, "rb") as f:
|
439
|
+
cert = f.read()
|
440
|
+
return cert
|
441
|
+
|
442
|
+
|
443
|
+
def format_long_msg(msg: str, max_len: int) -> str:
|
444
|
+
if len(msg) <= max_len:
|
445
|
+
return msg
|
446
|
+
return "%s... and %s more" % (msg[:max_len], len(msg) - max_len)
|
447
|
+
|
448
|
+
|
449
|
+
def make_cmd(
|
450
|
+
hostname: str,
|
451
|
+
cmd: str,
|
452
|
+
trace: bool = False,
|
453
|
+
qa: Optional[List[QA]] = None,
|
454
|
+
read_timeout: float = 0.0,
|
455
|
+
cmd_timeout: float = 0.0,
|
456
|
+
host_params: Optional[HostParams] = None,
|
457
|
+
) -> Message:
|
458
|
+
qa_cmd: List[Message] = []
|
459
|
+
if qa:
|
460
|
+
for item in qa:
|
461
|
+
qaitem = server_pb2.QA()
|
462
|
+
qaitem.question = item.question
|
463
|
+
qaitem.answer = item.answer
|
464
|
+
qa_cmd.append(qaitem)
|
465
|
+
res = server_pb2.CMD(
|
466
|
+
host=hostname,
|
467
|
+
cmd=cmd,
|
468
|
+
trace=trace,
|
469
|
+
qa=qa_cmd,
|
470
|
+
read_timeout=read_timeout,
|
471
|
+
cmd_timeout=cmd_timeout,
|
472
|
+
host_params=host_params.make_pb(),
|
473
|
+
)
|
474
|
+
return res # type: ignore
|
475
|
+
|
476
|
+
|
477
|
+
def make_files_request(files: Dict[str, File]) -> List[server_pb2.FileData]:
|
478
|
+
res: List[server_pb2.FileData] = []
|
479
|
+
for path, file in files.items():
|
480
|
+
res.append(server_pb2.FileData(path=path, data=file.content))
|
481
|
+
return res
|
gnetclisdk/exceptions.py
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
from typing import Optional, Sequence, Tuple, Type, Union
|
2
|
+
|
3
|
+
import grpc.aio
|
4
|
+
|
5
|
+
MetadataType = Sequence[Tuple[str, Union[str, bytes]]]
|
6
|
+
|
7
|
+
|
8
|
+
def extract_metadata(m: MetadataType) -> dict:
|
9
|
+
# calling get from metadataType throws KeyError
|
10
|
+
metadata = {}
|
11
|
+
for k, v in m:
|
12
|
+
metadata[k] = v
|
13
|
+
return metadata
|
14
|
+
|
15
|
+
|
16
|
+
class GnetcliException(Exception):
|
17
|
+
def __init__(
|
18
|
+
self,
|
19
|
+
message: str = "",
|
20
|
+
imetadata: Optional[MetadataType] = None,
|
21
|
+
request_id: Optional[str] = None,
|
22
|
+
verbose: Optional[str] = "",
|
23
|
+
):
|
24
|
+
self.message = message
|
25
|
+
if imetadata:
|
26
|
+
rs = extract_metadata(imetadata).get("real-server")
|
27
|
+
if rs:
|
28
|
+
self.message = f"{self.message} RS:{rs}"
|
29
|
+
if request_id:
|
30
|
+
self.message = f"{self.message} req_id:{request_id}"
|
31
|
+
if verbose:
|
32
|
+
self.message = f"{self.message} verbose:{verbose}"
|
33
|
+
super().__init__(self.message)
|
34
|
+
|
35
|
+
|
36
|
+
class DeviceConnectError(GnetcliException):
|
37
|
+
"""
|
38
|
+
Problem with connection to a device.
|
39
|
+
"""
|
40
|
+
|
41
|
+
pass
|
42
|
+
|
43
|
+
|
44
|
+
class UnknownDevice(GnetcliException):
|
45
|
+
"""
|
46
|
+
Host is not found in inventory
|
47
|
+
"""
|
48
|
+
|
49
|
+
pass
|
50
|
+
|
51
|
+
|
52
|
+
class DeviceAuthError(DeviceConnectError):
|
53
|
+
"""
|
54
|
+
Unable to authenticate on a device.
|
55
|
+
"""
|
56
|
+
|
57
|
+
pass
|
58
|
+
|
59
|
+
|
60
|
+
class ExecError(GnetcliException):
|
61
|
+
"""
|
62
|
+
Error happened during execution.
|
63
|
+
"""
|
64
|
+
|
65
|
+
pass
|
66
|
+
|
67
|
+
|
68
|
+
class NotReady(GnetcliException):
|
69
|
+
"""
|
70
|
+
Server is not ready.
|
71
|
+
"""
|
72
|
+
|
73
|
+
pass
|
74
|
+
|
75
|
+
|
76
|
+
class Unauthenticated(GnetcliException):
|
77
|
+
"""
|
78
|
+
Unable to authenticate on Gnetcli server.
|
79
|
+
"""
|
80
|
+
|
81
|
+
pass
|
82
|
+
|
83
|
+
|
84
|
+
class PermissionDenied(GnetcliException):
|
85
|
+
"""
|
86
|
+
Permission denied.
|
87
|
+
"""
|
88
|
+
|
89
|
+
pass
|
90
|
+
|
91
|
+
|
92
|
+
def parse_grpc_error(grpc_error: grpc.aio.AioRpcError) -> Tuple[Type[GnetcliException], str]:
|
93
|
+
code = grpc_error.code()
|
94
|
+
detail = ""
|
95
|
+
if grpc_error.details():
|
96
|
+
detail = grpc_error.details() # type: ignore
|
97
|
+
if code == grpc.StatusCode.UNAVAILABLE and detail == "not ready":
|
98
|
+
return NotReady, ""
|
99
|
+
if code == grpc.StatusCode.UNAUTHENTICATED:
|
100
|
+
return Unauthenticated, detail
|
101
|
+
if code == grpc.StatusCode.PERMISSION_DENIED:
|
102
|
+
return PermissionDenied, detail
|
103
|
+
if code == grpc.StatusCode.OUT_OF_RANGE:
|
104
|
+
return UnknownDevice, detail
|
105
|
+
if code == grpc.StatusCode.INTERNAL:
|
106
|
+
if detail == "auth_device_error":
|
107
|
+
verbose = ""
|
108
|
+
return DeviceAuthError, verbose
|
109
|
+
if detail in {"connection_error", "busy_error"}:
|
110
|
+
verbose = ""
|
111
|
+
return DeviceConnectError, verbose
|
112
|
+
elif detail in {"exec_error", "generic_error"}:
|
113
|
+
verbose = ""
|
114
|
+
return ExecError, verbose
|
115
|
+
|
116
|
+
return GnetcliException, ""
|