diff --git a/lib/yangtypes.py b/lib/yangtypes.py index a1b065cf..294703b4 100644 --- a/lib/yangtypes.py +++ b/lib/yangtypes.py @@ -723,6 +723,7 @@ def YANGDynClass(*args, **kwargs): extensions = kwargs.pop("extensions", None) extmethods = kwargs.pop("extmethods", None) is_keyval = kwargs.pop("is_keyval", False) + register_paths = kwargs.pop("register_paths", True) if not base_type: raise TypeError("must have a base type") if base_type in NUMPY_INTEGER_TYPES and len(args): @@ -754,7 +755,8 @@ class YANGBaseClass(base_type): __slots__ = ('_default', '_mchanged', '_yang_name', '_choice', '_parent', '_supplied_register_path', '_path_helper', '_base_type', '_is_leaf', '_is_container', '_extensionsd', - '_pybind_base_class', '_extmethods', '_is_keyval') + '_pybind_base_class', '_extmethods', '_is_keyval', + '_register_paths') _pybind_base_class = re.sub("<(type|class) '(?P.*)'>", "\g", str(base_type)) @@ -777,6 +779,7 @@ def __init__(self, *args, **kwargs): self._extensionsd = extensions self._extmethods = extmethods self._is_keyval = is_keyval + self._register_paths = register_paths if default: self._default = default @@ -789,9 +792,11 @@ def __init__(self, *args, **kwargs): if not self._is_container == "list": if self._path_helper: if self._supplied_register_path is None: - self._path_helper.register(self._register_path(), self) + if self._register_paths: + self._path_helper.register(self._register_path(), self) else: - self._path_helper.register(self._supplied_register_path, self) + if self._register_paths: + self._path_helper.register(self._supplied_register_path, self) if self._is_container == 'list' or self._is_container == 'container': kwargs['path_helper'] = self._path_helper diff --git a/pybind.py b/pybind.py index 488e041d..de1d3af1 100644 --- a/pybind.py +++ b/pybind.py @@ -228,6 +228,14 @@ def add_opts(self, optparser): help="""Allow a path-keyed dictionary to be used to specify methods related to a particular class"""), + optparse.make_option("--build-rpcs", + dest="build_rpcs", + action="store_true", + help="""Generate class bindings for + the input and output of RPCs + defined in each module. These + are placed at the root of + each module"""), ] g = optparser.add_option_group("pyangbind output specific options") g.add_options(optlist) @@ -355,6 +363,15 @@ def build_pybind(ctx, modules, fd): if ch.keyword in statements.data_definition_keywords] get_children(ctx, fd, children, m, m) + if ctx.opts.build_rpcs: + rpcs = [ch for ch in module.i_children + if ch.keyword == 'rpc'] + # Build RPCs specifically under the module name, since this + # can be used as a proxy for the namespace. + if len(rpcs): + get_children(ctx, fd, rpcs, module, module, register_paths=False, + path="/%s_rpc" % (safe_name(module.arg))) + def build_identities(ctx, defnd): # Build dicionaries which determine how identities work. Essentially, an @@ -601,7 +618,7 @@ def find_definitions(defn, ctx, module, prefix): def get_children(ctx, fd, i_children, module, parent, path=str(), - parent_cfg=True, choice=False): + parent_cfg=True, choice=False, register_paths=True): # Iterative function that is called for all elements that have childen # data nodes in the tree. This function resolves those nodes into the # relevant leaf, or container/list configuration and outputs the python @@ -671,6 +688,7 @@ def get_children(ctx, fd, i_children, module, parent, path=str(), # choice specified. if ctx.opts.split_class_dir: import_req = [] + for ch in i_children: if ch.keyword == "choice": for choice_ch in ch.i_children: @@ -678,10 +696,11 @@ def get_children(ctx, fd, i_children, module, parent, path=str(), for case_ch in choice_ch.i_children: elements += get_element(ctx, fd, case_ch, module, parent, path + "/" + ch.arg, parent_cfg=parent_cfg, - choice=(ch.arg, choice_ch.arg)) + choice=(ch.arg, choice_ch.arg), register_paths=register_paths) else: elements += get_element(ctx, fd, ch, module, parent, path + "/" + ch.arg, - parent_cfg=parent_cfg, choice=choice) + parent_cfg=parent_cfg, choice=choice, register_paths=register_paths) + if ctx.opts.split_class_dir: if hasattr(ch, "i_children") and len(ch.i_children): import_req.append(ch.arg) @@ -696,7 +715,8 @@ def get_children(ctx, fd, i_children, module, parent, path=str(), # 'container', 'module', 'list' and 'submodule' all have their own classes # generated. - if parent.keyword in ["container", "module", "list", "submodule"]: + if parent.keyword in ["container", "module", "list", "submodule", "input", + "output", "rpc"]: if ctx.opts.split_class_dir: nfd.write("class %s(PybindBase):\n" % safe_name(parent.arg)) else: @@ -890,6 +910,7 @@ def get_children(ctx, fd, i_children, module, parent, path=str(), choices[i["choice"][0]][i["choice"][1]].append(i["name"]) class_str["arg"] += ", path_helper=self._path_helper" class_str["arg"] += ", extmethods=self._extmethods" + class_str["arg"] += ", register_paths=%s" % i["register_paths"] if "extensions" in i: class_str["arg"] += ", extensions=%s" % i["extensions"] if keyval and i["yang_name"] in keyval: @@ -1239,7 +1260,7 @@ def find_absolute_default_type(default_type, default_value, elemname): def get_element(ctx, fd, element, module, parent, path, - parent_cfg=True, choice=False): + parent_cfg=True, choice=False, register_paths=True): # Handle mapping of an invidual element within the model. This function # produces a dictionary that can then be mapped into the relevant code that # dynamically generates a class. @@ -1256,9 +1277,10 @@ def get_element(ctx, fd, element, module, parent, path, elemdescr = elemdescr.arg # If the element has an i_children attribute then this is a container, list - # leaf-list or choice. + # leaf-list or choice. Alternatively, it can be the 'input' or 'output' + # substmts of an RPC if hasattr(element, 'i_children'): - if element.keyword in ["container", "list"]: + if element.keyword in ["container", "list", "input", "output"]: has_children = True elif element.keyword in ["leaf-list"]: create_list = True @@ -1278,7 +1300,7 @@ def get_element(ctx, fd, element, module, parent, path, if element.i_children: chs = element.i_children get_children(ctx, fd, chs, module, element, npath, parent_cfg=parent_cfg, - choice=choice) + choice=choice, register_paths=register_paths) elemdict = { "name": safe_name(element.arg), "origtype": element.keyword, @@ -1287,6 +1309,7 @@ def get_element(ctx, fd, element, module, parent, path, "description": elemdescr, "yang_name": element.arg, "choice": choice, + "register_paths": register_paths, } # Handle the different cases of class name, this depends on whether we # were asked to split the bindings into a directory structure or not. @@ -1490,6 +1513,7 @@ def get_element(ctx, fd, element, module, parent, path, "quote_arg": quote_arg, "description": elemdescr, "yang_name": element.arg, "choice": choice, + "register_paths": register_paths, } if cls == "leafref": elemdict["referenced_path"] = elemtype["referenced_path"] diff --git a/tests/rpc/lib b/tests/rpc/lib new file mode 120000 index 00000000..58677ddb --- /dev/null +++ b/tests/rpc/lib @@ -0,0 +1 @@ +../../lib \ No newline at end of file diff --git a/tests/rpc/rpc.yang b/tests/rpc/rpc.yang new file mode 100644 index 00000000..f1896d91 --- /dev/null +++ b/tests/rpc/rpc.yang @@ -0,0 +1,121 @@ +module rpc { + yang-version "1"; + namespace "http://rob.sh/yang/test/rpc"; + prefix "foo"; + organization "BugReports Inc"; + contact "A bug reporter"; + + description + "A test module"; + revision 2014-01-01 { + description "april-fools"; + reference "fooled-you"; + } + + rpc check { + description + "Basic RPC with a single input argument"; + input { + leaf argument { + type string; + } + } + } + + rpc check-two { + description + "Basic RPC check with two leaves in output"; + output { + leaf arg-one { + type int8; + } + + leaf arg-two { + type int8; + } + } + } + + rpc check-three { + description + "RPC check with a container under input"; + input { + container arguments { + leaf arg-one { + type string; + } + + leaf arg-two { + type string; + } + } + } + } + + + rpc check-four { + description + "RPC check with multiple containers under output"; + output { + container arguments { + leaf arg-one { + type string; + } + } + + container arguments-two { + leaf arg-two { + type string; + } + } + } + } + + rpc check-five { + description + "RPC check with input and output structures"; + + input { + container arguments { + leaf arg-one { + type string; + } + } + } + + output { + container return-values { + leaf return-val { + type int8; + } + } + } + } + + rpc check-six { + description + "RPC check with input and output values using a leafref which + requires use of the XPATHHELPER"; + + input { + leaf argument { + type leafref { + path "/test/reference-target"; + require-instance true; + } + } + } + + output { + leaf out { + type string; + } + } + } + + container test { + leaf-list reference-target { + type string; + } + } +} diff --git a/tests/rpc/run.py b/tests/rpc/run.py new file mode 100755 index 00000000..94313ac9 --- /dev/null +++ b/tests/rpc/run.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python + +import os +import sys +import getopt + +TESTNAME = "rpc" + + +# generate bindings in this folder +def main(): + try: + opts, args = getopt.getopt(sys.argv[1:], "k", ["keepfiles"]) + except getopt.GetoptError as e: + print str(e) + sys.exit(127) + + k = False + for o, a in opts: + if o in ["-k", "--keepfiles"]: + k = True + + pyangpath = os.environ.get('PYANGPATH') if \ + os.environ.get('PYANGPATH') is not None else False + pyangbindpath = os.environ.get('PYANGBINDPATH') if \ + os.environ.get('PYANGBINDPATH') is not None else False + assert pyangpath is not False, "could not find path to pyang" + assert pyangbindpath is not False, "could not resolve pyangbind directory" + + pyangpath = os.environ.get('PYANGPATH') if \ + os.environ.get('PYANGPATH') is not None else False + pyangbindpath = os.environ.get('PYANGBINDPATH') if \ + os.environ.get('PYANGBINDPATH') is not None else False + assert pyangpath is not False, "could not find path to pyang" + assert pyangbindpath is not False, "could not resolve pyangbind directory" + + this_dir = os.path.dirname(os.path.realpath(__file__)) + os.system(("%s --plugindir %s -f pybind --split-class-dir=%s/bindings " + + "--build-rpc --use-xpathhelper " + + "--pybind-class-dir=%s %s/%s.yang") % (pyangpath, pyangbindpath, + this_dir, pyangbindpath, this_dir, TESTNAME)) + + from lib.xpathhelper import YANGPathHelper + ph = YANGPathHelper() + + import_error = None + set_argument_error = None + try: + from bindings.rpc_rpc import check + ch = check.check(path_helper=ph) + ch.input.argument = "test" + except ImportError, m: + import_error = m + except ValueError, m: + set_argument_error = m + + assert import_error is None, "Could not import check RPC: %s" \ + % (import_error) + assert set_argument_error is None, "Could not set argument to string: %s" \ + % (set_argument_error) + + import_error = None + instantiation_error = None + try: + from bindings.rpc_rpc.check_two import output as chktwo_output + ch = chktwo_output.output(path_helper=ph) + except ImportError, m: + import_error = m + except TypeError, m: + instantiation_error = m + + assert import_error is None, "Could not import check_two RPC output: %s" \ + % (import_error) + assert instantiation_error is None, "Could not instantiate check_two " + \ + + "output: %s" % (instantiation_error) + + val_set = True + try: + ch.arg_one = 10 + ch.arg_two = 20 + except ValueError, m: + val_set = False + + assert val_set is True, "Could not set output leaf arguments directly" + \ + + ": %s" % (m) + + from bindings.rpc_rpc import check_three, check_four, check_five, check_six + + ch3 = check_three.check_three(path_helper=ph) + ch4 = check_four.check_four(path_helper=ph) + ch5 = check_five.check_five(path_helper=ph) + ch6 = check_six.check_six(path_helper=ph) + + attribute_err = None + value_err = None + try: + ch3.input.arguments.arg_one = "test string" + except AttributeError, m: + attribute_err = m + except ValueError, m: + value_err = m + + assert attribute_err is None, "Expected attribute for ch3 did not exist" \ + + " (arg-one): %s" % attribute_err + assert value_err is None, "Expected value could not be set for ch3" \ + + " (arg-one): %s" % value_err + + attribute_err = None + value_err = None + try: + ch3.input.arguments.arg_two = "test string" + except AttributeError, m: + attribute_err = m + except ValueError, m: + value_err = m + + assert attribute_err is None, "Expected attribute for ch3 did not exist" \ + + " (arg-two): %s" % attribute_err + assert value_err is None, "Expected value could not be set for ch3" \ + + " (arg-two): %s" % value_err + + attribute_err = None + value_err = None + try: + ch4.output.arguments.arg_one = "test string" + except AttributeError, m: + attribute_err = m + except ValueError, m: + value_err = m + + assert attribute_err is None, "Expected attribute for ch4 did not exist" \ + + " (arg-one): %s" % attribute_err + assert value_err is None, "Expected value could not be set for ch4" \ + + " (arg-one): %s" % value_err + + attribute_err = None + value_err = None + try: + ch4.output.arguments_two.arg_two = "test string" + except AttributeError, m: + attribute_err = m + except ValueError, m: + value_err = m + + assert attribute_err is None, "Expected attribute for ch4 did not exist" \ + + " (arg-two): %s" % attribute_err + assert value_err is None, "Expected value could not be set for ch4" \ + + " (arg-two): %s" % value_err + + attribute_err = None + value_err = None + try: + ch5.input.arguments.arg_one = "test string" + except AttributeError, m: + attribute_err = m + except ValueError, m: + value_err = m + + assert attribute_err is None, "Expected attribute for ch5 did not exist" \ + + " (arg-one): %s" % attribute_err + assert value_err is None, "Expected value could not be set for ch5" \ + + " (arg-one): %s" % value_err + + attribute_err = None + value_err = None + try: + ch5.output.return_values.return_val = 10 + except AttributeError, m: + attribute_err = m + except ValueError, m: + value_err = m + + assert attribute_err is None, "Expected attribute for ch4 did not exist" \ + + " (arg-two): %s" % attribute_err + assert value_err is None, "Expected value could not be set for ch5" \ + + " (arg-two): %s" % value_err + + assert ph.tostring() == "", "Attributes within an RPC registered" + \ + " in the path helper erroneously" + + from bindings import rpc + + r = rpc(path_helper=ph) + r.test.reference_target.append('six') + + set_v = True + try: + ch6.input.argument = 'six' + except ValueError, m: + set_v = False + + assert set_v is True, "Could not set value of a leafref in an RPC to a" + \ + "known good value: %s != True (ch.input.argument -> six)" % (set_v) + + set_v = True + try: + ch6.input.argument = 'fish' + except ValueError, m: + set_v = False + + assert set_v is False, "Set value of a leafref in an RPC to a" + \ + "known bad value: %s != False (ch.input.argument -> fish)" % (set_v) + + if not k: + os.system("/bin/rm -rf %s/bindings" % this_dir) + +if __name__ == '__main__': + main()