Real-life deployments are always challenging. Usually, the story goes like this:
You have your network devices to manage, a controller that usually provides a normalized interface for your network. Great, and now what? The next logical step is to integrate the SDN controller with overlaying management tools and systems. This is an easy job if the controller uses the RESTCONF interface, described by YANG models.
There is a catch, though.
Clients need to understand the semantics of the XML, or JSON data within RESTCONF messages.
lighty.io provides client libraries for Java, Go, and Python. For Java, there are three types of client libraries:
- Raw data client. This client provides convenient APIs for common operations like GET, POST, PUT, and DELETE. On top of that, there are APIs to call RPCs and subscribe to notifications. But wait, this is still raw API without types, isn’t it? Check binding independent and binding aware client APIs to get more information.
package io.lighty.clients.rest.api; import io.lighty.clients.rest.common.query.parameters.QueryParameters; import io.lighty.clients.rest.common.response.Response; import io.lighty.clients.rest.impl.RawListenerRegistration; import io.lighty.clients.rest.notifications.RawEstablishSubscriptionInput; import io.lighty.clients.rest.notifications.RawNotificationListener; import java.util.concurrent.CompletableFuture; import javax.ws.rs.core.MediaType; /** * Allows to configure devices with raw String. */ public interface RawRestClient extends AutoCloseable { /** * Read data - GET HTTP operation - allows to read data from device. * * @param path * - path to data * @param parameters * - optional parameters of GET operation (content, depth, fields) * @param acceptType * - acceptable type of data * @param contentType * - content type of data * @return completion stage with successful(data)/unsuccessful(error) response */ CompletableFuture<Response<String, Exception>> get(String path, QueryParameters parameters, MediaType acceptType, MediaType contentType); /** * Create data - POST HTTP operation - allows to create data on device. * * @param path * - path to data * @param data * - data * @param parameters * - optional parameters of POST operation (insert, point) * @param acceptType * - acceptable type of data * @param contentType * - content type of data * @return completion stage with successful/unsuccessful(error) response */ CompletableFuture<Response<Void, Exception>> post(String path, String data, QueryParameters parameters, MediaType acceptType, MediaType contentType); /** * Update data - PUT HTTP operation - allows to update data on device. * * @param path * - path to data * @param data * - data * @param parameters * - optional parameters of PUT operation (insert, point) * @param acceptType * - acceptable type of data * @param contentType * - content type of data * @return completion stage with successful/unsuccessful(error) response */ CompletableFuture<Response<Void, Exception>> put(String path, String data, QueryParameters parameters, MediaType acceptType, MediaType contentType); /** * Delete data - DELETE HTTP operation - allows to delete data on device. * * @param path * - path to data * @param acceptType * - acceptable type of data * @param contentType * - content type of data * @return completion stage with successful/unsuccessful(error) response */ CompletableFuture<Response<Void, Exception>> delete(String path, MediaType acceptType, MediaType contentType); /** * Invoke RPC - allows to invoke RPC on device. * * @param path * - path of RPC * @param input * - input of RPC * @param outputType * - RPC output * @param acceptType * - acceptable type of data * @param contentType * - content type of data * @return completion stage with successful(data)/unsuccessful(error) response */ CompletableFuture<Response<String, Exception>> invokeRpc(String path, String input, Class<String> outputType, MediaType acceptType, MediaType contentType); /** * Invoke RPC - allows to invoke RPC on device without output. * * @param path * - path of RPC * @param input * - input of RPC * @param acceptType * - acceptable type of data * @param contentType * - content type of data * @return completion stage with successful/unsuccessful(error) response */ CompletableFuture<Response<String, Exception>> invokeRpc(String path, String input, MediaType acceptType, MediaType contentType); /** * Register notification listener to start listen on notifications. Specific registration parameters can be found in * builder for establish subscription. * * @param notificationListener * - {@link RawNotificationListener raw notification listener} * @param establishBuilder * - builder with specific parameters for establish subscription * @return registration of listener, allows modify or kill registered subscription */ RawListenerRegistration registerNotificationListener(RawNotificationListener notificationListener, RawEstablishSubscriptionInput establishBuilder); }
- Binding independent client. This client is based on Raw data client and provides “easy to use” APIs like GET, POST, PUT and DELETE, call RPC and subscribe to notifications.
package io.lighty.clients.rest.api; import io.lighty.clients.rest.binding.common.device.MountPoint; import io.lighty.clients.rest.binding.common.notifications.BindingSubscriptionEstablish; import io.lighty.clients.rest.common.query.parameters.QueryParameters; import io.lighty.clients.rest.common.response.Response; import io.lighty.clients.rest.impl.BindingIndependentListenerRegistrationImpl; import java.util.concurrent.CompletableFuture; import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.model.api.SchemaPath; /** * Allows to configure devices with Binding Independent (DOM) objects. */ public interface BindingIndependentRestClient extends AutoCloseable { /** * Read data - GET HTTP operation - allows to read data from device. * * @param path * - {@link YangInstanceIdentifier Binding Independent path} to data * @param mountPoint * - for configure mount point (can be null if doesn't exist) * @param parameters * - optional parameters of GET operation (content, depth, fields) * @return completion stage with successful(data)/unsuccessful(error) response */ CompletableFuture<Response<NormalizedNode, Exception>> get(YangInstanceIdentifier path, MountPoint mountPoint, QueryParameters parameters); /** * Create data - POST HTTP operation - allows to create data on device. * * @param data * - {@link NormalizedNode Binding Independent data} * @param mountPoint * - for configure mount point (can be null if doesn't exist) * @param parameters * - optional parameters of POST operation (insert, point) * @return completion stage with successful/unsuccessful(error) response */ CompletableFuture<Response<Void, Exception>> post(NormalizedNode data, MountPoint mountPoint, QueryParameters parameters); /** * Update data - PUT HTTP operation - allows to update data on device. * * @param path * - {@link YangInstanceIdentifier Binding Independent path} to data * @param data * - {@link NormalizedNode Binding Independent data} * @param mountPoint * - for configure mount point (can be null if doesn't exist) * @param parameters * - optional parameters of PUT operation (insert, point) * @return completion stage with successful/unsuccessful(error) response */ CompletableFuture<Response<Void, Exception>> put(YangInstanceIdentifier path, NormalizedNode data, MountPoint mountPoint, QueryParameters parameters); /** * Delete data - DELETE HTTP operation - allows to delete data on device. * * @param path * - {@link YangInstanceIdentifier Binding Independent path} to data * @param mountPoint * - for configure mount point (can be null if doesn't exist) * @return completion stage with successful/unsuccessful(error) response */ CompletableFuture<Response<Void, Exception>> delete(YangInstanceIdentifier path, MountPoint mountPoint); /** * Invoke RPC - allows to invoke RPC on device. * * @param path * - {@link SchemaPath Path} of RPC * @param input * - {@link NormalizedNode Binding Independent input} of RPC * @param outputType * - RPC output * @param mountPoint * - for configure mount point (can be null if doesn't exist) * @return completion stage with successful(data)/unsuccessful(error) response */ CompletableFuture<Response<NormalizedNode, Exception>> invokeRpc(SchemaPath path, NormalizedNode input, Class<NormalizedNode> outputType, MountPoint mountPoint); /** * Invoke RPC - allows to invoke RPC on device without output. * * @param path * - {@link SchemaPath Path} of RPC * @param input * - {@link NormalizedNode Binding Independent input} of RPC * @param mountPoint * - for configure mount point (can be null if doesn't exist) * @return completion stage with successful/unsuccessful(error) response */ CompletableFuture<Response<NormalizedNode, Exception>> invokeRpc(SchemaPath path, NormalizedNode input, MountPoint mountPoint); /** * Register notification listener to start listen on notifications. Specific registration parameters can be found in * builder for establish subscription. * * @param notificationListener * - {@link DOMNotificationListener Binding Independent notification listener} * @param mountPoint * - for configure mount point (can be null if doesn't exist) * @param establishBuilder * - builder with specific parameters for establish subscription * @return registration of listener, allows modify or kill registered subscription */ BindingIndependentListenerRegistrationImpl registerNotificationListener(DOMNotificationListener notificationListener, MountPoint mountPoint, BindingSubscriptionEstablish establishBuilder); }
Ot the top of base API, following well-known OpenDaylight services are available on client
site:org.opendaylight.controller.md.sal.dom.api.DOMDataBroker
org.opendaylight.controller.md.sal.dom.api.DOMRpcService
org.opendaylight.controller.md.sal.dom.api.DOMNotificationService
org.opendaylight.controller.md.sal.dom.api.DOMMountPoint
- Binding aware client. This client is based on the binding independent client and uses YANG models to provide Java bindings for YANG on the client’s side. The APIs like GET, POST, PUT and DELETE, call RPC and subscribe to notifications are available as well.
package io.lighty.clients.rest.api; import io.lighty.clients.rest.binding.common.device.MountPoint; import io.lighty.clients.rest.binding.common.notifications.BindingSubscriptionEstablish; import io.lighty.clients.rest.common.query.parameters.QueryParameters; import io.lighty.clients.rest.common.response.Response; import io.lighty.clients.rest.impl.BindingAwareListenerRegistrationImpl; import java.util.List; import java.util.concurrent.CompletableFuture; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.binding.NotificationListener; import org.opendaylight.yangtools.yang.model.api.SchemaPath; /** * Allows to configure devices with Binding Aware objects. */ public interface BindingAwareRestClient extends AutoCloseable { /** * Read data - GET HTTP operation - allows to read data from device. * * @param path * - {@link InstanceIdentifier Binding Aware path} to data * @param mountPoint * - for configure mount point (can be null if doesn't exist) * @param parameters * - optional parameters of GET operation (content, depth, fields) * @return completion stage with successful(data)/unsuccessful(error) response */ <D extends DataObject> CompletableFuture<Response<D, Exception>> get(InstanceIdentifier<D> path, MountPoint mountPoint, QueryParameters parameters); /** * Create data - POST HTTP operation - allows to create data on device. * * @param dataIdentifier * - identifier of data * @param data * - {@link DataObject Binding Aware data} * @param mountPoint * - for configure mount point (can be null if doesn't exist) * @param parameters * - optional parameters of POST operation (insert, point) * @return completion stage with successful/unsuccessful(error) response */ <D extends DataObject> CompletableFuture<Response<Void, Exception>> post(InstanceIdentifier<D> dataIdentifier, D data, MountPoint mountPoint, QueryParameters parameters); /** * Update data - PUT HTTP operation - allows to update data on device. * * @param path * - {@link InstanceIdentifier Binding Aware path} to data * @param data * - {@link DataObject Binding Aware data} * @param mountPoint * - for configure mount point (can be null if doesn't exist) * @param parameters * - optional parameters of PUT operation (insert, point) * @return completion stage with successful/unsuccessful(error) response */ <D extends DataObject> CompletableFuture<Response<Void, Exception>> put(InstanceIdentifier<D> path, D data, MountPoint mountPoint, QueryParameters parameters); /** * Delete data - DELETE HTTP operation - allows to delete data on device. * * @param path * - {@link InstanceIdentifier Binding Aware path} to data * @param mountPoint * - for configure mount point (can be null if doesn't exist) * @return completion stage with successful/unsuccessful(error) response */ <D extends DataObject> CompletableFuture<Response<Void, Exception>> delete(InstanceIdentifier<D> path, MountPoint mountPoint); /** * @param <D> * - type of RPC input * @param <O> * - type of RPC output * @param path * - {@link SchemaPath Path} of RPC * @param input * - {@link DataObject Binding Aware input} of RPC * @param outputType * - RPC output * @param mountPoint * - for configure mount point (can be null if doesn't exist) * @return completion stage with successful(data)/unsuccessful(error) response */ <D extends DataObject, O extends DataObject> CompletableFuture<Response<O, Exception>> invokeRpc(SchemaPath path, D input, Class<O> outputType, MountPoint mountPoint); /** * Invoke RPC - allows to invoke RPC on device without output. * * @param <D> * - type of RPC input * @param path * - {@link SchemaPath Path} of RPC * @param input * - {@link DataObject Binding Aware input} of RPC * @param mountPoint * - for configure mount point (can be null if doesn't exist) * @return completion stage with successful/unsuccessful(error) response */ <D extends DataObject, O extends DataObject> CompletableFuture<Response<O, Exception>> invokeRpc(SchemaPath path, D input, MountPoint mountPoint); /** * Register notification listener to start listen on notifications. Specific registration parameters can be found in * builder for establish subscription. * * @param <N> * - notification type * * @param notificationListener * - {@link NotificationListener Binding Aware notification listener} * @param mountPoint * - for configure mount point (can be null if doesn't exist) * @param establishInputs * - list of inputs with specific parameters for establish subscriptions * @return registration of listener, allows modify or kill registered subscription */ <N extends NotificationListener> BindingAwareListenerRegistrationImpl<N> registerNotificationListener( N notificationListener, MountPoint mountPoint, List<BindingSubscriptionEstablish> establishInputs); }
Under the hood, each lighty.io client library is using an HTTP2 communication stack for better efficiency. It is also important that each client library supports YANG notifications RFC5277, so full-duplex reactive integrations with lighty.io SDN controllers are really easy.
Python & GO Clients
For polyglot environments, lighty.io offers client libraries for Python and Go. Python client provides convenient APIs for common operations like GET, POST, PUT, and DELETE.
On top of that, there are APIs to call RPCs and subscribe to notifications.
class Restconf(object): """Restconf client""" def options(self, path, keys=None, mount=None, query=None, headers=None): """OPTIONS REST method :param path: restconf request path or requested item :type path: str or pyangbind/ydk entity or lighty_restclient.path.Path :param keys: keys to be added to path {'list name': ['key1 value', ...]} :type keys: `dict` or None :param mount: mounted device to apply path to :type mount: lighty_restclient.mount.Mount or None :param query: query parameters :type query: `dict` or None :param headers: headers override :type headers: `dict` or None :returns: request response :rtype: RestconfResponse """ def head(self, path, keys=None, mount=None, query=None, headers=None): """HEAD REST method :param path: restconf request path or requested item :type path: str or pyangbind/ydk entity or lighty_restclient.path.Path :param keys: keys to be added to path {'list name': ['key1 value', ...]} :type keys: `dict` or None :param mount: mounted device to apply path to :type mount: lighty_restclient.mount.Mount or None :param query: query parameters :type query: `dict` or None :param headers: headers override :type headers: `dict` or None :returns: request response :rtype: RestconfResponse """ def get(self, path, keys=None, mount=None, query=None, headers=None): """GET REST method :param path: restconf request path or requested item :type path: str or pyangbind/ydk entity or lighty_restclient.path.Path :param keys: keys to be added to path {'list name': ['key1 value', ...]} :type keys: `dict` or None :param mount: mounted device to apply path to :type mount: lighty_restclient.mount.Mount or None :param query: query parameters :type query: `dict` or None :param headers: headers override :type headers: `dict` or None :returns: request response :rtype: RestconfResponse """ def delete(self, path, keys=None, mount=None, query=None, headers=None): """DELETE REST method :param path: restconf request path or requested item :type path: str or pyangbind/ydk entity or lighty_restclient.path.Path :param keys: keys to be added to path {'list name': ['key1 value', ...]} :type keys: `dict` or None :param mount: mounted device to apply path to :type mount: lighty_restclient.mount.Mount or None :param query: query parameters :type query: `dict` or None :param headers: headers override :type headers: `dict` or None :returns: request response :rtype: RestconfResponse """ def put(self, path, data, keys=None, mount=None, query=None, headers=None): """PUT REST method :param path: restconf request path or requested item :type path: str or pyangbind/ydk entity or lighty_restclient.path.Path :param data: payload (YANG data) :type data: `dict` or str or pyangbind/ydk entity :param keys: keys to be added to path {'list name': ['key1 value', ...]} :type keys: `dict` or None :param mount: mounted device to apply path to :type mount: lighty_restclient.mount.Mount or None :param query: query parameters :type query: `dict` or None :param headers: headers override :type headers: `dict` or None :returns: request response :rtype: RestconfResponse """ def post(self, path, data, keys=None, mount=None, query=None, headers=None): """POST REST method :param path: restconf request path or requested item :type path: str or pyangbind/ydk entity or lighty_restclient.path.Path :param data: payload (YANG data) :type data: `dict` or str or pyangbind/ydk entity :param keys: keys to be added to path {'list name': ['key1 value', ...]} :type keys: `dict` or None :param mount: mounted device to apply path to :type mount: lighty_restclient.mount.Mount or None :param query: query parameters :type query: `dict` or None :param headers: headers override :type headers: `dict` or None :returns: request response :rtype: RestconfResponse """ def patch(self, path, data, keys=None, mount=None, query=None, headers=None): """PATCH REST method :param path: restconf request path or requested item :type path: str or pyangbind/ydk entity or lighty_restclient.path.Path :param data: payload (YANG patch) :type data: `dict` or str or pyangbind/ydk entity :param keys: keys to be added to path {'list name': ['key1 value', ...]} :type keys: `dict` or None :param mount: mounted device to apply path to :type mount: lighty_restclient.mount.Mount or None :param query: query parameters :type query: `dict` or None :param headers: headers override :type headers: `dict` or None :returns: request response :rtype: RestconfResponse """ def subscribe(self, notification, input=None): """Establish notification subscription :param notification: notification name :type notification: str :param input: add or override keys to subscription request :type input: `dict` or None :returns: created notification stream :rtype: NotificationStream """
Here is a very simple python client library code example:
from ltrestconf.client import Restconf from ltrestconf.exceptions import RestconfServerError, RestconfRequestError client = Restconf(scheme='https', host='localhost', port=8181, insecure=True) try: client.put('data/toaster/darknessFactor', {'darknessFactor': 1000}) resp = client.get('data/toaster/darknessFactor') print(resp.dict()['darknessFactor']) except RestconfRequestError as exc: # failed to send request print(exc) except RestconfServerError: # request failed print(exc)
As you can imagine, integration with lighty.io SDN controller is pretty easy, thanks to Java, Python, and Go libraries.
Juraj Veverka