diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 769146ee..9820becf 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -9,7 +9,7 @@ on: pull_request: env: - MKDOCS_MATERIAL_VER: 9.2.6-insiders-4.40.2-hellt + MKDOCS_MATERIAL_VER: 9.4.14-insiders-4.46.0-hellt jobs: docs-test: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index fc23601b..61590ef1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: env: - MKDOCS_MATERIAL_VER: 9.1.16-insiders-4.36.0-hellt-4 # insiders version + MKDOCS_MATERIAL_VER: 9.4.14-insiders-4.46.0-hellt # insiders version jobs: publish-docs: diff --git a/Makefile b/Makefile index cfde4f76..a7eaf9f0 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ -MKDOCS_VER = 9.0.13 +MKDOCS_VER = 9.4.14 # insiders version/tag https://github.com/srl-labs/mkdocs-material-insiders/pkgs/container/mkdocs-material-insiders -MKDOCS_INS_VER = 9.2.6-insiders-4.40.2-hellt +MKDOCS_INS_VER = 9.4.14-insiders-4.46.0-hellt .PHONY: docs docs: - docker run --rm -v $$(pwd):/docs --entrypoint mkdocs squidfunk/mkdocs-material:$(MKDOCS_VER) build --clean --strict + docker run --rm -v $$(pwd):/docs --entrypoint mkdocs ghcr.io/srl-labs/mkdocs-material-insiders:$(MKDOCS_INS_VER) build --clean --strict # serve the site locally using mkdocs-material public container .PHONY: serve diff --git a/docs/blog/author/rdodin.md b/docs/blog/author/rdodin.md new file mode 100644 index 00000000..cc04deb8 --- /dev/null +++ b/docs/blog/author/rdodin.md @@ -0,0 +1,20 @@ +# Roman Dodin + +
+
+![Roman Dodin](https://gitlab.com/rdodin/pics/-/wikis/uploads/3d2bc0f6014eddb95f24016720ac63af/roman_dodin_avatar_pic.jpeg){.img-shadow width=50% style='border-radius: 20%;'} + +[:material-twitter:{ .twemoji-md }][rd-twitter] [:material-linkedin:{ .twemoji-md }][rd-linkedin] + +[rd-linkedin]: https://linkedin.com/in/rdodin +[rd-twitter]: https://twitter.com/ntdvps +
+
+Hey there! I'm Roman Dodin, a member of the SR Linux Product Management team at Nokia. Although I am much more often called a "[containerlab](https://containerlab.dev) guy" as I am one of the maintainers of the project. + +Network automation, virtual networking labs and beautifully looking docs with reproducible examples are my passion. I am also a big fan of open source initiatives and community building. +
+ +
+ +
diff --git a/docs/blog/posts/2023/intent-based-ansible.md b/docs/blog/posts/2023/intent-based-ansible.md index 73003d5b..dbbc11a3 100644 --- a/docs/blog/posts/2023/intent-based-ansible.md +++ b/docs/blog/posts/2023/intent-based-ansible.md @@ -25,11 +25,10 @@ While sounding simple, this approach can become a maintenance nightmare as the n This is when the second approach comes into play. It requires a deeper understanding of Ansible concepts, but it is more scalable and maintainable in the long run. The idea is to abstract the configuration tasks into reusable Ansible roles and use variables to pass the configuration parameters to the roles. This way, the playbook becomes a list of roles that are applied to the devices in the inventory. -When roles are designed in a way that make services provisioned on all the devices in the inventory, the playbook becomes an intent-based service provisioning tool[^1]. To provide a practical example of using Ansible to manage the configuration of an SR Linux fabric with the **intent-based approach** leveraging the official [Ansible collection for SR Linux][collection-doc-link] we created a comprehensive tutorial that covers A to Z the steps required to start managing a fabric in that way - **[:material-book: Intent-based management with Ansible][tutorial]** tutorial. +When roles are designed in a way that make services provisioned on all the devices in the inventory, the playbook becomes an intent-based service provisioning tool. To provide a practical example of using Ansible to manage the configuration of an SR Linux fabric with the **intent-based approach** leveraging the official [Ansible collection for SR Linux][collection-doc-link] we created a comprehensive tutorial that covers A to Z the steps required to start managing a fabric in that way - **[:material-book: Intent-based management with Ansible][tutorial]** tutorial. We are eager to hear your thoughts on that approach and the tutorial itself. Please drop a comment below or open an issue in the [GitHub repository][intent-based-ansible-lab] if you have any questions or remarks. -[^1]: Granted the roles maintain inter-dependencies between infrastructure configuration resources, e.g. a network instance and its associated interfaces, routes, etc. and the services themselves. [collection-doc-link]: ../../../ansible/collection/index.md [intent-based-ansible-lab]: https://github.com/srl-labs/intent-based-ansible-lab [tutorial]: ../../../tutorials/programmability/ansible/intent-based-management/index.md diff --git a/docs/ndk/.meta.yml b/docs/ndk/.meta.yml new file mode 100644 index 00000000..f9defc8a --- /dev/null +++ b/docs/ndk/.meta.yml @@ -0,0 +1 @@ +comments: true diff --git a/docs/ndk/guide/agent-install-and-ops.md b/docs/ndk/guide/agent-install-and-ops.md index 965055ba..371bf890 100644 --- a/docs/ndk/guide/agent-install-and-ops.md +++ b/docs/ndk/guide/agent-install-and-ops.md @@ -1,4 +1,4 @@ -## Installing the agent +# Installing the agent The onboarding of an NDK agent onto the SR Linux system is simply a task of copying [the agent and its files](agent.md) over to the SR Linux filesystem and placing them in the relevant directories. @@ -20,15 +20,18 @@ The agent installation procedure can be carried out in different ways: The first two options are easy to execute, but they are a bit more involved as the installers need to maintain the remote paths for the copy commands. When using the `rpm` option, though, it becomes less cumbersome to install the package. All the installers deal with is a single `.rpm` file and a copy command. Of course, the build process of the `rpm` package is still required, and we would like to explain this process in detail. -### RPM package +## RPM package + One of the easiest ways to create an rpm, deb, or apk package is to use the [nFPM][nFPM] tool - a simple, 0-dependencies packager. The only thing that nFPM requires of a user is to create a configuration file with the general instructions on how to build a package, and the rest will be taken care of. -#### nFPM installation +### nFPM installation + nFPM offers many [installation options](https://nfpm.goreleaser.com/install/) for all kinds of operating systems and environments. In the course of this guide, we will use the universal [nFPM docker image](https://nfpm.goreleaser.com/install/#running-with-docker). -#### nFPM configuration file +### nFPM configuration file + nFPM configuration file is the way of letting nFPM know how to build a package for the software artifacts that users created. The complete list of options the `nfpm.yml` file can have is documented on the [project's site](https://nfpm.goreleaser.com/configuration/). Here we will have a look at the configuration file that is suitable for a typical NDK application written in Go. @@ -54,7 +57,8 @@ contents: # contents to add to the package dst: /etc/opt/srlinux/appmgr/ # destination path of agent yml ``` -#### Running nFPM +### Running nFPM + When nFPM configuration and NDK agent files are present, proceed with building an `rpm` package. Consider the following file layout: @@ -81,7 +85,8 @@ docker run --rm -v $PWD:/tmp -w /tmp goreleaser/nfpm package \ This command will create `ndkDemo-1.0.0.x86_64.rpm` file in the current directory that can be copied over to the SR Linux system for installation. -#### Installing RPM +### Installing RPM + Delivering the available rpm package to a fleet of SR Linux boxes can be done with any configuration management tools. For demo purposes, we will utilize the `scp` utility: ```bash @@ -149,10 +154,11 @@ All the agent components are available by the paths specified in the nFPM config Congratulations, the agent has been installed successfully. -### Loading the agent +## Loading the agent + SR Linux's Application Manager is in charge of managing the applications lifecycle. App Manager controls both the native apps and customer-written agents. -After a user installs the agent on the SR Linux system by copying the relevant files, they need to reload the `app_mgr` process to detect new applications. App Manager gets to know about the available apps by reading the [app configuration files](agent.md#configuration-file) located at the following paths: +After a user installs the agent on the SR Linux system by copying the relevant files, they need to reload the `app_mgr` process to detect new applications. App Manager gets to know about the available apps by reading the [app configuration files](agent.md#application-manager-and-application-configuration-file) located at the following paths: | Directory | Description | | -------------------------- | ------------------------------ | @@ -172,6 +178,7 @@ Once reloaded, App Manager will detect the new applications and load them accord ``` ## Managing the agent's lifecycle + An application's lifecycle can be managed via any management interface by using the following knobs from the `tools` schema. ``` @@ -188,4 +195,4 @@ The commands that can be given to an application are translated to system signal | `quit` | Send `SIGQUIT` signal to the app. Default behavior is to terminate the process and dump core info | | `kill` | Send `SIGKILL` signal to the app. Kills the process without any cleanup | -[nFPM]: https://nfpm.goreleaser.com/ \ No newline at end of file +[nFPM]: https://nfpm.goreleaser.com/ diff --git a/docs/ndk/guide/agent.md b/docs/ndk/guide/agent.md index 278be2ef..d5df5e01 100644 --- a/docs/ndk/guide/agent.md +++ b/docs/ndk/guide/agent.md @@ -1,56 +1,80 @@ -As was explained in the [NDK Architecture](architecture.md) section, an agent[^1] is a custom software that can extend SR Linux capabilities by running alongside SR Linux native applications and performing some user-defined tasks. +--- +comments: true +--- + +# Agent Structure + +As was explained in the [NDK Architecture](architecture.md) section, an NDK agent[^10] is a custom software that can extend SR Linux capabilities by running alongside SR Linux's native applications and perform some user-defined work. To deeply integrate with the rest of the SR Linux architecture, the agents have to be defined like an application that SR Linux's application manager can take control of. The structure of the agents is the main topic of this chapter. +
+
The main three components of an agent: -1. Agent's executable file -2. YANG module -3. Agent configuration file - -## Executable file +1. An executable file +2. A YANG module +3. Application configuration file -An executable file is called when the agent starts running on SR Linux system. It contains the application logic and is typically an executable binary or a script. +SR Linux application manager (which is like `systemd`) onboards the NDK application by reading its configuration file and YANG models and then starts the agent's executable file. We will cover the role of each of these components in the subsequent sections of this chapter. -The application logic handles the agents' configuration that may be provided via any management interface (CLI, gNMI, etc.) and contains the core logic of interfacing with gRPC based NDK services. +
-In the subsequent sections of the Developers Guide, we will cover how to write the logic of an agent and interact with various NDK services. +```mermaid +flowchart TD + APPMGR[Application Manager] --> |Load App YAML| APP[NDK Application] + APPMGR[Application Manager] --> |Load App YANG| APP[NDK Application] + APP[NDK Application] --> EXEC(Started Executable) +``` -An executable file can be placed at `/usr/local/bin` directory. +
-## YANG module +## Application manager and Application configuration file -SR Linux is a [fully modeled](../../yang/index.md) Network OS - any native or custom application that can be configured or can have state is required to have a proper YANG model. +Recall the decoupled nature of SR Linux's architecture where each application is a separate process. Application manager is the service that is responsible for starting, stopping, and restarting applications, as well as for monitoring their health. -The "cost" associated with requiring users to write YANG models for their apps pays off immensely as this +Both native SR Linux applications (AAA, LLDP, BGP, etc.) and NDK agents are managed by the application manager. Applications that are managed by the application manager should have a configuration file[^30] that describes the application and its lifecycle. For native SR Linux applications the app config files are located by the `/opt/srlinux/appmgr` path, and for NDK agents, the files are located by the `/etc/opt/srlinux/appmgr` path. -* enables seamless integration of an agent with **all** management interfaces: CLI, gNMI, JSON-RPC. - Any agent's configuration knobs that users expressed in YANG will be immediately available in the SR Linux CLI as if it was part of it from the beginning. Yes, with auto-suggestion of the fields as well. -* provides out-of-the-box Streaming Telemetry (gNMI) support for any config or state data that the agent maintains +With an agent's config file, users define properties of an application, for example: -And secondly, the YANG modules for custom apps are not that hard to write as their data model is typically relatively small. +* application version +* location of the executable file +* YANG modules related to this app +* lifecycle management policy -///note -The YANG module is only needed if a developer wants their agent to be configurable via any management interfaces or keep state. -/// +NDK agents must have their config file present by the `/etc/opt/srlinux/appmgr` directory. It is a good idea to name the agent's config file after the agent's name; if we have the agent called `greeter`, then its config file can be named `greeter.yml` and stored by the `/etc/opt/srlinux/appmgr/greeter.yml` path. -YANG files related to an agent are typically located by the `/opt/$agentName/yang` path. +Let's have a look at configuration file for a simple `greeter` NDK written in Go: -## Configuration file +```yaml +greeter: + path: /usr/local/bin #(1)! + launch-command: greeter #(2)! + version-command: greeter --version #(3)! + failure-action: wait=10 #(4)! + config-delivery-format: json #(5)! + yang-modules: + names: + - "greeter" #(6)! + source-directories: + - "/opt/greeter/yang" #(7)! +``` -Due to SR Linux modular architecture, each application, be it an internal app like `bgp` or a custom NDK agent, needs to have a configuration file. This file contains application parameters read by the Application Manager service to onboard the application onto the system. +1. The path to use when searching for the executable file. +2. The binary app manager will launch. Relative to the `path`. +3. The command to run to get the version of the application. The version can be seen in the output of `show / system application `. +4. An action to carry out when the application fails (non zero exit code). The action can be one of the following: + * `reboot` - reboot the system + * `wait=` - wait for `` and then restart the application +5. The encoding format of the application's configuration when it is delivered to the application by the Notification Service. -With an agent's config file, users define properties of an application, for example: + For NDK agents the recommended format is `json`. -* application version -* location of the executable file -* YANG modules related to this app -* lifecycle management policy -* and others +6. The names of the YANG modules to load. This is usually the file-name without `.yang` extension. -Custom agents must have their config file present by the `/etc/opt/srlinux/appmgr` directory. It is a good idea to name the agent's config file after the agent's name; if we have the agent called `myCoolAgent`, then its config file can be named `myCoolAgent.yml` and stored by the `/etc/opt/srlinux/appmgr` path. + The YANG modules are searched for in the directories specified by the `source-directories` property. -Through the subsequent chapters of the Developers Guide, we will cover the most important options, but here is a complete list of config file parameters: +7. The source directories where to search for the YANG modules. ///details | Complete list of config files parameters @@ -140,10 +164,54 @@ other-application-name: /// -## Dependency and other files +When Application Manager is started/reloaded, it watches for application configuration files in the `/opt/srlinux/appmgr` and `/etc/opt/srlinux/appmgr` directories and starts managing the applications. + +Let us now have a closer look at the main components of an agent and understand their role. + +## YANG module + +SR Linux is a [fully modeled](../../yang/index.md) Network OS - any native or custom application that can be configured or can have state is required to have a proper YANG model describing the data. + +The "cost" associated with requiring application developers to write YANG models for their apps pays off immensely as this + +* enables seamless application integration with **all** management interfaces: CLI, gNMI, JSON-RPC, etc. + Any agent's configuration knobs that users expressed in YANG will be immediately available in the SR Linux CLI as if it was part of it from the beginning. Yes, with auto-suggestion of the fields as well. +* provides out-of-the-box Streaming Telemetry (gNMI) support for any config or state data that the application maintains + +And secondly, the YANG modules for custom apps are not that hard to write as their data model is typically relatively small. + +///note +The YANG module is only needed if a developer wants their agent to be configurable via any management interfaces or keep state. +/// + +YANG files related to an agent are typically located by the `/opt/$agentName/yang` directory. + +Consider the following YANG module for a simple `greeter` agent: + +```{.yang title="yang/greeter.yang"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/yang/greeter.yang" +``` + +The YANG module defines a container called `greeter` with two leaf nodes: `name` and `greeting`. The `name` leaf is a configuration node, and the `greeting` leaf is a state node. + +With this YANG module our application will populate the configuration datastore of SR Linux with `name` leaf and state datastore with `name` and `greeting` leaves. + +## Executable file + +Executable file is the main component of an agent as it contains the application logic. Depending on the language you choose to write your agent in, the executable file can be a binary or a script[^20]. + +The application logic handles the configuration that may be provided by users via any management interface (CLI, gNMI, etc.) and contains the core business logic of the application that may be triggered by some events from SR Linux service. + +In the subsequent sections of the Developers Guide, we will dive into the details of how to write an sample NDK application in [Go](dev/go/index.md) and [Python](dev/py/index.md). + +An executable file can be placed at `/usr/local/bin` directory. + +## Auxiliary files Quite often, an agent may require additional files for its operation. It can be a virtual environment for your Python agent or some JSON file that your agent consumes. All those auxiliary files can be saved by the `/opt/$agentName/` directory. -[^1]: terms NDK agent and NDK app are used interchangeably +[^10]: terms agent and application are used interchangeably. +[^20]: Binaries are typically relevant for compiled languages like C, Go, Rust, etc. Scripts are typically relevant for interpreted languages like Python. +[^30]: Expressed in YAML format. diff --git a/docs/ndk/guide/architecture.md b/docs/ndk/guide/architecture.md index c860fa9b..2ed4c1b9 100644 --- a/docs/ndk/guide/architecture.md +++ b/docs/ndk/guide/architecture.md @@ -1,13 +1,16 @@ - +--- +comments: true +--- + -SR Linux provides a Software Development Kit (SDK) to assist operators with developing agents that run alongside SR Linux applications. This SDK is named NetOps Development Kit, or NDK for short. +SR Linux provides a Software Development Kit (SDK) to assist operators with developing agents that run alongside SR Linux applications. This SDK is named **NetOps Development Kit**, or **NDK** for short. -NDK allows operators to write applications (a.k.a agents) that deeply integrate with other native SR Linux applications. The deep integration is the courtesy of the NDK gRPC service that enables custom applications to interact with other SR Linux applications via Impart Database (IDB). +NDK allows operators to write applications (a.k.a agents) that deeply integrate[^10] with other native SR Linux applications. The deep integration is the courtesy of the NDK gRPC service that enables custom applications to interact with other SR Linux applications via Impart Database (IDB). -In Fig. 1, custom NDK applications `app-1` and `app-2` interact with other SR Linux subsystems via gRPC-based NDK service. +Fig. 1 shows how custom NDK applications `app-1` and `app-2` interact with other SR Linux subsystems via gRPC-based NDK service that offers access to IDB. SR Linux native apps, like `bgp`, `lldp`, and others also interface with IDB to read and write configuration and state data.
-
+
Fig 1. NDK applications integration
@@ -15,7 +18,7 @@ In addition to the traditional tasks of reading and writing configuration, NDK-b ## gRPC & Protocol buffers -NDK uses gRPC - a high-performance, open-source framework for remote procedure calls. +NDK leverages [gRPC](https://grpc.io) - a high-performance, open-source framework for remote procedure calls - to enable communication between custom applications and SR Linux system. gRPC framework by default uses [Protocol buffers](https://developers.google.com/protocol-buffers) as its Interface Definition Language as well as the underlying message exchange format. @@ -27,26 +30,26 @@ In gRPC, a client application can directly call a method on a server application On the server side, the server implements this interface and runs a gRPC server to handle client calls. On the client side, the client provides the same methods as the server.
-
+
Fig 2. gRPC client-server interactions
Leveraging gRPC and protobufs provides some substantial benefits for NDK users: -1. Language neutrality: NDK apps can be written in any language for which protobuf compiler exists. Go, Python, C, Java, Ruby, and more languages are supported by Protocol buffers enabling SR Linux users to write apps in the language of their choice. -2. High-performance: protobuf-encoded messaging is an efficient way to exchange data in a client-server environment. Applications that consume high-volume streams of data (for example, route updates) benefit from an efficient and fast message delivery enabled by protobuf. -3. Backwards API compatibility: a protobuf design property of using IDs for data fields makes it possible to evolve API over time without ever breaking backward compatibility. Old clients will still be able to consume data stored in the original fields, whereas new clients will benefit from accessing data stored in the new fields. +1. **Language neutrality**: NDK apps can be written in any language for which protobuf compiler exists. Go, Python, C, Java, Ruby, and more languages are supported by Protocol buffers enabling SR Linux users to write apps in the language of their choice. +2. **High-performance**: protobuf-encoded messaging is an efficient way to exchange data in a client-server environment. Applications that consume high-volume streams of data (for example, route updates) benefit from an efficient and fast message delivery enabled by protobuf. +3. **Backwards API compatibility**: a protobuf design property of using IDs for data fields makes it possible to evolve API over time without ever breaking backward compatibility. Old clients will still be able to consume data stored in the original fields, whereas new clients will benefit from accessing data stored in the new fields. ## NDK Service -NDK provides a collection of [gRPC](https://grpc.io/) services, each of which enables custom applications to interact with a particular subsystem on an SR Linux OS, delivering a high level of integration and extensibility. +NDK is composed of a collection of gRPC services, each of which enables custom applications to interact with a particular subsystem on an SR Linux NOS, delivering a high level of integration and extensibility. -With this architecture, NDK agents act as gRPC clients that execute remote procedure calls (RPC) on a system that implements a gRPC server. +With this architecture, NDK applications act as gRPC clients that execute remote procedure calls (RPC) on a system that runs a gRPC server. -On SR Linux, `ndk_mgr` is the application that runs the NDK gRPC server. Fig 3. shows how custom agents interact via gRPC with NDK, and NDK executes the remote procedure and communicates with other system applications through IDB and pub/sub interface to return the result of the RPC to a client. +On SR Linux, `ndk_mgr` is the application that runs the NDK gRPC server. Fig 3. shows how custom agents interact via gRPC with NDK, and NDK facilitates communication between custom apps and the rest of the system via IDB's pub/sub interface.
-
+
Fig 3. gRPC as an Inter Process Communication (IPC) protocol
@@ -56,127 +59,16 @@ As a result, custom applications are able to communicate with the native SR Linu NDK services, underlying RPCs, and messages are defined in `.proto` files. These files are used to generate language bindings essential for the NDK apps development process and serve as the data modeling language for the NDK itself. -The source `.proto` files for NDK are open and published in [`nokia/srlinux-ndk-protobufs`](https://github.com/nokia/srlinux-ndk-protobufs) repository. Anyone can clone this repository and explore the NDK gRPC services or build language bindings for the programming language of their choice. +The source `.proto` files for NDK are available at [`nokia/srlinux-ndk-protobufs`](https://github.com/nokia/srlinux-ndk-protobufs) GitHub repository. Anyone can clone this repository and explore the NDK gRPC services or build language bindings for the programming language of their choice. -Additionally users can find the NDK proto files on SR Linux filesystem by the `/opt/srlinux/protos/ndk` path[^3]. +Additionally, users can find the NDK proto files on the SR Linux file system by the `/opt/srlinux/protos/ndk` path[^20]. ### Documentation -Although the proto files are human-readable, it is easier to browse the NDK services using the generated documentation that we keep in the same [`nokia/srlinux-ndk-protobufs`](https://github.com/nokia/srlinux-ndk-protobufs) repo. The HTML document is provided in the readme file that appears when a user selects a tag that matches the NDK release version[^1]. +Although the proto files are human-readable, it is easier to browse the NDK services using the generated documentation that we keep in the same [`nokia/srlinux-ndk-protobufs`](https://github.com/nokia/srlinux-ndk-protobufs) repo. The HTML document is linked in the [repository structure](https://github.com/nokia/srlinux-ndk-protobufs#repository-structure) section of the readme[^30]. The generated documentation provides the developers with a human-readable reference of all the services, messages, and types that comprise the NDK service. -### Operations flow - -Regardless of the language in which the agents are written, at a high level, the following flow of operations applies to all agents when interacting with the NDK service: - -
-
-
Fig 4. NDK operations flow
-
- -1. Establish gRPC channel with NDK manager and instantiate an NDK client -2. Register the agent with the NDK manager -3. Register notification streams for different types of NDK services (config, lldp, interface, etc.) -4. Start streaming notifications -5. Handle the streamed notifications -6. Update agent's state data if needed -7. Exit gracefully if required - -To better understand the steps each agent undergoes, we will explain them in a language-neutral manner. For language-specific implementations, read the "Developing with NDK" chapter. - -#### gRPC Channel and NDK Manager Client - -NDK agents communicate with gRPC based NDK service by invoking RPCs and handling responses. An RPC generally takes in a client request message and returns a response message from the server. - -A gRPC channel must be established before communicating with the NDK manager application running on SR Linux[^2]. NDK server runs on port `50053`; agents which are installed on SR Linux OS use `localhost:50053` socket to establish the gRPC channel. - -Once the gRPC channel is set up, a gRPC client (often called _stub_) needs to be created to perform RPCs. Each gRPC service needs to have its own client. In NDK, the [`SdkMgrService`][sdk_mgr_svc_doc] service is the first service that agents interact with, therefore, users first need to create the NDK Manager Client (Mgr Client on diagram) that will be able to call RPCs defined for [`SdkMgrService`][sdk_mgr_svc_doc]. - -#### Agent registration - -Agent must be first registered with SRLinux NDK by calling [`AgentRegister`](https://github.com/nokia/srlinux-ndk-protobufs/blob/protos/ndk/sdk_service.proto#L32) RPC of [`SdkMgrService`][sdk_mgr_svc_doc]. Initial agent state is created during the registration process. - -An `AgentRegistrationResponse` is returned (omitted in Fig. 4) with the status of the registration process. - -#### Registering notifications - -Agents interact with other services like Network Instance, Config, LLDP, BFD by subscribing to notification updates from these services. - -Before subscribing to a notification stream of a certain service the subscription stream needs to be created. To create it, a client of [`SdkMgrService`][sdk_mgr_svc_doc] calls [`NotificationRegister`][sdk_mgr_svc_doc] RPC with [`NotificationRegistrationRequest`][notif_reg_req_doc] field `Op` set to `Create` and other fields absent. - -!!!info - `NotificationRegistrationRequest` message's field `Op` (for Operation) may have one of the following values: - - - `Create` creates a subscription stream and returns a `StreamId` that is used when adding subscriptions with the `AddSubscription` operation. - - `Delete` deletes the existing subscription stream that has a particular `SubId`. - - `AddSubscription` adds a subscription. The stream will now be able to stream notifications of that subscription type (e.g., Intf, NwInst, etc). - - `DeleteSubscription` deletes the previously added subscription. - -When `Op` field is set to `Create`, NDK Manager responds with [`NotificationRegisterResponse`][notif_reg_resp_doc] message with `stream_id` field set to some value. The stream has been created, and the subscriptions can be added to the created stream. - -To subscribe to a certain service notification updates another call of [`NotificationRegister`][sdk_mgr_svc_doc] RPC is made with the following fields set: - -* `stream_id` set to an obtained value from the `NotificationRegisterResponse` -* `Op` is set to `AddSubscription` -* one of the [`subscription_types`](https://github.com/nokia/srlinux-ndk-protobufs/blob/protos/ndk/sdk_service.proto#L125) is set according to the desired service notifications. For example, if notifications from the [`Config`][cfg_svc_doc] service are of interest, then `config` field of type [`ConfigSubscriptionRequest`][cfg_sub_req_doc] is set. - -[`NotificationRegisterResponse`][notif_reg_resp_doc] message follows the request and contains the same `stream_id` but now also the `sub_id` field - subscription identifier. At this point agent successfully indicated its desire to receive notifications from certain services, but the notification streams haven't been started yet. - -#### Streaming notifications - -Requesting applications to send notifications is done by interfacing with [`SdkNotificationService`][sdk_notif_svc_doc]. As this is another gRPC service, it requires its own client - Notification client. - -To initiate streaming of updates based on the agent subscriptions the Notification Client executes [`NotificationStream`][sdk_notif_svc_doc] RPC which has [`NotificationStreamRequest`][notif_stream_req_doc] message with `stream_id` field set to the ID of a stream to be used. This RPC returns a stream of [`NotificationStreamResponse`][notif_stream_resp_doc], which makes this RPC of type "server streaming RPC". - -???info "Server-streaming RPC" - A server-streaming RPC is similar to a unary RPC, except that the server returns a stream of messages in response to a client's request. After sending all its messages, the server's status details (status code and optional status message) and optional trailing metadata are sent to the client. This completes processing on the server side. The client completes once it has all the server's messages. - -`NotificationStreamResponse` message represents a notification stream response that contains one or more notifications. The [`Notification`][notif_doc] message contains one of the [`subscription_types`](https://github.com/nokia/srlinux-ndk-protobufs/blob/57386044bacdb4689eda414bc07bd78e17b170c3/ndk/sdk_service.proto#L204) notifications, which will be set in accordance to what notifications were subscribed by the agent. - -In our example, we sent `ConfigSubscriptionRequest` inside the `NotificationRegisterRequest`, hence the notifications that we will get back for that `stream_id` will contain [`ConfigNotification`][cfg_notif_doc] messages inside `Notification` of a `NotificationStreamResponse`. - -#### Handling notifications - -The agent handles the stream of notifications by analyzing which concrete type of notification was read from the stream. The Server streaming RPC will provide notifications till the last available one; the agent then reads out the incoming notifications and handles the messages contained within them. - -The handling of notifications is done when the last notification is sent by the server. At this point, the agent may perform some work on the received data and, if needed, update the agent's state if it has one. - -#### Updating agent's state data - -Each agent may keep state and configuration data modeled in YANG. When an agent needs to set/update its own state data (for example, when it made some calculations based on received notifications), it needs to use [`SdkMgrTelemetryService`][sdk_mgr_telem_svc_doc] and a corresponding client. - -
-
-
Fig 5. Updating agent's state flow
-
- -The state that an agent intends to have will be available for gNMI telemetry, CLI access, and JSON-RPC retrieval, as it essentially becomes part of the SR Linux state. - -Updating or initializing agent's state with data is done with [`TelemetryAddOrUpdate`][sdk_mgr_telem_svc_doc] RPC that has a request of type [`TelemetryUpdateRequest`][telem_upd_req_doc] that encloses a list of [`TelemetryInfo`][telem_info_doc] messages. Each `TelemetryInfo` message contains a `key` field that points to a subtree of agent's YANG model that needs to be updated with the JSON data contained within `data` field. - -#### Exiting gracefully - -When an agent needs to stop its operation and be removed from the SR Linux system, it needs to be unregistered by invoking `AgentUnRegister` RPC of the `SdkMgrService`. The gRPC connection to the NDK server needs to be closed. - -When unregistered, the agent's state data will be removed from SR Linux system and will no longer be accessible to any of the management interfaces. - -[sdk_mgr_svc_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.1.0/doc/index.html#ndk.SdkMgrService -[sdk_mgr_svc_proto]: https://github.com/nokia/srlinux-ndk-protobufs/blob/protos/ndk/sdk_service.proto -[sdk_notif_svc_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.1.0/doc/index.html#ndk.SdkNotificationService -[sdk_mgr_telemetry_proto]: https://github.com/nokia/srlinux-ndk-protobufs/blob/protos/ndk/telemetry_service.proto -[notif_reg_req_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.1.0/doc/index.html#ndk.NotificationRegisterRequest -[notif_reg_resp_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.1.0/doc/index.html#ndk.NotificationRegisterResponse -[cfg_svc_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.1.0/doc/index.html#ndk%2fconfig_service.proto -[cfg_notif_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.1.0/doc/index.html#ndk.ConfigNotification -[cfg_sub_req_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.1.0/doc/index.html#ndk.ConfigSubscriptionRequest -[notif_stream_req_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.1.0/doc/index.html#ndk.NotificationStreamRequest -[notif_stream_resp_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.1.0/doc/index.html#ndk.NotificationStreamResponse -[notif_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.1.0/doc/index.html#ndk.Notification -[sdk_mgr_telem_svc_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.1.0/doc/index.html#ndk.SdkMgrTelemetryService -[telem_upd_req_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.1.0/doc/index.html#ndk.TelemetryUpdateRequest -[telem_info_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.1.0/doc/index.html#ndk.TelemetryInfo - -[^1]: For example, [here](https://github.com/nokia/srlinux-ndk-protobufs/tree/v0.1.0) you will find the auto-generated documentation for the latest NDK version at the moment of this writing. -[^2]: `sdk_mgr` is the name of the application that implements NDK gRPC server and runs on SR Linux OS. -[^3]: starting from 23.7.1 release. +[^10]: See [NDK Introduction](../index.md) for more details on what level do NDK apps integrate with the rest of SR Linux NOS. +[^20]: starting from 23.7.1 release. +[^30]: For example, [here](https://github.com/nokia/srlinux-ndk-protobufs/tree/v0.1.0) you will find the auto-generated documentation for the latest NDK version at the moment of this writing. diff --git a/docs/ndk/guide/dev/go.md b/docs/ndk/guide/dev/go.md deleted file mode 100644 index 611eb7e6..00000000 --- a/docs/ndk/guide/dev/go.md +++ /dev/null @@ -1,235 +0,0 @@ -# Developing agents with NDK in Go - -This guide explains how to consume the NDK service when developers write the agents in a Go[^1] programming language. - -!!!note - This guide provides code snippets for several operations that a typical agent needs to perform according to the [NDK Service Operations Flow](../architecture.md#operations-flow) chapter. - - Where applicable, the chapters on this page will refer to the NDK Architecture section to provide more context on the operations. - -In addition to the publicly available [protobuf files][ndk_proto_repo], which define the NDK Service, Nokia also provides generated Go bindings for data access classes of NDK in a [`nokia/srlinux-ndk-go`][ndk_go_bindings] repo. - -The [`github.com/nokia/srlinux-ndk-go`][go_package_repo] package provided in that repository enables developers of NDK agents to immediately start writing NDK applications without the need to generate the Go package themselves. - -[ndk_proto_repo]: https://github.com/nokia/srlinux-ndk-protobufs -[ndk_go_bindings]: https://github.com/nokia/srlinux-ndk-go -[go_package_repo]: https://pkg.go.dev/github.com/nokia/srlinux-ndk-go@v0.1.0/ndk - -## Establish gRPC channel with NDK manager and instantiate an NDK client - -[:octicons-question-24: Additional information](../architecture.md#grpc-channel-and-ndk-manager-client) - -To call service methods, a developer first needs to create a gRPC channel to communicate with the NDK manager application running on SR Linux. - -This is done by passing the NDK server address - `localhost:50053` - to `grpc.Dial()` as follows: - -```go -import ( - "google.golang.org/grpc" -) - -conn, err := grpc.Dial("localhost:50053", grpc.WithInsecure()) -if err != nil { - ... -} -defer conn.Close() -``` - -Once the gRPC channel is setup, we need to instantiate a client (often called _stub_) to perform RPCs. The client is obtained using the [`NewSdkMgrServiceClient`][NewSdkMgrServiceClient_godoc] method provided. - -```go -import "github.com/nokia/srlinux-ndk-go/v21/ndk" - -client := ndk.NewSdkMgrServiceClient(conn) -``` - -[NewSdkMgrServiceClient_godoc]: https://pkg.go.dev/github.com/nokia/srlinux-ndk-go@v0.1.0/ndk#NewSdkMgrServiceClient - -## Register the agent with the NDK manager - -[:octicons-question-24: Additional information](../architecture.md#agent-registration) - -Agent must be first registered with SR Linux by calling the `AgentRegister` method available on the returned [`SdkMgrServiceClient`][NewSdkMgrServiceClient_godoc] interface. The initial agent state is created during the registration process. - -### Agent's context - -Go [context](https://pkg.go.dev/context) is a required parameter for each RPC service method. Contexts provide the means of enforcing deadlines and cancellations as well as transmitting metadata within the request. - -During registration, SR Linux will be expecting a key-value pair with the `agent_name` key and a value of the agent's name passed in the context of an RPC. The agent name is defined in the agent's YAML file. - -!!!warning - Not including this metadata in the agent `ctx` would result in an agent registration failure. SR Linux would not be able to differentiate between two agents both connected to the same NDK manager. - -```go -ctx, cancel := context.WithCancel(context.Background()) -defer cancel() -// appending agent's name to the context metadata -ctx = metadata.AppendToOutgoingContext(ctx, "agent_name", "ndkDemo") -``` - -### Agent registration - -[`AgentRegister`][NewSdkMgrServiceClient_godoc] method takes in the context `ctx` that is by now has agent name as its metadata and an [`AgentRegistrationRequest`][AgentRegistrationRequest_godoc]. - -[`AgentRegistrationRequest`][AgentRegistrationRequest_godoc] structure can be passed in with its default values for a basic registration request. - -```go -import "github.com/nokia/srlinux-ndk-go/v21/ndk" - -r, err := client.AgentRegister(ctx, &ndk.AgentRegistrationRequest{}) -if err != nil { - log.Fatalf("agent registration failed: %v", err) -} -``` - -[`AgentRegister`][AgentRegister_godoc] method returns [`AgentRegistrationResponse`][AgentRegistrationResponse_godoc] and an error. Response can be additionally checked for status and error description. - -[AgentRegistrationRequest_godoc]: https://pkg.go.dev/github.com/nokia/srlinux-ndk-go@v0.1.0/ndk#AgentRegistrationRequest -[AgentRegister_godoc]: https://pkg.go.dev/github.com/nokia/srlinux-ndk-go@v0.1.0/ndk#UnimplementedSdkMgrServiceServer.AgentRegister -[AgentRegistrationResponse_godoc]: https://pkg.go.dev/github.com/nokia/srlinux-ndk-go@v0.1.0/ndk#AgentRegistrationResponse - -## Register notification streams - -[:octicons-question-24: Additional information](../architecture.md#registering-notifications) - -### Create subscription stream - -A subscription stream needs to be created first before any of the subscription types can be added. -[`SdkMgrServiceClient`][NewSdkMgrServiceClient_godoc] first creates the subscription stream by executing [`NotificationRegister`][NewSdkMgrServiceClient_godoc] method with a [`NotificationRegisterRequest`][NotificationRegisterRequest_godoc] only field `Op` set to a value of `const NotificationRegisterRequest_Create`. This effectively creates a stream which is identified with a `StreamID` returned inside the [`NotificationRegisterResponse`][NotificationRegisterResponse_godoc]. - -`StreamId` must be associated when subscribing/unsubscribing to certain types of router notifications. - -```go -req := &ndk.NotificationRegisterRequest{ - Op: ndk.NotificationRegisterRequest_Create, -} - -resp, err := client.NotificationRegister(ctx, req) -if err != nil { - log.Fatalf("Notification Register failed with error: %v", err) -} else if resp.GetStatus() == ndk.SdkMgrStatus_kSdkMgrFailed { - r.log.Fatalf("Notification Register failed with status %d", resp.GetStatus()) -} - -log.Debugf("Notification Register was successful: StreamID: %d SubscriptionID: %d", resp.GetStreamId(), resp.GetSubId()) -``` - -[NotificationRegisterRequest_godoc]: https://pkg.go.dev/github.com/nokia/srlinux-ndk-go@v0.1.0/ndk#NotificationRegisterRequest -[NotificationRegisterResponse_godoc]: https://pkg.go.dev/github.com/nokia/srlinux-ndk-go@v0.1.0/ndk#NotificationRegisterResponse - -### Add notification subscriptions - -Once the `StreamId` is acquired, a client can register notifications of a particular type to be delivered over that stream. - -Different types of notifications types can be subscribed to by calling the same [`NotificationRegister`][NewSdkMgrServiceClient_godoc] method with a [`NotificationRegisterRequest`][NotificationRegisterRequest_godoc] having `Op` field set to `NotificationRegisterRequest_AddSubscription` and certain `SubscriptionType` selected. - -In the example below we would like to receive notifications from the [`Config`][config_service_docs] service, hence we specify `NotificationRegisterRequest_Config` subscription type. - -```go -subType := &ndk.NotificationRegisterRequest_Config{ // This is unique to each notification type (Config, Intf, etc.). - Config: &ndk.ConfigSubscriptionRequest{}, -} -req := &ndk.NotificationRegisterRequest{ - StreamId: resp.GetStreamId(), // StreamId is retrieved from the NotificationRegisterResponse - Op: ndk.NotificationRegisterRequest_AddSubscription, - SubscriptionTypes: subType, -} -resp, err := r.mgrStub.NotificationRegister(r.ctx, req) -if err != nil { - log.Fatalf("Agent could not subscribe for config notification") -} else if resp.GetStatus() == ndk.SdkMgrStatus_kSdkMgrFailed { - log.Fatalf("Agent could not subscribe for config notification with status %d", resp.GetStatus()) -} -log.Infof("Agent was able to subscribe for config notification with status %d", resp.GetStatus()) -``` - -[config_service_docs]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.1.0/doc/index.html#ndk%2fconfig_service.proto - -## Streaming notifications - -[:octicons-question-24: Additional information](../architecture.md#streaming-notifications) - -Actual streaming of notifications is a task for another service - [`SdkNotificationService`][SdkNotificationService_doc]. This service requires developers to create its own client, which is done with [`NewSdkNotificationServiceClient`][NewSdkNotificationServiceClient_godoc] function. - -The returned [`SdkNotificationServiceClient`][SdkNotificationServiceClient_godoc] interface has a single method `NotificationStream` that is used to start streaming notifications. - -`NotificationsStream` is a **server-side streaming RPC** which means that SR Linux (server) will send back multiple event notification responses after getting the agent's (client) request. - -To tell the server to start streaming notifications that were subscribed to before the [`NewSdkNotificationServiceClient`][NewSdkNotificationServiceClient_godoc] executes `NotificationsStream` method where [`NotificationStreamRequest`][NotificationStreamRequest_godoc] struct has its `StreamId` field set to the value that was obtained at subscription stage. - -```go -req := &ndk.NotificationStreamRequest{ - StreamId: resp.GetStreamId(), -} -streamResp, err := notifClient.NotificationStream(ctx, req) -if err != nil { - log.Fatal("Agent failed to create stream client with error: ", err) -} -``` - -[SdkNotificationService_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.1.0/doc/index.html#srlinux.sdk.SdkNotificationService -[NewSdkNotificationServiceClient_godoc]: https://pkg.go.dev/github.com/nokia/srlinux-ndk-go@v0.1.0/ndk#NewSdkNotificationServiceClient -[SdkNotificationServiceClient_godoc]: https://pkg.go.dev/github.com/nokia/srlinux-ndk-go@v0.1.0/ndk#SdkNotificationServiceClient -[NotificationStreamRequest_godoc]: https://pkg.go.dev/github.com/nokia/srlinux-ndk-go@v0.1.0/ndk#NotificationStreamRequest - -## Handle the streamed notifications - -[:octicons-question-24: Additional information](../architecture.md#handling-notifications) - -Handling notifications starts with reading the incoming notification messages and detecting which type this notification is exactly. When the type is known the client reads the fields of a certain notification. Here is the pseudocode that illustrates the flow: - -```go -func HandleNotifications(stream ndk.SdkNotificationService_NotificationStreamClient) { - for { // loop until stream returns io.EoF - notification stream response (nsr) := stream.Recv() - for notif in nsr.Notification { // nsr.Notification is a slice of `Notification` - if notif.GetConfig() is not nil { - 1. config notif = notif.GetConfig() - 2. handle config notif - } else if notif.GetIntf() is not nil { - 1. intf notif = notif.GetIntf() - 2. handle intf notif - } ... // Do this if statement for every notification type the agent is subscribed to - } - } -} -``` - -`NotificationStream` method of the [`SdkNotificationServiceClient`][SdkNotificationServiceClient_godoc] interface will return a stream client [`SdkNotificationService_NotificationStreamClient`][NotificationStreamClient_godoc]. - -`SdkNotificationService_NotificationStreamClient` contains a `Recv()` to retrieve notifications one by one. At the end of a stream `Rev()` will return `io.EOF`. - -`Recv()` returns a [`*NotificationStreamResponse`][NotificationStreamResponse_godoc] which contains a slice of [`Notification`][NotificationStreamResponse_godoc]. - -[`Notification`][NotificationStreamResponse_godoc] struct has `GetXXX()` methods defined which retrieve the notification of a specific type. For example: [`GetConfig`][GetConfig_godoc] returns [`ConfigNotification`][ConfigNotification_godoc]. - -!!!note - `ConfigNotification` is returned **only if** `Notification` struct has a certain subscription type set for its `SubscriptionType` field. Otherwise, `GetConfig` returns `nil`. - -Once the specific `XXXNotification` has been extracted using the `GetXXX()` method, users can access the fields of the notification and process the data contained within the notification using `GetKey()` and `GetData()` methods. - -[NotificationStreamClient_godoc]: https://pkg.go.dev/github.com/nokia/srlinux-ndk-go@v0.1.0/ndk#SdkNotificationService_NotificationStreamClient -[NotificationStreamResponse_godoc]: https://pkg.go.dev/github.com/nokia/srlinux-ndk-go@v0.1.0/ndk#NotificationStreamResponse -[GetConfig_godoc]: https://pkg.go.dev/github.com/nokia/srlinux-ndk-go@v0.1.0/ndk#Notification.GetConfig -[ConfigNotification_godoc]: https://pkg.go.dev/github.com/nokia/srlinux-ndk-go@v0.1.0/ndk#ConfigNotification - -## Exiting gracefully - -Agent needs to handle SIGTERM signal that is sent when a user invokes `stop` command via SR Linux CLI. The following is the required steps to cleanly stop the agent: - -1. Remove any agent's state if it was set using [`TelemetryDelete`][SdkMgrTelemetryServiceClient_godoc] method of a Telemetry client. -2. Delete notification subscriptions stream [`NotificationRegister`][NewSdkMgrServiceClient_godoc] method with `Op` set to `NotificationRegisterRequest_Delete`. -3. Invoke use `AgentUnRegister()` method of a [`SdkMgrServiceClient`][NewSdkMgrServiceClient_godoc] interface. -4. Close gRPC channel with the `sdk_mgr`. - -[SdkMgrTelemetryServiceClient_godoc]: https://pkg.go.dev/github.com/nokia/srlinux-ndk-go@v0.1.0/ndk#SdkMgrTelemetryServiceClient - -## Logging - -To debug an agent, the developers can analyze the log messages that the agent produced. If the agent's logging facility used stdout/stderr to write log messages, then these messages will be found at `/var/log/srlinux/stdout/` directory. - -The default SR Linux debug messages are found in the messages directory `/var/log/srlinux/buffer/messages`; check them when something went wrong within the SR Linux system (agent registration failed, IDB server warning messages, etc.). - -[Logrus](https://github.com/sirupsen/logrus) is a popular structured logger for Go that can log messages of different levels of importance, but developers are free to choose whatever logging package they see fit. - -[^1]: Make sure that you have set up the dev environment as explained on [this page](../env/go.md). Readers are also encouraged to first go through the [gRPC basic tutorial](https://grpc.io/docs/languages/go/basics/) to get familiar with the common gRPC workflows when using Go. diff --git a/docs/ndk/guide/dev/go/app-instance.md b/docs/ndk/guide/dev/go/app-instance.md new file mode 100644 index 00000000..b005e577 --- /dev/null +++ b/docs/ndk/guide/dev/go/app-instance.md @@ -0,0 +1,157 @@ +# Application Instance + +At the end of the [main][main-go] function we create the instance of the greeter application by calling `greeter.NewApp(ctx, &logger)`: + +```go title="main.go" +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/main.go:main-init-app" +``` + +The `NewApp` function is defined in the [`greeter/app.go`][app-go] file and instantiates the `App` struct. + +```go linenums="1" title="greeter/app.go" +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:pkg-greeter" +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:app-struct" +``` + +The `App` struct is the main structure of the greeter application. It holds the application config, state, logger instance, gNMI client and the NDK clients to communicate with the NDK services. + +## Creating the App Instance + +The `NewApp` function is the constructor of the `App` struct. It takes the context and the logger as arguments and returns the pointer to the `App` struct. + +```{.go title="greeter/app.go" .code-scroll-lg} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:new-app" +``` + +## Connecting to NDK Socket + +As stated in the [NDK Operations][operations-ndk-mgr-client], the first thing we need to do is to connect to the NDK socket. This is what we do with the helper `connect` function inside the `NewApp` constructor: + +```{.go title="greeter/app.go" hl_lines="4"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:pkg-greeter" +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:pkg-greeter-const" +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:connect" +``` + +The connection is made to the NDK manager's unix socket using unsecured transport. The insecure transport is justifiable in this case as the NDK manager is running on the same host as the application. + +## Creating NDK Clients + +Recall, that NDK is a collection of gRPC services, and each service requires a client to communicate with it. + +The `NewApp` function creates the clients for the following services: + +* [NDK Manager Client:][operations-ndk-mgr-client] to interact with the NDK manager service. +* [Notification Service Client:][operations-subscr-to-notif] to subscribe to the notifications from the NDK manager. +* [Telemetry Service Client:][operations-handling-state] to update application's state. + +Creating clients is easy. We just leverage the [Generated NDK Bindings][srlinux-ndk-go] and the `ndk` package contained in the `github.com/nokia/srlinux-ndk-go` module. + +```{.go title="greeter/app.go"} +package greeter + +import ( + // snip + "github.com/nokia/srlinux-ndk-go/ndk" + // snip +) + +func NewApp(ctx context.Context, logger *zerolog.Logger) *App { + // snip +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:create-ndk-clients" + // snip +} +``` + +We pass to each client constructor function the gRPC connection we just created and off we go. + +## gNMI Client + +The NDK service collection allows your application to receive notifications from different SR Linux apps and services. But when it comes to changing SR Linux configuration or reading it your application needs to utilize one of the management interfaces. + +Since it is very common to have the application either reading existing configuration or changing it, we wanted our greeter app to demonstrate how to do it. + +/// note +When your application needs to read its own config, it can do so by leveraging the `Config` notifications and NDK Notification Client. It is only when the application needs to configure SR Linux or read the configuration outside of its own config that it needs to use the management interfaces. +/// + +When the greeter app creates the `greeting` message it uses the following template: + +```bash +👋 Hi ${name}, SR Linux was last booted at ${last-boot-time} +``` + +Since `name` value belongs to the greeter' application config, we can get this value later with the help of the NDK Notification Client. But the `last-boot-time` value is not part of the greeter app config and we need to get it from the SR Linux configuration. This is where we need greeter to use the management interface. + +We opted to use the gNMI interface in this tutorial powered by the awesome [gNMIc][gnmic] project. gNMIc project has lots of subcomponents revolving around gNMI, but we are going to use its API package to interact with the SR Linux's gNMI server. + +In the `NewApp` function right after we created the NDK clients we create the gNMI client: + +```{.go title="greeter/app.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:create-gnmi-target" +``` + +The `newGNMITarget` function creates the gNMI Target using the `gnmic` API package. We provide the gRPC server unix socket as the address to establish the connection as well as hardcoded default credentials for SR Linux. + +```{.go title="greeter/app.go" hl_lines="3"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:pkg-greeter-const" +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:new-gnmi-target" +``` + +/// details | gNMI Configuration on SR Linux +When you're using Containerlab-based lab environment, the gNMI server is configured to run over the unix socket as well, but when you run the greeter app in a production environment, you will have to make sure the relevant configuration is in place. +/// + +Once the target is created we create the gNMI client for it and returning the pointer to the target struct. + +## Registering the Agent + +Next task is to [register the agent][operations-register-agent] with the NDK manager. At this step NDK initializes the state of our agent, creates the IDB tables and assigns an ID to our application. + +Registration is carried out by calling the `AgentRegister` function of the NDK manager client. + +```{.go title="greeter/app.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:register-agent" +``` + +We pass the empty ` &ndk.AgentRegistrationRequest{}` as this is all we need to do to register the agent. + +The `AgentRegister` function returns the [`AgentRegistrationResponse`][agent-reg-resp-doc] that contains the agent ID assigned by the NDK manager. We store this response in a variable, since we will need it later. + +## App Config and State + +The last bit is to initialize the structure for our app's config and state. This struct will hold the configured `name`, the computed `greeting` value. Here is how our `ConfigState` struct looks: + +```{.go title="greeter/config.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/config.go:configstate-struct" +``` + +The role of the `receivedCh` channel is explained in the [Receiving Configuration](receiving-config.md) section. + +Finally, we return the pointer to the `App` struct from the `NewApp` function with struct fields initialized with the respective values. + +```{.go title="greeter/app.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:return-app" +``` + +1. Storing application ID received from the NDK manager when we [registered](#registering-the-agent) the agent. + +## Next Step + +Once we initialized the app struct with the necessary clients we go back to the `main` function where `app.Start(ctx)` is called to start our application. + +```go title="main.go" +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/main.go:main-init-app" +``` + +Let's see what happens there in the [Notification Stream](notif-stream.md) section. + +[main-go]: https://github.com/srl-labs/ndk-greeter-go/blob/main/main.go +[app-go]: https://github.com/srl-labs/ndk-greeter-go/blob/main/greeter/app.go +[operations-ndk-mgr-client]: ../../operations.md#creating-ndk-manager-client +[operations-subscr-to-notif]: ../../operations.md#subscribing-to-notifications +[operations-handling-state]: ../../operations.md#handling-applications-configuration-and-state +[operations-register-agent]: ../../operations.md#agent-registration +[srlinux-ndk-go]: https://github.com/nokia/srlinux-ndk-go +[agent-reg-resp-doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.AgentRegistrationResponse +[gnmic]: https://gnmic.openconfig.net diff --git a/docs/ndk/guide/dev/go/app-start.md b/docs/ndk/guide/dev/go/app-start.md new file mode 100644 index 00000000..98da4268 --- /dev/null +++ b/docs/ndk/guide/dev/go/app-start.md @@ -0,0 +1,20 @@ +# Application Start + +Recall that our program's entrypoint [finishes](main.md#initializing-the-application) with initializing the app struct and calling the `app.Start(ctx)` function. The `Start` function is a place where we start the application's lifecycle. + +```{.go title="greeter/app.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:app-start" +``` + +The `Start` function is composed of the following parts: + +1. [Start](#__codelineno-0-2) receiving configuration notifications with `receiveConfigNotifications` function. In this function we: + 1. Start Configuration Stream + 2. Receive notifications from the stream + 3. When the configuration notification is received, unmarshal received config into the `ConfigState` struct + 4. Upon "commit.end" marker seen in the config notification, signal that the whole config set has been read by sending a message to the `receivedCh` channel +2. [Process the configuration](#__codelineno-0-9) by computing the `greeting` value +3. [Update application's state](#__codelineno-0-11) by with `name` and `greeting` values +4. [Stop](#__codelineno-0-13:15){ data-proofer-ignore } the application when the context is cancelled + +Time to have a closer look at the first part of the `Start` function - receiving configuration notifications with `go a.receiveConfigNotifications(ctx)` function. diff --git a/docs/ndk/guide/dev/go/handling-notif.md b/docs/ndk/guide/dev/go/handling-notif.md new file mode 100644 index 00000000..d363dc9f --- /dev/null +++ b/docs/ndk/guide/dev/go/handling-notif.md @@ -0,0 +1,136 @@ +# Handling Received Config Notifications + +Now that we have a notification stream up and running, we can start receiving and handling Config notifications from the NDK. We are back again in the `receiveConfigNotifications` function where range over the `configStream` channel and receive notifications from the NDK. + +```{.go title="greeter/config.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/config.go:rcv-cfg-notif" +``` + +For every received [`NotificationStreamResponse`][notif_stream_resp_doc] from the `configStream` channel we: + +1. Log the incoming notification for debugging purposes. +2. Call the `handleConfigNotifications` function that handles the received notification. + +Recall, that our Notification Stream is a gRPC stream. This means that the notifications are streamed from the NDK to our app in real-time. When we talk about the configuration we need to process the full configuration before we can start using it. This is why we have the `receivedCh` channel that we utilize to signal the application when the full configuration has been received. + +Check out how the notifications logged when we configure a name for the greeter app and commit it: + +```srl +--{ + candidate shared default }--[ ]-- +A:greeter# greeter name "show me the stream" + +--{ +* candidate shared default }--[ ]-- +A:greeter# commit stay +All changes have been committed. Starting new transaction. +``` + +Upon commit action we receive two separate notifications, first one contains the new `name` value and the second one is a "commit end" marker. The "commit end" marker indicates that the committed config has been streamed in full and we can start using it. + +```json +2023-12-02 12:13:51 UTC INF Received notifications: +notification: { + sub_id: 1 + config: { + op: Update + key: { + js_path: ".greeter" + js_path_with_keys: ".greeter" + } + data: { + json: "{\n \"name\": \"show me the stream\"\n}\n" + } + } +} + +2023-12-02 12:13:51 UTC INF Received notifications: +notification: { + sub_id: 1 + config: { + op: Update + key: { + js_path: ".commit.end" + js_path_with_keys: ".commit.end" + } + data: { + json: "{\"commit_seq\":32}" + } + } +} +``` + +While in our example we only had one notification with "actual" config change, there might be many of them, before the "commit end" marker is received. So we need to handle them as they appear and stop only when the "commit end" marker is received. + +The `handleConfigNotifications` function is responsible for handling "important" notifications until the "commit end" marker is received. That way we only handle notifications that directly relate to the configuration and discard the marker notifications. + +```{.go title="greeter/config.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/config.go:buffer-cfg-notif" +``` + +Pay attention to the [`cfgNotif := n.GetConfig()`](#__codelineno-4-7) call. Since our [`NotificationStreamResponse`][notif_stream_resp_doc] embeds the [`Notification`][notif_doc] message, we need to extract the `Config` message from it by calling `n.GetConfig()`. In Go bindings, the Notification is the interface, with the `GetXXX` methods being the getters for the underlying message type. The [`GetConfig`][get-config] method returns the [`Config Notification`][config_notif_doc] message if the underlying message is of the `Config` type. + +For each notification that is not a "commit end" marker we call the `a.handleGreeterConfig(cfgNotif)` and whenever we receive the "commit end" marker we signal the application that the full configuration has been received. + +## Handling Greeter Config + +Now that we filtered notifications that only contain config-related information, we can handle them. + +By handling the config notifications we mean reading the configuration updates received from the notification stream and updating the application's [`ConfigState`](app-instance.md#app-config-and-state) struct with the received value. Later the `ConfigState` struct is used to update application's state in the state datastore. + +The `handleGreeterConfig` function is responsible for handling the received notifications. + +```{.go title="greeter/config.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/config.go:handle-greeter-cfg" +``` + +In this function we consider two cases: + +1. Configuration Notification contains the empty `data` field. This means that the config has been deleted/cleared and we need to clear the greeter values in the state data store of SR Linux. +2. Configuration Notification contains the non-empty `data` field. This means that the config has been updated or created, and we need to update the greeter values in the state data store of SR Linux. + +### Handling Config Deletion + +Let's start with the deletion case. How do we know that the config has been deleted? + +There are two options: + +1. We can check the `op` field of the [`ConfigNotification`][config_notif_doc] message. If the `op` field is set to `Delete`, then the object has been deleted. This does not apply for non-presence containers, like our [greeter YANG container](../../agent.md#yang-module), since they are always present. +2. We can have a look at the `data` field of the [`ConfigNotification`][config_notif_doc] message that contains the embedded [ConfigData][config_data_doc] message. The `ConfigData` message has the `json` field that contains the JSON representation of the config[^10] and if the json string is an empty json object, then the config has been deleted/emptied. + This applies to non-presence containers. + +Since our `greeter` container is a non-presence conatainer, in our code we use the 2nd method and check if the data field is empty in the received notification: + +```{.go title="greeter/config.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/config.go:delete-case" +``` + +An empty config means that we need to erase the `name` and `greeting` values of the [`ConfigState`](app-instance.md#app-config-and-state) struct. The empty values will then be populated in the state datastore. + +### Handling Config Update + +If the config is not empty, this means that it has been updated or created. In this case we need to update the [`ConfigState`](app-instance.md#app-config-and-state) struct our App struct uses to store the greeter values. + +```{.go title="greeter/config.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/config.go:non-delete-case" +``` + +We unmarshal the received configuration update to the [`ConfigState`](app-instance.md#app-config-and-state) struct. This will update the struct fields with the values from the received notification. + +## Signalling Config Received + +As we mentioned earlier, we need to signal the application when the full configuration has been received. We do this by sending a message to the `receivedCh` channel and this is done when we receive a config notification [with the ".commit.end" key](#__codelineno-3-21:29){ data-proofer-ignore } as part of the message. + +This indicates that the full commit set has been streamed and we can start using the configuration. + +The receiving end of the `receivedCh` channel is all the way back in the `Start` function after receiving the message from this channel indicates that we can start [processing the config](processing-config.md). + +```{.go title="greeter/app.go" hl_lines="6"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:app-start" +``` + +[notif_stream_resp_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.NotificationStreamResponse +[notif_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.Notification +[config_notif_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.ConfigNotification +[get-config]: https://github.com/nokia/srlinux-ndk-go/blob/main/ndk/sdk_service.pb.go#L958 +[config_data_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.ConfigData + +[^10]: The `ConfigData` message also has the `bytes` field, but it is not used by the NDK and is reserved for internal SR Linux applications. diff --git a/docs/ndk/guide/dev/go/index.md b/docs/ndk/guide/dev/go/index.md new file mode 100644 index 00000000..212ebe1e --- /dev/null +++ b/docs/ndk/guide/dev/go/index.md @@ -0,0 +1,189 @@ +# Developing NDK applications with Go + +[**Go**](https://go.dev) is a statically typed, compiled programming language designed at Google by Robert Griesemer, Rob Pike, and Ken Thompson. Go is syntactically similar to C, but with memory safety, garbage collection, structural typing, and CSP-style concurrency. + +Go is a solid and popular choice for developing NDK applications because of its simplicity, performance, powerful standard library, and static binary compilation. The latter allows for easy distribution of the NDK applications as a single binary file. + +In this chapter we will cover most of the aspects of developing NDK applications with Go. Based on the demo [**`srl-labs/ndk-greeter-go`**][greeter-go-repo] application we will cover everything from the project structure, through the NDK services interacton to the build and release process. + +Buckle up for an exciting journey into the world of Go and NDK! + +## Development Environment + +Although every developer's environment is different and is subject to a personal preference, we will provide recommendations for a [Go](https://go.dev) toolchain setup suitable for the NDK applications development. + +The toolchain that can be used to develop and build Go-based NDK apps consists of the following components: + +1. [Go programming language](https://golang.org/dl/) - Go compiler, toolchain, and standard library + To continue with this tutorial users should install the Go programming language on their development machine. The installation process is described in the [Go documentation](https://golang.org/doc/install). + +2. [Go NDK bindings](https://github.com/nokia/srlinux-ndk-go) - generated language bindings for the gRPC-based NDK service. + As covered in the [NDK Architecture](../../architecture.md) section, NDK is a collection of gRPC-based services. To be able to use gRPC services in a Go program the [language bindings](https://grpc.io/docs/languages/go/quickstart/) have to be generated from the [source proto files](../../architecture.md#proto-files). + + Nokia not only provides the [proto files](https://github.com/nokia/srlinux-ndk-protobufs) for the SR Linux NDK service but also offers [NDK Go language bindings](https://github.com/nokia/srlinux-ndk-go) generated for each NDK release. + + With the provided Go bindings, users don't need to generate them themselves. + +3. [Goreleaser](https://goreleaser.com/) - Go-focused build & release pipeline runner. Contains [nFPM](https://nfpm.goreleaser.com/) project to craft deb/rpm packages. Deb/RPM packages is the preferred way to [install NDK agents](../../agent-install-and-ops.md). + Goreleaser is optional, but it is a nice tool to build and release Go-based NDK applications in an automated fashion. + +## Meet the `greeter` + +This tutorial is based on the simple `greeter` NDK app published at [**`srl-labs/ndk-greeter-go`**][greeter-go-repo] GitHub repository. The app is a simple starter kit for developers looking to work with the NDK. It gets a developer through the most common NDK functionality: + +* Agent Registration +* Receiving and handling configuration +* Performing "the work" based on the received config +* And finally publishing state + +The `greeter` app adds `/greeter` context to SR Linux and allows users to configure `/greeter/name` value. Greeter will greet the user with a message +`👋 Hi ${provided name}, SR Linux was last booted at ${last-booted-time}` +and publish `/greeter/name` and `/greeter/greeting` values in the state datastore. + +Maybe a quick demo that shows how to interact with `greeter` and get its state over gNMI and JSON-RPC is worth a thousand words: + +
+ +
+ +## Deploying the lab + +Before taking a deep dive into the code, let's deploy the `greeter` app to SR Linux using containerlab and see how it works. + +/// details | Containerlab for NDK +When developing NDK applications, it is important to have a lab environment to test the application. The lab environment should be as close as possible to the production environment and also be easy to spin up and tear down. + +The [Containerlab](https://containerlab.dev/) tool is a perfect fit for this purpose. Containerlab makes it easy to create a personal lab environment composed of network devices and connected by virtual links. We are going to use Containerlab to create a lab environment for the `greeter` NDK application development down the road. +/// + +It all starts with cloning the `greeter`[greeter-go-repo] repo: + +```bash +git clone https://github.com/srl-labs/ndk-greeter-go.git && \ +cd ndk-greeter-go +``` + +/// note + attrs: {class: inline end} +[Containerlab v0.48.6](https://containerlab.dev/install) version and SR Linux 23.10.1 are used in this tutorial. Users are advised to use these version to have the same outputs as in this tutorial. + +Newer versions of Containerlab and SR Linux should work as well, but the outputs might be slightly different. +/// + +And then running the deployment script[^10]: + +```bash +./run.sh deploy-all #(1)! +``` + +1. `deploy-all` is a script that builds the `greeter` app, deploys a containerlab topology file, and installs the app on the running SR Linux node. + +It won't take you longer than 30 seconds to get the `greeter` app up and running on a freshly deployed lab. Type `ssh greeter` and let's configure our greeter app: + +```bash +❯ ssh greeter #(1)! +Warning: Permanently added 'greeter' (ED25519) to the list of known hosts. + +Welcome to the srlinux CLI. +Type 'help' (and press ) if you need any help using this. + +--{ running }--[ ]-- +A:greeter# +``` + +1. Containerlab injects host routes and SSH config on your system to allow you to connect to the lab nodes using only its name. + +Once connected to the `greeter` SR Linux node, let's configure the app: + +```srl +--{ running }--[ ]-- +A:greeter# enter candidate + +--{ candidate shared default }--[ ]-- +A:greeter# greeter + +--{ candidate shared default }--[ greeter ]-- +A:greeter# name "Learn SR Linux Reader" + +--{ * candidate shared default }--[ greeter ]-- +A:greeter# commit stay +All changes have been committed. Starting new transaction. +``` + +Now that we've set the `name` value, let's verify that the name is indeed set in the candidate configuration and running datastore: + +```srl +--{ + candidate shared default }--[ greeter ]-- +A:greeter# info from running + name "Learn SR Linux Reader" +``` + +Look at that, the `greeting` value is not there. That's because the `greeting` is a state leaf, it is only present in the state datastore. Let's check it out, while we're in the `/greeter` context we can use `info from state` command to get the state of the current context: + +```srl +--{ + candidate shared default }--[ greeter ]-- +A:greeter# info from state + name "Learn SR Linux Reader" + greeting "👋 Hi Learn SR Linux Reader, SR Linux was last booted at 2023-11-29T21:28:53.282Z" +``` + +As advertised, the greeter app greets us with a message that includes the `name` value we've set and the last booted time of the SR Linux node. Should you change the `name` value and commit, you will see the new `greeting` message. + +## Project structure + +The project structure is a matter of personal preference. There are no strict rules on how to structure a Go project. However, there are some best practices we can enforce making the NDK project structure more consistent and easier to understand. + +This is the project structure used in this tutorial: + +```bash +❯ tree +. +├── LICENSE +├── README.md +├── build #(1)! +├── go.mod +├── go.sum +├── goreleaser.yml #(2)! +├── greeter #(3)! +├── greeter.yml #(4)! +├── lab +│ └── greeter.clab.yml #(5)! +├── logs +│ ├── greeter #(6)! +│ └── srl #(7)! +├── main.go #(8)! +├── nfpm.yml #(9)! +├── run.sh #(10)! +└── yang #(11)! + └── greeter.yang +``` + +1. Directory to store build artifacts. This directory is ignored by Git. +2. [Goreleaser](https://goreleaser.com/) config file to build and publish the NDK application. Usually run via CI/CD pipeline. +3. Directory to store the `greeter` package source code. This is where the application logic is implemented. +4. Application [configuration file](../../agent.md#application-manager-and-application-configuration-file). +5. Containerlab topology file to assist with the development and testing of the NDK application. +6. Directory with the application log file. +7. Directory with the SR Linux log directory to browse the SR Linux applications logs. +8. Main executable file. +9. [nFPM](https://nfpm.goreleaser.com/) configuration file to build deb/rpm packages locally. +10. Script to orchestrate lab environment and application lifecycle. +11. Directory with the application YANG modules. + +Besides short descriptions, we will cover the purpose of each file and directory in the following sections when we start to peel off the layers of the `greeter` NDK application. + +## Application Configuration + +As was [mentioned before][app-config], in order for the NDK application to be installed on the SR Linux node, it needs to be registered with the Application Manager. The Application Manager is a service that manages the lifecycle of all applications, native and custom ones. + +The Application Manager uses the application configuration file to onboard the application. Our greeter app comes with the following [`greeter.yml`][greeter-yml] configuration file: + +```yaml +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter.yml:snip" +``` + +Refer to the [application configuration][app-config] section to better understand what each field means. Application Manager will look for the `greeter` binary in the `/usr/local/bin/` directory when starting our application. + +[greeter-go-repo]: https://github.com/srl-labs/ndk-greeter-go +[app-config]: ../../agent.md#application-manager-and-application-configuration-file +[greeter-yml]: https://github.com/srl-labs/ndk-greeter-go/blob/main/greeter.yml diff --git a/docs/ndk/guide/dev/go/logging.md b/docs/ndk/guide/dev/go/logging.md new file mode 100644 index 00000000..e6a6503d --- /dev/null +++ b/docs/ndk/guide/dev/go/logging.md @@ -0,0 +1,86 @@ +# Logging + +Application logs are essential for debugging, troubleshooting, and monitoring. NDK apps are no exception. In this section we explain how we set up logging for NDK applications based on the `greeter` example. + +There are three different how NDK app can log messages: + +1. By creating its own log file. +2. By logging to the stdout/stderr. +3. By logging to the syslog. + +## Logging to a file + +Logging to a file is the most common way to log messages. NDK app can create a file by the `/var/log//.log` path and write messages to it. + +It is up to the developer to decide what messages to log and how to format them. It is also up to the developer to decide when to rotate the log file to not overflow the disk space and reduce the frequency of the disk writes if the log location is on a flash drive. + +## Logging to stdout/stderr + +Logging to stdout/stderr is the simplest way to log messages. When NDK app is registered with SR Linux, any `fmt.PrintX()` or `log.XXX` call will write the message to the `/var/log/srlinux/.log` file. + +Note, that this log file is not managed by SR Linux and is not rotated automatically. Therefore, this log destination is only suitable for debugging and troubleshooting, but not for production. + +## Logging to Syslog + +Syslog is the default way to log messages in SR Linux. NDK app can write messages to syslog with the needed severity/facility and SR Linux will take care of the rest. + +## Setting up logging + +In the greeter app the logging is set up in the `main()` function: + +```{.go linenums="1" hl_lines="18" title="main.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/main.go:pkg-main" +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/main.go:pkg-main-vars" +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/main.go:main" +``` + +We create and configure the logger instance in the `setupLogger()` function: + +```{.go linenums="1" title="main.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/main.go:setup-logger" +``` + +Our logger is implemented by the [zerolog](https://github.com/rs/zerolog) logger library. It is a very powerful and fast logger that supports structured logging and can write messages to different destinations. + +We configure our logger to write to two destinations: + +1. To a file with log rotation policy in the structured JSON format when the application is running in production mode. +2. To `stdout` with the `console` format when the application is running in debug mode. + +With this setup we show two modes of logging that can be used in the NDK applications. + +/// note | Syslog +Syslog-based logging examples will follow soon. +/// + +### Stdout log + +To make sure we log to `stdout` only when the app is running in debug mode, we check if `/tmp/.ndk-dev-mode` file exists on a filesystem. If it does, we set the logger to write to stdout. Developers can add this file on a filesystem when they need to see the logs in the console format. + +When you run the greeter app using the `run.sh` script, the `/tmp/.ndk-dev-mode` file is created automatically. + +Once the lab is running, you can see the logs in the console format by opening the `./logs/srl/stdout/greeter.log` file relative to the apps' repository root: + +``` +❯ tail -5 logs/srl/stdout/greeter.log +2023-12-05 09:37:13 UTC INF Fetching SR Linux uptime value +2023-12-05 09:37:14 UTC INF GetResponse: notification:{timestamp:1701769034084642836 update:{path:{elem:{name:"system"} elem:{name:"information"} elem:{name:"last-booted"}} val:{string_val:"2023-12-05T09:34:49.769Z"}}} +2023-12-05 09:37:14 UTC INF updating: .greeter: {"name":"me","greeting":"👋 Hi me, SR Linux was last booted at 2023-12-05T09:34:49.769Z"} +2023-12-05 09:37:14 UTC INF Telemetry Request: state:{key:{js_path:".greeter"} data:{json_content:"{\"name\":\"me\",\"greeting\":\"👋 Hi me, SR Linux was last booted at 2023-12-05T09:34:49.769Z\"}"}} +2023-12-05 09:37:14 UTC INF Telemetry add/update status: kSdkMgrSuccess, error_string: "" +``` + +### File log + +The default and "always-on" logging destination the greeter app uses is the file log. The log file destination is configured with the [lumberjack](https://github.com/natefinch/lumberjack) library that provides log rotation functionality. + +The log file location is set to `/var/log/greeter/greeter.log` and log format is set to JSON (default for the `zerolog` library). This format is ideal for use with logtail/fluentd/filebeat and other log collectors to push logs to central log storage. + +``` +❯ tail -5 logs/greeter/greeter.log +{"level":"info","time":"2023-12-05T09:37:13Z","message":"Fetching SR Linux uptime value"} +{"level":"info","time":"2023-12-05T09:37:14Z","message":"GetResponse: notification:{timestamp:1701769034084642836 update:{path:{elem:{name:\"system\"} elem:{name:\"information\"} elem:{name:\"last-booted\"}} val:{string_val:\"2023-12-05T09:34:49.769Z\"}}}"} +{"level":"info","time":"2023-12-05T09:37:14Z","message":"updating: .greeter: {\"name\":\"me\",\"greeting\":\"👋 Hi me, SR Linux was last booted at 2023-12-05T09:34:49.769Z\"}"} +{"level":"info","time":"2023-12-05T09:37:14Z","message":"Telemetry Request: state:{key:{js_path:\".greeter\"} data:{json_content:\"{\\\"name\\\":\\\"me\\\",\\\"greeting\\\":\\\"👋 Hi me, SR Linux was last booted at 2023-12-05T09:34:49.769Z\\\"}\"}}"} +{"level":"info","time":"2023-12-05T09:37:14Z","message":"Telemetry add/update status: kSdkMgrSuccess, error_string: \"\""} +``` diff --git a/docs/ndk/guide/dev/go/main.md b/docs/ndk/guide/dev/go/main.md new file mode 100644 index 00000000..39ca065b --- /dev/null +++ b/docs/ndk/guide/dev/go/main.md @@ -0,0 +1,114 @@ +# Application Entry Point + +In Go, the `main()` function is the entry point of the binary application and is defined in the [`main.go`][main-go] file of our application: + +```{.go linenums="1"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/main.go:pkg-main" +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/main.go:pkg-main-vars" +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/main.go:main" +``` + +## Application version + +As you can see, the `main` function is rather simple. First, we [handle the `version`](#__codelineno-0-9:16){ data-proofer-ignore } CLI flag to make sure our application can return its version when asked. + +Application config has a [`version-command`](index.md#__codelineno-7-4) field that indicates which command needs to be executed to get the application version. In our case, the `version` field is set to `greeter --version` and we just went through the handler of this flag. + +In SR Linux CLI we can get the version of the `greeter` app by executing the `greeter --version` command: + +```srl +--{ + running }--[ ]-- +A:greeter# show system application greeter + +---------+------+---------+-------------+--------------------------+ + | Name | PID | State | Version | Last Change | + +=========+======+=========+=============+==========================+ + | greeter | 4676 | running | dev-a6f880b | 2023-11-29T21:29:04.243Z | + +---------+------+---------+-------------+--------------------------+ +``` + +/// details | Why the version is `dev-a6f880b`? +Attentive readers might have noticed that the version of the `greeter` app is `dev-a6f880b` instead of `v0.0.0-` following the [`version` and `commit` variables](#__codelineno-8-3:6){ data-proofer-ignore } values in [`main.go`][main-go] file. This is because we setting the values for these variables at build time using the Go linker flags in the [`run.sh`][runsh] script: + +```bash +LDFLAGS="-s -w -X main.version=dev -X main.commit=$(git rev-parse --short HEAD)" +``` + +These variables are then set to the correct values when we build the application with Goreleaser. +/// + +## Setting up the Logger + +Logging is an important part of any application. It aids the developer in debugging the application and provides valuable information about the application's state for its users. + +```go +func main() { + // snip + logger := setupLogger() + // snip +} +``` + +We create the logger before initializing the application so that we can pass it to the application and use it to log the application's state. + +Logging from the NDK application is a separate topic that is covered in the [Logging](logging.md) section of this guide. + +## Context, gRPC Requests and Metadata + +Moving down the `main` function, we create the [context](https://www.ardanlabs.com/blog/2019/09/context-package-semantics-in-go.html) that will drive the lifecycle of our greeter application. + +Once the context is created we attach the [metadata](https://grpc.io/docs/guides/metadata/) to it. The metadata is a map of key-value pairs that will be sent along with the gRPC requests. + +The NDK service uses the metadata to identify the application from which the request was sent. + +```go +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/main.go:metadata" +``` + +The metadata **must** be attached to the parent context and it should has the `agent_name` key with the value of the application name. The application name in the metadata doesn't have to match anything, but should be unique among all the applications that are registered with the Application Manager. + +## Exit Handler + +Another important part of the application lifecycle is the exit handler. In the context of the NDK application life cycle the exit handler is a function that is called when the application receives Interrupt or SIGTERM signals. + +The exit handler is a good place to perform cleanup actions like closing the open connections, releasing resources, etc. + +We execute `exitHandler` function passing it the cancel function of the context: + +```go linenums="1" +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/main.go:exit-handler" +``` + +This function is non-blocking as it spawns a goroutine that waits for the registered signals and then execute the `cancel` function of the context. This will propagate the cancellation signal to all the child contexts and our application [reacts](#__codelineno-6-13:15){ data-proofer-ignore } to it. + +```go linenums="1" hl_lines="19-21" title="greeter/app.go" +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:app-start" +``` + +We will cover the `func (a *App) Start()` function properly when we get there, but for now, it is important to highlight how cancellation of the main context is intercepted in this function and leading to `a.stop()` call. + +The `a.stop()` function is responsible to perform the graceful shutdown of the application. + +```go linenums="1" title="greeter/app.go" +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:app-stop" +``` + +Following the [Graceful Exit](../../operations.md#exiting-gracefully) section we first unregister the agent with the NDK manager and then closing all connections that our app had opened. + +## Initializing the Application + +And finally in the main function we initialize the greeter application and start it: + +```go title="main.go" +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/main.go:main-init-app" +``` + +This is where the application logic starts to kick in. Let's turn the page and start digging into it in the [next chapter](app-instance.md). + +[greeter-go-repo]: https://github.com/srl-labs/ndk-greeter-go +[runsh]: https://github.com/srl-labs/ndk-greeter-go/blob/main/run.sh +[greeter-yml]: https://github.com/srl-labs/ndk-greeter-go/blob/main/greeter.yml +[main-go]: https://github.com/srl-labs/ndk-greeter-go/blob/main/main.go +[ndk_proto_repo]: https://github.com/nokia/srlinux-ndk-protobufs +[ndk_go_bindings]: https://github.com/nokia/srlinux-ndk-go +[go_package_repo]: https://pkg.go.dev/github.com/nokia/srlinux-ndk-go@v0.1.0/ndk +[cfg_svc_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#ndk%2fconfig_service.proto diff --git a/docs/ndk/guide/dev/go/notif-stream.md b/docs/ndk/guide/dev/go/notif-stream.md new file mode 100644 index 00000000..0a56fb8a --- /dev/null +++ b/docs/ndk/guide/dev/go/notif-stream.md @@ -0,0 +1,112 @@ +# Notification Stream + +In the previous chapter we looked at `receiveConfigNotifications` function that is responsible for receiving configuration notifications from the NDK. We saw that it starts with creating the notification stream - by calling `a.StartConfigNotificationStream(ctx)` - and then starts receiving notifications from it. + +Let's see how the notification stream is created and how we receive notifications from it. + +```{.go title="greeter/notification.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/notification.go:start-cfg-notif-stream" +``` + +The `StartConfigNotificationStream` performs three major tasks: + +1. Create the notification stream and associated Stream ID +2. Add the `Config` subscription to the allocated notification stream +3. Creates the streaming client and starts sending received notifications to the `streamChan` channel + +Wouldn't hurt to have a look at each of these tasks in more detail. + +## Creating Notification Stream + +First, on [line 2](#__codelineno-0-2), we create a notification stream as explained in the [Creating Notification Stream][operations-create-notif-stream] section. + +```{.go title="greeter/notification.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/notification.go:create-notif-stream" +``` + +The function tries to create a notification stream for the `greeter` application with a retry timeout and returns the allocated Stream ID when it succeeds. The Stream ID is later used to request notification delivery of a specific type, which is in our case the [Config Notification][config_notif_doc]. + +## Adding Config Subscription + +With the notification stream created, we now request the NDK to deliver updates of our app's configuration. These are the updates made to the config tree of the greeter app, and it has only one configurable field - `name` leaf. + +This is done in the [`a.addConfigSubscription(ctx, streamID)`](#__codelineno-0-8) function. + +```{.go title="greeter/notification.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/notification.go:add-cfg-sub" +``` + +To indicate that we want to receive config notifications over the created notification stream we have to craft the [`NotificationRegisterRequest`][notif_reg_req_doc]. We populate it with the `streamID` received after creating the notification stream to specify the stream we want to receive the notifications on. + +The `SubscriptionTypes` set to the `&ndk.NotificationRegisterRequest_Config` value indicates that we would like to receive updates of this specific type as they convey configuration updates. + +And we pass the empty [`ConfigSubscriptionRequest`][cfg_sub_req_doc] request since we don't want to apply any filtering on the notifications we receive. + +Executing `NotificationRegister` function of the `SDKMgrServiceClient` with notification Stream ID and [`NotificationRegisterRequest`][notif_reg_req_doc] effectively tells NDK about our intention to receive `Config` messages. + +It is time to start the notification stream. + +## Starting Notification Stream + +[The last bits](#__codelineno-0-10:14){ data-proofer-ignore } in the `StartConfigNotificationStream` function create a Go channel[^10] of type [`NotificationStreamResponse`][notif_stream_resp_doc] and pass it to the `startNotificationStream` function that is started in its own goroutine. Here is the `startNotificationStream` function: + +```{.go title="greeter/notification.go" .code-scroll-lg} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/notification.go:start-notif-stream" +``` + +Let's have a look at the two major parts of the function - creating the streaming client and receiving notifications. + +### Stream Client + +The function [starts](#__codelineno-3-14) with creating a Notification Stream Client with `a.getNotificationStreamClient(ctx, req)` function call. This client is a pure gRPC construct, it is automatically generated from the gRPC service proto file and facilitates the streaming of notifications. + +```{.go title="greeter/notification.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/notification.go:stream-client" +``` + +### Receiving Notifications + +Coming back to our `startNotificationStream` function, we can see that it [loops](#__codelineno-5-16:37){ data-proofer-ignore } over the notifications received from the NDK until the parent context is cancelled. The `streamClient.Recv()` function call is a blocking call that waits for the next notification to be streamed from the NDK. + +```{.go title="greeter/notification.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/notification.go:start-notif-stream" +``` + +When the notification is received, it is passed to the `streamChan` channel. On the receiving end of this channel is our app's [`Start`](#__codelineno-0-6:17){ data-proofer-ignore } function that starts the `aggregateConfigNotifications` function for each received notification. + +/// details | Stream Response Type + +If you wonder what type the notifications are, it solely depends on the type of subscriptions we added on the notification stream. In our case, we only [added](#adding-config-subscription) the `Config` subscription, so the notifications we receive will be backed by the [`ConfigNotification`][config_notif_doc] type. + +Since the Notification Client can transport notifications of different types, the notification type is hidden behind the [`NotificationStreamResponse`][notif_stream_resp_doc] type. The `NotificationStreamResponse` embeds the `Notification` message that can be one of the following types: + +```proto +message Notification +{ + uint64 sub_id = 1; /* Subscription identifier */ + oneof subscription_types + { + InterfaceNotification intf = 10; // Interface details + NetworkInstanceNotification nw_inst = 11; // Network instance details + LldpNeighborNotification lldp_neighbor = 12; // LLDP neighbor details + ConfigNotification config = 13; // Configuration notification + BfdSessionNotification bfd_session = 14; // BFD session details + IpRouteNotification route = 15; // IP route details + AppIdentNotification appid = 16; // App identification details + NextHopGroupNotification nhg = 17; // Next-hop group details + } +} +``` + +See the `ConfigNotification` type? This is what we expect to receive in our app. +/// + +Now our configuration notifications are streamed from the NDK to our app. Let's see how we process them to update the app's configuration. + +[operations-create-notif-stream]: ../../operations.md#creating-notification-stream +[notif_reg_req_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.NotificationRegisterRequest +[cfg_sub_req_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.ConfigSubscriptionRequest +[notif_stream_resp_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.NotificationStreamResponse +[config_notif_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.ConfigNotification + +[^10]: Here is where Go channels come really handy because we can use them to deliver the notifications to our app. diff --git a/docs/ndk/guide/dev/go/processing-config.md b/docs/ndk/guide/dev/go/processing-config.md new file mode 100644 index 00000000..90037e16 --- /dev/null +++ b/docs/ndk/guide/dev/go/processing-config.md @@ -0,0 +1,31 @@ +# Processing Config + +Now that our application has its config stored in the `ConfigState` struct, we can use it to perform the application logic. While in the case of `greeter` the app logic is trivial, in real-world applications it might be more complex. + +```{.go title="greeter/app.go" hl_lines="9"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:app-start" +``` + +Greeter' core app logic is to calculate the greeting message that consists of a name and the last-booted-time of SR Linux system. + +```{.go title="greeter/config.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/config.go:process-config" +``` + +The `processConfig` function first checks if the `name` field is not an empty string. If it is an empty, it means the config name was either deleted or not configured at all. In this case we set the `ConfigState` struct to an empty value which should result in the empty state in the SR Linux data store. + +If `name` is set we proceed with calculating the greeting message. Remember that we need to retrieve the last-booted-time from the SR Linux system. We do this by calling the `getUptime` function. + +```{.go title="greeter/app.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:get-uptime" +``` + +[Earlier on](app-instance.md#gnmi-client), we created the gNMI Target and now it is time to use it. We use the **gNMI Get** request to retrieve the `/system/information/last-booted` value from the SR Linux. The `Get` function returns a **gNMI Get Response** from which we extract the `last-booted` value as string. + +Now that we have the `name` value retrieved from the configuration notification and `last-booted` value fetched via gNMI, we can compose the greeting message: + +```{.go title="greeter/config.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/config.go:greeting-msg" +``` + +With our `ConfigState` struct populated with the `name` and `greeting` values we have only one step left: to [update the state](updating-state.md) datastore with them. diff --git a/docs/ndk/guide/dev/go/receiving-config.md b/docs/ndk/guide/dev/go/receiving-config.md new file mode 100644 index 00000000..03930225 --- /dev/null +++ b/docs/ndk/guide/dev/go/receiving-config.md @@ -0,0 +1,31 @@ +# Receiving Configuration + +For our application to work it needs to receive its own configuration from the SR Linux Management Server. This process is facilitated by the NDK and subscriptions to the configuration notifications. + +In the NDK Operations section about [Subscribing to Notifications][operations-subscr-to-notif] we explained how NDK plays a somewhat "proxy" role for your application when it needs to receive updates from other SR Linux applications. + +--8<-- "docs/ndk/guide/operations.md:notif-diagram" + +Our greeter app is no different, it needs to receive notifications from the NDK, but it only needs to receive a particular notification type - its own configuration updates. +Whenever we configure the `/greeter/name` leaf and commit the configuration, our app needs to receive updates and act on them. + +It all begins in the `Start` function where we called `a.receiveConfigNotifications(ctx)` function. + +```{.go title="greeter/app.go" hl_lines="2"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:app-start" +``` + +We start this function in a goroutine because we want this function to signal when the full configuration has been received by writing to `receivedCh` channel. We will see how this is used later. + +Inside the `receiveConfigNotifications` function we start by creating the Configuration Notification Stream; this is the stream of notifications about greeter's config. + +```{.go title="greeter/config.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/config.go:rcv-cfg-notif" +``` + +Then we start receiving notifications from the stream. For every received [`NotificationStreamResponse`][notif_stream_resp_doc] from the `configStream` channel we handle that notification with `handleConfigNotifications` function. + +But first, let's understand how the notification stream is started in the next chapter. + +[operations-subscr-to-notif]: ../../operations.md#subscribing-to-notifications +[notif_stream_resp_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.NotificationStreamResponse diff --git a/docs/ndk/guide/dev/go/updating-state.md b/docs/ndk/guide/dev/go/updating-state.md new file mode 100644 index 00000000..a450d583 --- /dev/null +++ b/docs/ndk/guide/dev/go/updating-state.md @@ -0,0 +1,35 @@ +# Updating State + +When `processConfig` function finished processing the configuration, we need to update the state datastore with the new values. We do this by calling the `updateState` function. + +```{.go title="greeter/app.go" hl_lines="11"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/app.go:app-start" +``` + +In SR Linux, the state data store contains both configuration and read-only elements, so we need to update the state datastore with the `ConfigState` struct that contains both the `name` and `greeting` values. + +```{.go title="greeter/state.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/state.go:state-const" +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/state.go:update-state" +``` + +The `updateState` function first marshals the `ConfigState` struct to JSON and then calls `telemetryAddOrUpdate` function to post these changes to the state datastore. + +Let's see what's inside the `telemetryAddOrUpdate`: + +```{.go title="greeter/state.go"} +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter/state.go:telemetry-add-or-update" +``` + +As we covered in the [Handling application's configuration and state](../../operations.md#handling-applications-configuration-and-state) section, NDK's [Telemetry service][sdk_mgr_telem_svc_doc] provides the RPCs to add/update and delete data from SR Linux's state data store. We initialized the Telemetry service client when we [created](app-instance.md#creating-ndk-clients) the application's instance at the very beginning of this tutorial, and now we use it to modify the state data. + +First we craft the [`TelemetryUpdateRequest`][sdk_mgr_telem_upd_req_doc] that has `TelemetryInfo` message nested in it, which contains the `TelemetryKey` and `TelemetryData` messages. The `TelemetryKey` message contains the `path` field that specifies the path to the state data element we want to update. The `TelemetryData` message contains the `json` field that contains the JSON representation of the data we want to add/update. + +Our `ConfigState` struct was already marshaled to JSON, so we just need to set the `json` field of the `TelemetryData` message to the marshaled JSON and call `TelemetryAddOrUpdate` function of the Telemetry service client. + +This will update the state data store with the new values. + +Congratulations :partying_face:! You have successfully implemented the greeter application and reached the end of this tutorial. You can now apply the core concepts you learned here to build your own applications that extend SR Linux functionality and tailor it to your needs. + +[sdk_mgr_telem_svc_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.SdkMgrTelemetryService +[sdk_mgr_telem_upd_req_doc]:https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.TelemetryUpdateRequest diff --git a/docs/ndk/guide/dev/python.md b/docs/ndk/guide/dev/py/greeter-py.md similarity index 95% rename from docs/ndk/guide/dev/python.md rename to docs/ndk/guide/dev/py/greeter-py.md index 31bd7585..45a58a72 100644 --- a/docs/ndk/guide/dev/python.md +++ b/docs/ndk/guide/dev/py/greeter-py.md @@ -3,7 +3,7 @@ This guide explains how to consume the NDK service when developers write the agents using Python[^1]. !!!note - This guide provides code snippets for several operations that a typical agent needs to perform according to the [NDK Service Operations Flow](../architecture.md#operations-flow) chapter. + This guide provides code snippets for several operations that a typical agent needs to perform according to the [NDK Service Operations Flow](../../operations.md) chapter. Where applicable, the chapters on this page will refer to the NDK Architecture section to provide more context on the operations. @@ -14,7 +14,7 @@ In addition to the publicly available [protobuf files][ndk_proto_repo], which de ## Establish gRPC channel with NDK manager and instantiate an NDK client -[:octicons-question-24: Additional information](../architecture.md#grpc-channel-and-ndk-manager-client) +[:octicons-question-24: Additional information](../../operations.md#creating-ndk-manager-client) To call service methods, a developer first needs to create a gRPC channel to communicate with the NDK manager application running on SR Linux. @@ -38,7 +38,7 @@ sdk_mgr_client = SdkMgrServiceStub(channel) ## Register the agent with the NDK manager -[:octicons-question-24: Additional information](../architecture.md#agent-registration) +[:octicons-question-24: Additional information](../../operations.md#agent-registration) Agent must be first registered with SR Linux by calling the `AgentRegister` method available on the returned [`SdkMgrService`][SdkMgrService_docs] interface. The initial agent state is created during the registration process. @@ -77,7 +77,7 @@ The [`AgentRegister`][SdkMgrService_docs] method returns a [`AgentRegistrationRe ## Register notification streams -[:octicons-question-24: Additional information](../architecture.md#registering-notifications) +[:octicons-question-24: Additional information](../../operations.md#creating-notification-stream) ### Create subscription stream @@ -139,7 +139,7 @@ else: ## Streaming notifications -[:octicons-question-24: Additional information](../architecture.md#streaming-notifications) +[:octicons-question-24: Additional information](../../operations.md#streaming-notifications) Actual streaming of notifications is a task for another service - [`SdkNotificationService`][SdkNotificationService_docs]. This service requires developers to create its own client, which is done with `SdkNotificationServiceStub` function. @@ -165,7 +165,7 @@ for response in stream_response: ## Handle the streamed notifications -[:octicons-question-24: Additional information](../architecture.md#handling-notifications) +[:octicons-question-24: Additional information](../../operations.md#handling-notifications) Handling notifications starts with reading the incoming notification messages and detecting which type this notification is exactly. When the type is known the client reads the fields of a certain notification. Here is a method that checks for all notification types and delegates handling to helper methods. @@ -218,4 +218,4 @@ To debug an agent, the developers can analyze the log messages that the agent pr The default SR Linux debug messages are found in the messages directory `/var/log/srlinux/buffer/messages`; check them when something went wrong within the SR Linux system (agent registration failed, IDB server warning messages, etc.). -[^1]: Make sure that you have set up the dev environment as explained on [this page](../env/python.md). +[^1]: Make sure that you have set up the dev environment as explained on [this page](index.md). diff --git a/docs/ndk/guide/env/python.md b/docs/ndk/guide/dev/py/index.md similarity index 84% rename from docs/ndk/guide/env/python.md rename to docs/ndk/guide/dev/py/index.md index fe27bde7..c03e02c6 100644 --- a/docs/ndk/guide/env/python.md +++ b/docs/ndk/guide/dev/py/index.md @@ -1,5 +1,9 @@ # Python Development Environment +/// warning "Work in progress" +This tutorial might be outdated. Please check Go tutorial for the latest updates until this version is updated. +/// + Although every developer's environment is different and is subject to a personal preference, we will provide some recommendations for a [Python](https://www.python.org/) toolchain setup suitable for the development of NDK applications. ## Environment components @@ -24,7 +28,7 @@ Here is an example project structure that you can use for the NDK agent developm ## NDK language bindings -As explained in the [NDK Architecture](../architecture.md) section, NDK is a gRPC based service. The [language bindings](https://grpc.io/docs/languages/python/quickstart/) have to be generated from the source proto files to use gRPC services in a Python program. +As explained in the [NDK Architecture](../../architecture.md) section, NDK is a gRPC based service. The [language bindings](https://grpc.io/docs/languages/python/quickstart/) have to be generated from the source proto files to use gRPC services in a Python program. Nokia provides both the [proto files](https://github.com/nokia/srlinux-ndk-protobufs) for the SR Linux NDK service and also [NDK Python language bindings](https://github.com/nokia/srlinux-ndk-py). diff --git a/docs/ndk/guide/env/go.md b/docs/ndk/guide/env/go.md deleted file mode 100644 index 2172e795..00000000 --- a/docs/ndk/guide/env/go.md +++ /dev/null @@ -1,37 +0,0 @@ -# Go Development Environment -Although every developer's environment is different and is subject to a personal preference, we will provide recommendations for a [Go](https://go.dev) toolchain setup suitable for the development and build of NDK applications. - -## Environment components -The toolchain that can be used to develop and build Go-based NDK apps consists of the following components: - -1. [Go programming language](https://golang.org/dl/) - Go compiler, toolchain, and standard library -2. [Go NDK bindings](https://github.com/nokia/srlinux-ndk-go) - generated data access classes for gRPC based NDK service. -3. [Goreleaser](https://goreleaser.com/) - Go-focused build & release pipeline runner. Packages [nFPM](https://nfpm.goreleaser.com/) to produce rpm packages that can be used to [install NDK agents](../agent-install-and-ops.md). - - -## Project structure -It is recommended to use [Go modules](https://golang.org/ref/mod) when developing applications with Go. Go modules allow for better dependency management and can be placed outside the `$GOPATH` directory. - -Here is an example project structure that you can use for the NDK agent development: - -``` -. # Root of a project -├── app # Contains agent core logic -├── yang # A directory with agent YANG modules -├── agent.yml # Agent yml config file -├── .goreleaser.yml # Goreleaser config file -├── main.go # Package main that calls agent logic -├── go.mod # Go mod file -├── go.sum # Go sum file -``` - -## NDK language bindings -As explained in the [NDK Architecture](../architecture.md) section, NDK is a gRPC based service. To be able to use gRPC services in a Go program the [language bindings](https://grpc.io/docs/languages/go/quickstart/) have to be generated from the source proto files. - -Nokia not only provides the [proto files](https://github.com/nokia/srlinux-ndk-protobufs) for the SR Linux NDK service but also [NDK Go language bindings](https://github.com/nokia/srlinux-ndk-go). - -With the provided Go bindings, the NDK can be imported in a Go project like that: - -```go -import "github.com/nokia/srlinux-ndk-go/ndk" -``` diff --git a/docs/ndk/guide/operations.md b/docs/ndk/guide/operations.md new file mode 100644 index 00000000..c23e2dba --- /dev/null +++ b/docs/ndk/guide/operations.md @@ -0,0 +1,179 @@ +--- +comments: true +--- + + +# NDK Operations + +When NDK application is [installed](./agent-install-and-ops.md) on SR Linux it interfaces with the NDK service via gRPC. Regardless of programming language the agent is written in, every application will perform the following basic NDK operations (as shown in Fig. 1): + +1. Establish gRPC channel with NDK manager and instantiate an NDK client +2. Register the agent with the NDK manager +3. Register notification streams for different types of NDK services (config, lldp, interface, etc.) +4. Start streaming notifications +5. Handle the streamed notifications +6. Perform some work based on the received notifications +7. Update agent's state data if required +8. Exit gracefully by unregistering the agent + +
+
+
Fig 1. NDK operations flow
+
+ +To better understand the steps an agent undergoes, we will explain them in a language-neutral manner. For language-specific implementations, read the "Developing with NDK" chapter. + +## Creating NDK Manager Client + +NDK agents communicate with gRPC-based NDK service by means of remote procedure calls (RPC). An RPC generally takes in a client request message and returns a response message from the server. + +First, a gRPC channel must be established with the NDK manager application running on SR Linux[^10]. By default, NDK server listens for connections on a unix socket `unix:///opt/srlinux/var/run/sr_sdk_service_manager:50053`[^20] and doesn't require any authentication. NDK app is expected to connect to this socket to establish gRPC. + +```mermaid +sequenceDiagram + participant N as NDK app + participant S as NDK Manager + + N->>S: Open gRPC channel + Note over N,S: gRPC channel established + create participant MC as NDK Manager Client + N-->>MC: Create NDK Manager Client + activate MC + Note over MC: NDK Manager Client
interacts with SdkMgrService +``` + +Once the gRPC channel is set up, a gRPC client (often called _the stub_) needs to be created to perform RPCs. In gRPC, each service requires its own client and in NDK the [`SdkMgrService`][sdk_mgr_svc_doc] service is the first service that agents interact with. +Therefore, users first need to create the NDK Manager Client (_Mgr Client_ in Fig. 1) that will be able to call RPCs of [`SdkMgrService`][sdk_mgr_svc_doc]. + +/// tip +In the proto files and the generated NDK documentation the NDK services have `Sdk` in their name. While in fact NDK is a fancy name for an SDK, we would like to call the client of the `SdkMgrService` the NDK Manager Client. +/// + +## Agent registration + +With the gRPC channel set up and the NDK Manager Client created, we can start using the NDK service. The first mandatory step is the agent registration with the NDK Manager. At this step NDK initializes the state of our agent, creates the IDB tables and assigns an ID to our application. + +```mermaid +sequenceDiagram + participant NMC as NDK Manager Client + participant SDK as SDK Manager Service + NMC->>SDK: AgentRegister + SDK-->>NMC: AgentRegistrationResponse +``` + +The registration process is carried out by calling [`AgentRegister`](https://github.com/nokia/srlinux-ndk-protobufs/blob/protos/ndk/sdk_service.proto#L32) RPC of the [`SdkMgrService`][sdk_mgr_svc_doc]. A [`AgentRegistrationResponse`][agent_reg_resp_doc] is returned (omitted in Fig. 1) with the status of the registration process and application ID assigned to the app by the NDK. + +## Subscribing to notifications + +Remember we said that NDK Agents can interact with other native SR Linux apps? The interaction is done by subscribing to notifications from other SR Linux applications with NDK Manager acting like a gateway between your application and the messaging bus that all SR Linux applications communicate over. + +For example, an NDK app can get information from Network Instance, Config, LLDP, BFD and other applications by requesting subscription to notification updates from these applications: + + +```mermaid +sequenceDiagram + participant App as NDK App + participant NDK as NDK Service + participant IDB as Messaging bus
IDB + participant LLDP as LLDP Manager + App->>NDK: I want to receive LLDP notifications + NDK->>IDB: Subscribing to LLDP notifications + LLDP-->>IDB: LLDP event + IDB-->>NDK: LLDP event + NDK-->>App: LLDP event +``` + + +Let's have a closer look at what it takes to subscribe to notifications from other SR Linux applications. + +### Creating notification stream + +Prior to subscribing to any application's notifications a subscription stream needs to be created. A client of [`SdkMgrService`][sdk_mgr_svc_doc] calls `NotificationRegister` RPC providing [`NotificationRegistrationRequest`][notif_reg_req_doc] message with the `op` field set to `Create` and other fields absent. + +/// details | Other values of Registration Request operations field +`NotificationRegistrationRequest` message's field `op` (short for "operation") may have one of the following values: + +- `Create` creates a subscription stream and returns a `StreamId` that is used when adding subscriptions with the `AddSubscription` operation. +- `Delete` deletes the existing subscription stream that has a particular `SubId`. +- `AddSubscription` adds a subscription. The stream will now be able to stream notifications of that subscription type (e.g., Intf, NwInst, etc). +- `DeleteSubscription` deletes the previously added subscription. +/// + +NDK Manager responds with [`NotificationRegisterResponse`][notif_reg_resp_doc] message with the allocated `stream_id` value. The stream has been created, and the subscriptions can now be added to the created stream. + +### Adding subscriptions + +With subscription stream allocated we can proceed with adding one or more subscriptions to it. [`SdkMgrService`][sdk_mgr_svc_doc] service offers `NotificationRegister` RPC to add subscriptions to the stream. The RPC uses the same [`NotificationRegistrationRequest`][notif_reg_req_doc] message (step 4 in Fig. 1) as we used to create the stream, but now with the following fields set: + +- `stream_id` set to an obtained value from the previous step +- `op` is set to `AddSubscription` +- one of the supported [`subscription_types`][sub_types] according to the desired service notifications. For example, if we are interested to receive [`Config`][cfg_svc_doc] notifications, then `config` field of type [`ConfigSubscriptionRequest`][cfg_sub_req_doc] is set. + +[`NotificationRegisterResponse`][notif_reg_resp_doc] message (step 5 in Fig. 1) is returned and contains the same `stream_id` value and now also the `sub_id` value - a subscription identifier. At this point NDK application indicated its intention to receive notifications from certain services, but the notification streams haven't been started yet. + +## Streaming notifications + +Requesting applications to send notifications is done by interacting with [`SdkNotificationService`][sdk_notif_svc_doc]. As this is another gRPC service, it requires its own client - Notification client. Steps 6 and 7 in Fig. 1 show the interaction between the Notification Client and the Notification service. + +To initiate streaming of the notifications requested in the previous step the Notification Client calls [`NotificationStream`][sdk_notif_svc_doc] RPC with [`NotificationStreamRequest`][notif_stream_req_doc] message with `stream_id` field set to the ID of a stream to be used. This RPC returns a **stream** of [`NotificationStreamResponse`][notif_stream_resp_doc], which makes this RPC to be classified as "server streaming RPC". + +/// details | Server-streaming RPC +A [server-streaming RPC](https://grpc.io/docs/what-is-grpc/core-concepts/#server-streaming-rpc) is similar to a unary RPC, except that the server returns a stream of messages in response to a client's request. +After sending all its messages, the server's status details (status code and optional status message) and optional trailing metadata are sent to the client. This completes processing on the server side. The client completes once it has all the server's messages. +/// + +The Notification client starts to receive a stream of `NotificationStreamResponse` messages where each message contains one or more [`Notification`][notif_doc] message. The [`Notification`][notif_doc] message itself will contain a field with one of the [`subscription_types`][sub_types] notifications, which will be set in accordance to the type of the notification requested on this stream in the [Adding subscriptions](#adding-subscriptions) step. + +In our example, we sent `ConfigSubscriptionRequest` inside the `NotificationRegisterRequest`, hence the notifications that we will get back for that `stream_id` will contain [`ConfigNotification`][cfg_notif_doc] messages inside `Notification` of a `NotificationStreamResponse`. + +## Handling notifications + +Now we reached the point where our custom Application can start doing actual work. The application receives a stream of notifications from the NDK Manager based on the subscriptions it requested. +The application needs to handle the notifications and perform some work based on the received data. + +For example, if the application requested to receive `Config` notifications, it will receive a stream of [`ConfigNotification`][cfg_notif_doc] messages containing the configuration changes that happened on the SR Linux system for this application's configuration. The application then can parse the received data and perform some work based on it. + +The Server streaming RPC will provide notifications till the last available one and block awaiting more notifications to send; the application then reads out the incoming notifications and handles the messages contained within them. Some notification messages have `SyncStart` and `SyncEnd` messages that indicate the start and end of a stream of notifications. The application can use these messages to synchronize its state with the SR Linux system. +For example, when streaming out routes from the RIB table, the application can use `SyncStart` and `SyncEnd` messages to know when the stream of routes is complete and can start processing the received routes. + +## Handling application's configuration and state + +As any other "regular" application, NDK applications can have their own configuration and state modelled with YANG and injected in the global config/state of the SR Linux NOS. + +When NDK developer defines the YANG model for the application, they can model configuration and state data that will be accessible via CLI and all other management interfaces. The configuration data can be edited by a user via common management interfaces and delivered to the application via NDK. This workflow enables configuration management of the application via the same management interfaces as the rest of the SR Linux system. + +Once configured, the application may need to update its state data based on the received notifications and the work it carried out. The state of the application becomes part of the SR Linux state datastore and is accessible via all management interfaces. + +
+
+
Fig 2. Updating agent's state flow
+
+ +Updating or creating agent's state is done with [`TelemetryAddOrUpdate`][sdk_mgr_telem_svc_doc] RPC that uses [`TelemetryUpdateRequest`][telem_upd_req_doc] message with a list of [`TelemetryInfo`][telem_info_doc] messages. Each `TelemetryInfo` message contains a `key` field that points to a subtree of agent's YANG model that needs to be updated with the JSON data contained within the `data` field. + +## Exiting gracefully + +When an agent needs to stop its operation and/or be removed from the SR Linux system, it needs to unregister by invoking `AgentUnRegister` RPC of the [`SdkMgrService`][sdk_mgr_svc_doc]. The gRPC connection to the NDK server needs to be closed. + +When unregistered, the agent's state data will be removed from SR Linux system and will no longer be accessible to any of the management interfaces. + +[sdk_mgr_svc_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.SdkMgrService +[sdk_mgr_svc_proto]: https://github.com/nokia/srlinux-ndk-protobufs/blob/protos/ndk/sdk_service.proto +[sdk_notif_svc_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.SdkNotificationService +[sdk_mgr_telemetry_proto]: https://github.com/nokia/srlinux-ndk-protobufs/blob/protos/ndk/telemetry_service.proto +[notif_reg_req_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.NotificationRegisterRequest +[notif_reg_resp_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.NotificationRegisterResponse +[cfg_svc_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#ndk%2fconfig_service.proto +[cfg_notif_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.ConfigNotification +[cfg_sub_req_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.ConfigSubscriptionRequest +[notif_stream_req_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.NotificationStreamRequest +[notif_stream_resp_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.NotificationStreamResponse +[notif_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.Notification +[sdk_mgr_telem_svc_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.SdkMgrTelemetryService +[telem_upd_req_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.TelemetryUpdateRequest +[telem_info_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.TelemetryInfo +[agent_reg_resp_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#srlinux.sdk.AgentRegistrationResponse +[sub_types]: https://github.com/nokia/srlinux-ndk-protobufs/blob/protos/ndk/sdk_service.proto#L125 + +[^10]: `sdk_mgr` is the name of the application that implements NDK gRPC server and runs on SR Linux OS. +[^20]: The server is also available on a TCP socket `localhost:50053`. diff --git a/docs/ndk/index.md b/docs/ndk/index.md index a67b01f4..49f92d87 100644 --- a/docs/ndk/index.md +++ b/docs/ndk/index.md @@ -1,32 +1,43 @@ --- tags: - ndk +hide: + - toc --- -# NetOps Development Kit +# NetOps Development Kit (NDK) -Nokia SR Linux enables its users to create high-performance applications which run alongside native apps on SR Linux Network OS. These "on-box custom applications" can be deeply integrated with the rest of the SR Linux system and thus can perform tasks that are not possible with traditional management interfaces standard for the typical network operating systems. +Nokia SR Linux enables its users to create high-performance applications that run alongside native apps on SR Linux Network OS. These "on-box custom applications" can deeply integrate with the rest of the SR Linux system and therefore can perform tasks that are not feasible to perform with traditional out-of-the-box automation done via management interfaces.
- ![arch](https://gitlab.com/rdodin/pics/-/wikis/uploads/6beed5e008a32cffaeca2f6f811137b2/image.png){ width="640" } + ![arch](https://gitlab.com/rdodin/pics/-/wikis/uploads/6beed5e008a32cffaeca2f6f811137b2/image.png){.img-shadow width="640" }
Custom applications run natively on SR Linux NOS
-The on-box applications (which we also refer to as "agents") leverage the SR Linux SDK called **NetOps Development Kit** or NDK for short. +The on-box applications (which we also refer to as "agents") leverage the SR Linux software development kit called **NetOps Development Kit** or NDK for short. Applications developed with SR Linux NDK have a set of unique characteristics which set them aside from the traditional off-box automation solutions: 1. **Native integration with SR Linux system** - SR Linux architecture is built so that NDK agents look and feel like any other regular application such as bgp or acl. This seamless integration is achieved on several levels: - 1. System integration: when deployed on SR Linux system, an NDK agent renders itself like any other "standard" application. That makes lifecycle management unified between Nokia-provided system apps and custom agents. - 2. CLI integration: every NDK agent automatically becomes a part of the global CLI tree, making it possible to configure the agent and query its state the same way as for any other configuration region. - 3. Telemetry integration: an NDK agent configuration and state data will automatically become available for Streaming Telemetry consumption. + SR Linux architecture is built in a way that let NDK agents look and feel like any other regular application such as BGP or ACL. This seamless integration is achieved on several levels: + 1. **System** integration: when deployed on SR Linux system, an NDK agent renders itself like any other "standard" application. That makes lifecycle management unified between Nokia-provided system apps and custom agents. + 2. **Management** integration: each NDK app configuration and state model automatically becomes a part of the global SR Linux management tree, making it possible to configure the agent and query its state the same way as for any other configuration region. + 3. **Telemetry** integration: an NDK agent configuration and state data will automatically become available for Streaming Telemetry consumption. 2. **Programming language-neutral** - With SR Linux NDK, the developers are not forced to use any particular language when writing their apps. As NDK is a gRPC service defined with Protocol Buffers, it is possible to use any[^1] programming language for which protobuf compiler is available. + With SR Linux NDK, the developers are not forced to use any particular language when writing their apps. As NDK is based on gRPC, it is possible to use any[^1] programming language that supports protobuf. 3. **Deep integration with system components** NDK apps are not constrained to only configuration and state management, as often happens with traditional north-bound interfaces. On the contrary, the NDK service exposes additional services that enable deep integration with the SR Linux system, such as listening to RIB/FIB updates or having direct access to the datapath. -With the information outlined in the [NDK Developers Guide](guide/architecture.md), you will learn about NDK architecture and how to develop apps with this kit. +Developers are welcomed to dig into the [NDK Developers Guide](guide/architecture.md) to learn all about NDK architecture and how to develop apps with this kit. -Please navigate to the [Apps Catalog](apps/index.md) to browse our growing list of NDK apps that Nokia or 3rd parties wrote. +Browse our [Apps Catalog](apps/index.md) with a growing list of NDK apps that Nokia or 3rd parties published. + +## NDK artifacts + +A list of links to various NDK artifacts: + +* NDK Proto files: [`nokia/srlinux-ndk-protobufs`](https://github.com/nokia/srlinux-ndk-protobufs) +* [Generated NDK Service documentation](https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html) +* Go bindings for NDK: [`nokia/srlinux-ndk-go`](https://github.com/nokia/srlinux-ndk-go) +* Python bindings for NDK: [`nokia/srlinux-ndk-py`](https://github.com/nokia/srlinux-ndk-py) [^1]: This in practice covers all popular programming languages: Python, Go, C#, C, C++, Java, JS, etc. diff --git a/docs/ndk/releases/0.2.md b/docs/ndk/releases/0.2.md new file mode 100644 index 00000000..21cda848 --- /dev/null +++ b/docs/ndk/releases/0.2.md @@ -0,0 +1,4 @@ +# NDK v0.2.0 Release Notes + +* Introduced with SR Linux `v23.10.1`. +* Diff with the previous release: [`v0.1.1...v0.2.0`](https://github.com/nokia/srlinux-ndk-protobufs/compare/v0.1.1...v0.2.0) diff --git a/docs/ndk/releases/index.md b/docs/ndk/releases/index.md new file mode 100644 index 00000000..9f12cb6e --- /dev/null +++ b/docs/ndk/releases/index.md @@ -0,0 +1,20 @@ +# NDK Releases + +NDK release cycle does not follow the SR Linux release cycle. NDK releases are published independently due to the fact that not every SR Linux release contains NDK updates. +For the same reason the NDK versioning scheme is different from the SR Linux's one and uses [Semantic Versioning](https://semver.org/). + +At the same time, new NDK release appear together with a certain SR Linux release where NDK updates were made. + +/// note | Semantic Versioning and Non Backwards Compatible Changes +Semantic Versioning imposes certain rules on how to version software releases. The most important one is that a new major version release (e.g. 2.0.0) may contain non backwards compatible changes. + +Since NDK is versioned with `v0` at the moment of this writing, we are not bound by this rule and may introduce non backwards compatible changes in any release. The NBC changes would be mentioned in the release notes. +/// + +The following table shows the mapping between SR Linux and NDK releases: + +| NDK Release | SR Linux Release[^10] | Comments | +| ---------------- | ---------------- | -------- | +| [v0.2.0](0.2.md) | 23.10.1 | | + +[^10]: SR Linux release where NDK changes were introduced. diff --git a/docs/stylesheets/nokia.css b/docs/stylesheets/nokia.css index f81d2561..fdb24ff8 100644 --- a/docs/stylesheets/nokia.css +++ b/docs/stylesheets/nokia.css @@ -71,8 +71,8 @@ h4 { background-color: #124191; } -th { - color: #FFFFFF !important; +.md-typeset__table th { + color: #FFFFFF; } /* Pumping heart */ @@ -232,4 +232,9 @@ div.highlight.code-scroll-lg pre>code { div.highlight.code-scroll-sm pre>code { max-height: 50vh; +} + +/* custom twemoji sizes */ +.twemoji-md { + --md-icon-size: 2em !important; } \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index b18d9e46..0f6405e9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -37,14 +37,24 @@ nav: - ndk/index.md - Developers Guide: - NDK Architecture: ndk/guide/architecture.md - - Agent Structure: ndk/guide/agent.md - - Agent Installation & Operations: ndk/guide/agent-install-and-ops.md - - Environment Setup: - - Go: ndk/guide/env/go.md - - Python: ndk/guide/env/python.md + - NDK Operations: ndk/guide/operations.md + - Agent Components: ndk/guide/agent.md - Developing with NDK: - - Go: ndk/guide/dev/go.md - - Python: ndk/guide/dev/python.md + - Go: + - ndk/guide/dev/go/index.md + - Main function: ndk/guide/dev/go/main.md + - App Instance: ndk/guide/dev/go/app-instance.md + - App Start: ndk/guide/dev/go/app-start.md + - Receiving Configuration: ndk/guide/dev/go/receiving-config.md + - Notification Stream: ndk/guide/dev/go/notif-stream.md + - Handling Notifications: ndk/guide/dev/go/handling-notif.md + - Processing Config: ndk/guide/dev/go/processing-config.md + - Updating State: ndk/guide/dev/go/updating-state.md + - Logging: ndk/guide/dev/go/logging.md + - Python: + - ndk/guide/dev/py/index.md + - Greeter App: ndk/guide/dev/py/greeter-py.md + - Agent Installation & Operations: ndk/guide/agent-install-and-ops.md - App Catalog: - ndk/apps/index.md - EVPN Proxy: ndk/apps/evpn-proxy.md @@ -52,6 +62,9 @@ nav: - Prometheus Telemetry Exporter: ndk/apps/srl-prom-exporter.md - SR Linux GPT: ndk/apps/srl-gpt.md - Satellite Tracker: ndk/apps/satellite.md + - Releases: + - ndk/releases/index.md + - 0.2: ndk/releases/0.2.md - Ansible: - ansible/index.md - nokia.srlinux collection: @@ -188,6 +201,7 @@ plugins: archive: false categories: false post_excerpt_max_authors: 2 + authors_profiles: true - tags: tags_file: blog/tags.md # media.md is a file that only shows posts with `media` tag @@ -258,8 +272,8 @@ markdown_extensions: - pymdownx.critic - pymdownx.details - pymdownx.emoji: - emoji_index: !!python/name:materialx.emoji.twemoji - emoji_generator: !!python/name:materialx.emoji.to_svg + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg - pymdownx.highlight: line_spans: __span anchor_linenums: true