YANG is a data modelling language, which allows modelling of Remote Procedure Calls (RPCs). YANG is widely used in networking for modelling configurational and operational data of network devices (more than just routers and switches). These devices also implement some of the well-defined protocols for transport of the data modelled in YANG e.g.: NETCONF [RFC6241], RESTCONF [RFC8040] or GRPC [OpenConfig].

Network controller software can manage such devices implementing these protocols, by using the YANG models of the configuration and operational data. This way the controller or other software applications on top of the controller can configure and monitor network of huge size. OpenDaylight is the leading open source SDN controller, which provides such functionalities.

Meet lighty.io, which is based on OpenDaylight components with additional Pantheon Technologies plugins and tools for fast development of lightweight SDN applications. One of the provided extensions by lighty.io is unified API for YANG modelled data transformations. This API is important for integration of lighty.io with external systems. This article describes how to model RPC in YANG modelling language, how to generate Java classes from the YANG model and how to implement the modelled RPC over HTTP protocol using the Codecs component of lighty.io.

The YANG model of the example RPC (see the yang model file for more details) is pretty straight forward:

    rpc compute-sum {
        input {
            leaf operand-a {
                type uint16;
                mandatory true;
            }

            leaf operand-b {
                type uint16;
                mandatory true;
            }
        }
        output {
            leaf sum {
                type uint32;
                mandatory true;
            }
        }
    }

Input of the RPC consists of two numbers and the output represents the sum of input numbers.

The ODL yang-maven-plugin is used to generate Java classes and interfaces from the Yang model (see the pom.xml of this example for more details). An interface called LightyRpcExampleService is generated, which defines the RPC as Java method with the input container as argument and output container as return value (wrapped by Future<RpcResult<>>).

Now we need to provide implementation for this freshly generated interface:

package tech.pantheon.lighty.examples.rpc;

import org.opendaylight.yang.gen.v1.urn.pantheon.params.xml.ns.yang.lighty.rpc.example.rev180219.ComputeSumInput;
import org.opendaylight.yang.gen.v1.urn.pantheon.params.xml.ns.yang.lighty.rpc.example.rev180219.ComputeSumOutput;
import org.opendaylight.yang.gen.v1.urn.pantheon.params.xml.ns.yang.lighty.rpc.example.rev180219.ComputeSumOutputBuilder;
import org.opendaylight.yang.gen.v1.urn.pantheon.params.xml.ns.yang.lighty.rpc.example.rev180219.LightyRpcExampleService;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.opendaylight.yangtools.yang.common.RpcResultBuilder;

import java.util.concurrent.Future;

public class LightyRpcExampleServiceImpl implements LightyRpcExampleService {

    public Future<RpcResult<ComputeSumOutput>> computeSum(ComputeSumInput input) {
        return RpcResultBuilder.success(
                new ComputeSumOutputBuilder()
                        .setSum((long) input.getOperandA() + input.getOperandB())
                        .build())
                .buildFuture();
    }
}

Next step is to write the code, which will decode serialized input (received as HTTP payload), call the RPC and serialize resulting output to be sent back to client. At the beginning it is needed to initialize a schema context including the YANG model which is needed for serialization and deserialization of modelled data. This example shows how the schema context and RPC definition objects are initialized in the constructor of class RpcHttpServer from this example:

        System.out.println("Loading Yang model schema context");

        /* Load YANG models available at the class path */
        List<YangModuleInfo> moduleInfos = new LinkedList<>();
        ServiceLoader<YangModelBindingProvider> yangProviderLoader = ServiceLoader.load(YangModelBindingProvider.class);
        for (YangModelBindingProvider yangModelBindingProvider : yangProviderLoader) {
            moduleInfos.add(yangModelBindingProvider.getModuleInfo());
        }

        /* Create the schema context for loaded models */
        ModuleInfoBackedContext moduleInfoBackedCntxt = ModuleInfoBackedContext.create();
        moduleInfoBackedCntxt.addModuleInfos(moduleInfos);
        Optional<SchemaContext> schemaCtx =
                moduleInfoBackedCntxt.tryToCreateSchemaContext().toJavaUtil();
        if (!schemaCtx.isPresent()) {
            throw new IllegalStateException("Failed to load schema context");
        }
        this.schemaContext = schemaCtx.get();
        System.out.println("Yang model schema context loaded");

        /* Load the RPC used in this example */
        Optional<RpcDefinition> loadRpc = ConverterUtils.loadRpc(schemaContext, rpcServiceQname);
        if (!loadRpc.isPresent()) {
            throw new IllegalStateException("RPC definition not found");
        }
        this.rpcDef = loadRpc.get();
        System.out.println("Loaded definition of RPC: " + this.rpcDef.getQName().getLocalName());

The last code snippet shows how to deserialize the incoming payload of HTTP request encoded in JSON using a codec object. Then the method implementing RPC is called with the input object as argument and output object is obtained as a return value of the method. The output object is then serialized to JSON using the same codec object as for input HTTP payload. The output JSON data can be written into HTTP response payload (see the example source code for more details).

                /* Read the payload from HTTP request */
                final String payload = com.google.common.io.CharStreams.toString(req.getReader());
                
                if (payload.isEmpty()) {
                    System.out.println("Nothing received");
                    return;
                } else {
                    System.out.println("Received payload: " + payload);
                }

                /* Create codec for the RPC input and load the RPC definition */
                DataCodec<ComputeSumInput> dataCodec = new DataCodec<>(schemaContext);

                /* Deserialize the payload to normalized node */
                NormalizedNode rpcInputNode =
                        dataCodec.withJson().deserialize(rpcDef, new StringReader(payload));

                /* Convert the normalized node to RPC's input object */
                ComputeSumInput rpcInput =
                        dataCodec.convertToBindingAwareRpc(rpcDef.getInput().getPath(),
                                                           (ContainerNode) rpcInputNode);

                /* Call the RPC and get RPC output */
                ComputeSumOutput rpcOutput =
                        rpcImpl.computeSum(rpcInput).get().getResult();

                /* Serialize the RPC output */
                Writer output = dataCodec.withJson().serializeRpc(rpcDef.getOutput(),
                                                                  dataCodec.convertToBindingIndependentRpc(rpcOutput));

                System.out.println("RPC output: " + output.toString());

You can use the following to test the RPC, and see the output:

curl -H “Content-Type: application/json” -X POST -d ‘{ “input”: { “operand-a”: 200, “operand-b”: 22 }}’ http://localhost:8080

{“output”:{“sum”:222}}

… sample output from the Java app above:

Loading Yang model schema context
Yang model schema context loaded
Loaded definition of RPC: compute-sum
HTTP server started at port 8080
Press any key to exit...
Received payload: { "input": { "operand-a": 200, "operand-b": 22 }}
RPC output: {"output":{"sum":222}}

This video shows a simple implementation of RPC :

Categories: lighty.io