Skip to content

Commit

Permalink
Merge pull request #9 from truenas/backwards-compatibility
Browse files Browse the repository at this point in the history
NAS-130663 / 25.04 / Make client backwards-compatible
  • Loading branch information
yocalebo authored Aug 16, 2024
2 parents 6b302ab + a36f1da commit 76a4a29
Show file tree
Hide file tree
Showing 4 changed files with 595 additions and 70 deletions.
92 changes: 22 additions & 70 deletions truenas_api_client/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import argparse
from base64 import b64decode
from collections import defaultdict, namedtuple
from collections import defaultdict
import errno
import logging
import os
Expand All @@ -22,23 +22,33 @@
from websocket._socket import sock_opt

from . import ejson as json
from .config import CALL_TIMEOUT
from .exc import ReserveFDException, ClientException, ErrnoMixin, ValidationErrors, CallTimeout
from .legacy import LegacyClient
from .jsonrpc import JSONRPCError
from .utils import MIDDLEWARE_RUN_DIR, ProgressBar, undefined
try:
from libzfs import Error as ZFSError
except ImportError:
# this happens on our CI/CD runners as they do not install the py-libzfs module to run our api integration tests
LIBZFS = False
else:
LIBZFS = True

logger = logging.getLogger(__name__)

CALL_TIMEOUT = int(os.environ.get('CALL_TIMEOUT', 60))

class Client:
def __init__(self, uri=None, reserved_ports=False, py_exceptions=False, log_py_exceptions=False,
call_timeout=undefined, verify_ssl=True):
if uri is not None and uri.endswith('/websocket'):
client_class = LegacyClient
else:
client_class = JSONRPCClient

self.__client = client_class(uri, reserved_ports, py_exceptions, log_py_exceptions, call_timeout, verify_ssl)

def __getattr__(self, item):
return getattr(self.__client, item)

class ReserveFDException(Exception):
pass
def __enter__(self):
return self.__client.__enter__()

def __exit__(self, exc_type, exc_val, exc_tb):
return self.__client.__exit__(exc_type, exc_val, exc_tb)


class WSClient:
Expand Down Expand Up @@ -200,61 +210,7 @@ def result(self):
return job['result']


class ErrnoMixin:
ENOMETHOD = 201
ESERVICESTARTFAILURE = 202
EALERTCHECKERUNAVAILABLE = 203
EREMOTENODEERROR = 204
EDATASETISLOCKED = 205
EINVALIDRRDTIMESTAMP = 206
ENOTAUTHENTICATED = 207
ESSLCERTVERIFICATIONERROR = 208

@classmethod
def _get_errname(cls, code):
if LIBZFS and 2000 <= code <= 2100:
return 'EZFS_' + ZFSError(code).name
for k, v in cls.__dict__.items():
if k.startswith("E") and v == code:
return k


class ClientException(ErrnoMixin, Exception):
def __init__(self, error, errno=None, trace=None, extra=None):
self.errno = errno
self.error = error
self.trace = trace
self.extra = extra

def __str__(self):
return self.error


Error = namedtuple('Error', ['attribute', 'errmsg', 'errcode'])


class ValidationErrors(ClientException):
def __init__(self, errors):
self.errors = []
for e in errors:
self.errors.append(Error(e[0], e[1], e[2]))

super().__init__(str(self))

def __str__(self):
msgs = []
for e in self.errors:
errcode = errno.errorcode.get(e.errcode, 'EUNKNOWN')
msgs.append(f'[{errcode}] {e.attribute or "ALL"}: {e.errmsg}')
return '\n'.join(msgs)


class CallTimeout(ClientException):
def __init__(self):
super().__init__("Call timeout", errno.ETIMEDOUT)


class Client:
class JSONRPCClient:
def __init__(self, uri=None, reserved_ports=False, py_exceptions=False, log_py_exceptions=False,
call_timeout=undefined, verify_ssl=True):
"""
Expand All @@ -264,10 +220,6 @@ def __init__(self, uri=None, reserved_ports=False, py_exceptions=False, log_py_e
if uri is None:
uri = f'ws+unix://{MIDDLEWARE_RUN_DIR}/middlewared.sock'

# FIXME: Jenkins pipeline has a legacy URL
if uri.endswith('/websocket'):
uri = uri.removesuffix('/websocket') + '/api/current'

if call_timeout is undefined:
call_timeout = CALL_TIMEOUT

Expand Down
3 changes: 3 additions & 0 deletions truenas_api_client/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import os

CALL_TIMEOUT = int(os.environ.get("CALL_TIMEOUT", 60))
68 changes: 68 additions & 0 deletions truenas_api_client/exc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from collections import namedtuple
import errno

try:
from libzfs import Error as ZFSError
except ImportError:
# this happens on our CI/CD runners as they do not install the py-libzfs module to run our api integration tests
LIBZFS = False
else:
LIBZFS = True


class ReserveFDException(Exception):
pass


class ErrnoMixin:
ENOMETHOD = 201
ESERVICESTARTFAILURE = 202
EALERTCHECKERUNAVAILABLE = 203
EREMOTENODEERROR = 204
EDATASETISLOCKED = 205
EINVALIDRRDTIMESTAMP = 206
ENOTAUTHENTICATED = 207
ESSLCERTVERIFICATIONERROR = 208

@classmethod
def _get_errname(cls, code):
if LIBZFS and 2000 <= code <= 2100:
return 'EZFS_' + ZFSError(code).name
for k, v in cls.__dict__.items():
if k.startswith("E") and v == code:
return k


class ClientException(ErrnoMixin, Exception):
def __init__(self, error, errno=None, trace=None, extra=None):
self.errno = errno
self.error = error
self.trace = trace
self.extra = extra

def __str__(self):
return self.error


Error = namedtuple('Error', ['attribute', 'errmsg', 'errcode'])


class ValidationErrors(ClientException):
def __init__(self, errors):
self.errors = []
for e in errors:
self.errors.append(Error(e[0], e[1], e[2]))

super().__init__(str(self))

def __str__(self):
msgs = []
for e in self.errors:
errcode = errno.errorcode.get(e.errcode, 'EUNKNOWN')
msgs.append(f'[{errcode}] {e.attribute or "ALL"}: {e.errmsg}')
return '\n'.join(msgs)


class CallTimeout(ClientException):
def __init__(self):
super().__init__("Call timeout", errno.ETIMEDOUT)
Loading

0 comments on commit 76a4a29

Please sign in to comment.