diff options
| author | John Estabrook <jestabro@vyos.io> | 2022-08-25 13:36:43 -0500 | 
|---|---|---|
| committer | John Estabrook <jestabro@vyos.io> | 2022-08-25 14:32:14 -0500 | 
| commit | b4646149b4993578707833df96e972f76a298ef7 (patch) | |
| tree | 0746771e98420c6a87225e5bb10834936ef9cbb5 | |
| parent | e8dc2c731697c7c76c75fa7844edde52dc0ff76b (diff) | |
| download | vyos-1x-b4646149b4993578707833df96e972f76a298ef7.tar.gz vyos-1x-b4646149b4993578707833df96e972f76a298ef7.zip | |
graphql: T4640: add schema defs and resolver support for op-mode errors
| -rw-r--r-- | src/services/api/graphql/bindings.py | 3 | ||||
| -rw-r--r-- | src/services/api/graphql/graphql/errors.py | 8 | ||||
| -rw-r--r-- | src/services/api/graphql/graphql/mutations.py | 13 | ||||
| -rw-r--r-- | src/services/api/graphql/graphql/queries.py | 13 | ||||
| -rw-r--r-- | src/services/api/graphql/session/errors/op_mode_errors.py | 13 | ||||
| -rw-r--r-- | src/services/api/graphql/session/session.py | 13 | ||||
| -rwxr-xr-x | src/services/api/graphql/utils/schema_from_op_mode.py | 43 | 
7 files changed, 96 insertions, 10 deletions
| diff --git a/src/services/api/graphql/bindings.py b/src/services/api/graphql/bindings.py index 049d59de7..0b1260912 100644 --- a/src/services/api/graphql/bindings.py +++ b/src/services/api/graphql/bindings.py @@ -17,6 +17,7 @@ import vyos.defaults  from . graphql.queries import query  from . graphql.mutations import mutation  from . graphql.directives import directives_dict +from . graphql.errors import op_mode_error  from . utils.schema_from_op_mode import generate_op_mode_definitions  from ariadne import make_executable_schema, load_schema_from_path, snake_case_fallback_resolvers @@ -27,6 +28,6 @@ def generate_schema():      type_defs = load_schema_from_path(api_schema_dir) -    schema = make_executable_schema(type_defs, query, mutation, snake_case_fallback_resolvers, directives=directives_dict) +    schema = make_executable_schema(type_defs, query, op_mode_error, mutation, snake_case_fallback_resolvers, directives=directives_dict)      return schema diff --git a/src/services/api/graphql/graphql/errors.py b/src/services/api/graphql/graphql/errors.py new file mode 100644 index 000000000..1066300e0 --- /dev/null +++ b/src/services/api/graphql/graphql/errors.py @@ -0,0 +1,8 @@ + +from ariadne import InterfaceType + +op_mode_error = InterfaceType("OpModeError") + +@op_mode_error.type_resolver +def resolve_op_mode_error(obj, *_): +    return obj['name'] diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py index c8ae0f516..1b77cff87 100644 --- a/src/services/api/graphql/graphql/mutations.py +++ b/src/services/api/graphql/graphql/mutations.py @@ -22,6 +22,8 @@ from makefun import with_signature  from .. import state  from .. import key_auth  from api.graphql.session.session import Session +from api.graphql.session.errors.op_mode_errors import op_mode_err_msg, op_mode_err_code +from vyos.opmode import Error as OpModeError  mutation = ObjectType("Mutation") @@ -86,10 +88,19 @@ def make_mutation_resolver(mutation_name, class_name, session_func):                  "success": True,                  "data": data              } +        except OpModeError as e: +            typename = type(e).__name__ +            return { +                "success": False, +                "errore": ['op_mode_error'], +                "op_mode_error": {"name": f"{typename}", +                                 "message": op_mode_err_msg.get(typename, "Unknown"), +                                 "vyos_code": op_mode_err_code.get(typename, 9999)} +            }          except Exception as error:              return {                  "success": False, -                "errors": [str(error)] +                "errors": [repr(error)]              }      return func_impl diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py index 921a66274..8ae61b704 100644 --- a/src/services/api/graphql/graphql/queries.py +++ b/src/services/api/graphql/graphql/queries.py @@ -22,6 +22,8 @@ from makefun import with_signature  from .. import state  from .. import key_auth  from api.graphql.session.session import Session +from api.graphql.session.errors.op_mode_errors import op_mode_err_msg, op_mode_err_code +from vyos.opmode import Error as OpModeError  query = ObjectType("Query") @@ -86,10 +88,19 @@ def make_query_resolver(query_name, class_name, session_func):                  "success": True,                  "data": data              } +        except OpModeError as e: +            typename = type(e).__name__ +            return { +                "success": False, +                "errors": ['op_mode_error'], +                "op_mode_error": {"name": f"{typename}", +                                 "message": op_mode_err_msg.get(typename, "Unknown"), +                                 "vyos_code": op_mode_err_code.get(typename, 9999)} +            }          except Exception as error:              return {                  "success": False, -                "errors": [str(error)] +                "errors": [repr(error)]              }      return func_impl diff --git a/src/services/api/graphql/session/errors/op_mode_errors.py b/src/services/api/graphql/session/errors/op_mode_errors.py new file mode 100644 index 000000000..7ba75455d --- /dev/null +++ b/src/services/api/graphql/session/errors/op_mode_errors.py @@ -0,0 +1,13 @@ + + +op_mode_err_msg = { +    "UnconfiguredSubsystem": "subsystem is not configured or not running", +    "DataUnavailable": "data currently unavailable", +    "PermissionDenied": "client does not have permission" +} + +op_mode_err_code = { +    "UnconfiguredSubsystem": 2000, +    "DataUnavailable": 2001, +    "PermissionDenied": 1003 +} diff --git a/src/services/api/graphql/session/session.py b/src/services/api/graphql/session/session.py index 23bc7154c..93e1c328e 100644 --- a/src/services/api/graphql/session/session.py +++ b/src/services/api/graphql/session/session.py @@ -22,6 +22,7 @@ from vyos.config import Config  from vyos.configtree import ConfigTree  from vyos.defaults import directories  from vyos.template import render +from vyos.opmode import Error as OpModeError  from api.graphql.utils.util import load_op_mode_as_module, split_compound_op_mode_name @@ -177,10 +178,10 @@ class Session:          mod = load_op_mode_as_module(f'{scriptname}')          func = getattr(mod, func_name) -        if len(list(data)) > 0: +        try:              res = func(True, **data) -        else: -            res = func(True) +        except OpModeError as e: +            raise e          return res @@ -199,9 +200,9 @@ class Session:          mod = load_op_mode_as_module(f'{scriptname}')          func = getattr(mod, func_name) -        if len(list(data)) > 0: +        try:              res = func(**data) -        else: -            res = func() +        except OpModeError as e: +            raise e          return res diff --git a/src/services/api/graphql/utils/schema_from_op_mode.py b/src/services/api/graphql/utils/schema_from_op_mode.py index f990aae52..379d15250 100755 --- a/src/services/api/graphql/utils/schema_from_op_mode.py +++ b/src/services/api/graphql/utils/schema_from_op_mode.py @@ -21,7 +21,7 @@  import os  import json  import typing -from inspect import signature, getmembers, isfunction +from inspect import signature, getmembers, isfunction, isclass, getmro  from jinja2 import Template  from vyos.defaults import directories @@ -35,6 +35,7 @@ SCHEMA_PATH = directories['api_schema']  DATA_DIR = directories['data']  op_mode_include_file = os.path.join(DATA_DIR, 'op-mode-standardized.json') +op_mode_error_schema = 'op_mode_error.graphql'  schema_data: dict = {'schema_name': '',                       'schema_fields': []} @@ -53,6 +54,7 @@ type {{ schema_name }} {  type {{ schema_name }}Result {      data: {{ schema_name }} +    op_mode_error: OpModeError      success: Boolean!      errors: [String]  } @@ -76,6 +78,7 @@ type {{ schema_name }} {  type {{ schema_name }}Result {      data: {{ schema_name }} +    op_mode_error: OpModeError      success: Boolean!      errors: [String]  } @@ -85,6 +88,21 @@ extend type Mutation {  }  """ +error_template = """ +interface OpModeError { +    name: String! +    message: String! +    vyos_code: Int! +} +{% for name in error_names %} +type {{ name }} implements OpModeError { +    name: String! +    message: String! +    vyos_code: Int! +} +{%- endfor %} +""" +  def _snake_to_pascal_case(name: str) -> str:      res = ''.join(map(str.title, name.split('_')))      return res @@ -136,7 +154,30 @@ def create_schema(func_name: str, base_name: str, func: callable) -> str:      return res +def create_error_schema(): +    from vyos import opmode + +    e = Exception +    err_types = getmembers(opmode, isclass) +    err_types = [k for k in err_types if issubclass(k[1], e)] +    # drop base class, to be replaced by interface type. Find the class +    # programmatically, in case the base class name changes. +    for i in range(len(err_types)): +        if err_types[i][1] in getmro(err_types[i-1][1]): +            del err_types[i] +            break +    err_names = [k[0] for k in err_types] +    error_data = {'error_names': err_names} +    j2_template = Template(error_template) +    res = j2_template.render(error_data) + +    return res +  def generate_op_mode_definitions(): +    out = create_error_schema() +    with open(f'{SCHEMA_PATH}/{op_mode_error_schema}', 'w') as f: +        f.write(out) +      with open(op_mode_include_file) as f:          op_mode_files = json.load(f) | 
