From 4b56a812b7e485a0625c5b144a24a06f78e8bbaa Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Fri, 1 Dec 2023 15:39:20 +0200 Subject: [PATCH] added more stuff --- docs/ndk/guide/dev/go/app-instance.md | 155 ++++++++++++++++++ docs/ndk/guide/dev/go/index.md | 137 +++++++++++++--- docs/ndk/guide/dev/go/logging.md | 3 + docs/ndk/guide/dev/go/main.md | 116 +++++++++++++ docs/ndk/guide/dev/go/notif-stream.md | 80 +++++++++ .../guide/dev/go/{greeter-go.md => temp.md} | 60 +------ docs/stylesheets/nokia.css | 4 +- mkdocs.yml | 5 +- 8 files changed, 477 insertions(+), 83 deletions(-) create mode 100644 docs/ndk/guide/dev/go/app-instance.md create mode 100644 docs/ndk/guide/dev/go/logging.md create mode 100644 docs/ndk/guide/dev/go/main.md create mode 100644 docs/ndk/guide/dev/go/notif-stream.md rename docs/ndk/guide/dev/go/{greeter-go.md => temp.md} (80%) 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..5d1f08fb --- /dev/null +++ b/docs/ndk/guide/dev/go/app-instance.md @@ -0,0 +1,155 @@ +# 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<-- "http://172.17.0.1:49080/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<-- "http://172.17.0.1:49080/greeter/app.go:pkg-greeter" +--8<-- "http://172.17.0.1:49080/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<-- "http://172.17.0.1:49080/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<-- "http://172.17.0.1:49080/greeter/app.go:pkg-greeter" +--8<-- "http://172.17.0.1:49080/greeter/app.go:pkg-greeter-const" +--8<-- "http://172.17.0.1:49080/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 the application 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<-- "http://172.17.0.1:49080/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 +👋 Hello ${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<-- "http://172.17.0.1:49080/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<-- "http://172.17.0.1:49080/greeter/app.go:pkg-greeter-const" +--8<-- "http://172.17.0.1:49080/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<-- "http://172.17.0.1:49080/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` and the computed `greeting` values. Here is how our `ConfigState` struct looks: + +```{.go title="greeter/config.go"} +--8<-- "http://172.17.0.1:49080/greeter/config.go:configstate-struct" +``` + +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<-- "http://172.17.0.1:49080/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<-- "http://172.17.0.1:49080/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/index.md b/docs/ndk/guide/dev/go/index.md index 1e179ae0..dba99792 100644 --- a/docs/ndk/guide/dev/go/index.md +++ b/docs/ndk/guide/dev/go/index.md @@ -14,25 +14,125 @@ Although every developer's environment is different and is subject to a personal 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 language bindings for the gRPC-based NDK service. -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). +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). -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). NDK bindings and Goreleaser can be installed later when we reach a point where we need them. +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). -Clone the [`srl-labs/ndk-greeter-go`][greeter-go-repo] project and let's get started! + 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 +`👋 Hello ${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 ``` -### Project structure +/// 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. +/// + +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 "👋 Hello 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 . @@ -67,27 +167,22 @@ This is the project structure used in this tutorial: 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. -### NDK language bindings +## Application Configuration -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). +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. -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. +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: -With the provided Go bindings, users don't need to generate them themselves. To leverage the bindings, users need to import the `ndk` package in their Go program. - -```go -import "github.com/nokia/srlinux-ndk-go/ndk" +```yaml +--8<-- "https://raw.githubusercontent.com/srl-labs/ndk-greeter-go/main/greeter.yml:snip" ``` -We will see how to use the `ndk` package in the following sections. - -### Lab environment - -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. +Refer to the [application configuration][app-config] section covered previously to understand better what each field means. Here it is worth mentioning that the 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..0b494522 --- /dev/null +++ b/docs/ndk/guide/dev/go/logging.md @@ -0,0 +1,3 @@ +# Logging + +Under construction. diff --git a/docs/ndk/guide/dev/go/main.md b/docs/ndk/guide/dev/go/main.md new file mode 100644 index 00000000..a43275d4 --- /dev/null +++ b/docs/ndk/guide/dev/go/main.md @@ -0,0 +1,116 @@ +# 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<-- "http://172.17.0.1:49080/main.go:pkg-main" +--8<-- "http://172.17.0.1:49080/main.go:pkg-main-vars" +--8<-- "http://172.17.0.1:49080/main.go:main" +``` + +## Application version + +As you can see, the `main` function is rather simple. First, we [handle the `version`](#__codelineno-0-9:16) 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) 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<-- "http://172.17.0.1:49080/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`](#__codelineno-8-23) function passing it the cancel function of the context: + +```go linenums="1" +--8<-- "http://172.17.0.1:49080/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 to it. + +```go linenums="1" hl_lines="19-21" title="greeter/app.go" +--8<-- "http://172.17.0.1:49080/greeter/app.go:app-start" +``` + +We will make a proper stop at `func (a *App) Start()` function 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<-- "http://172.17.0.1:49080/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<-- "http://172.17.0.1:49080/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 + +[^10]: We use [`./run.sh` runner script][runsh] that is a sane alternative to Makefile. Functions in this file perform actions like building the app, destroying the lab, etc. t with memory safety, garbage collection, structural typing, and CSP-style concurrency. 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..aae9649b --- /dev/null +++ b/docs/ndk/guide/dev/go/notif-stream.md @@ -0,0 +1,80 @@ +# Notification Stream + +Recall that our program's entrypoint finishes 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<-- "http://172.17.0.1:49080/greeter/app.go:app-start" +``` + +Largely, the `Start` function can be divided in three parts: + +1. Start the Configuration Notification Stream +2. Process the Configuration Notification Stream responses +3. Stop the application when the context is cancelled + +The application exit procedure has been covered in the [Exit Handler](main.md#exit-handler) section so here we will focus on the first two parts. + +## Configuration Notification Stream + +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. + +And you know what, our greeter app is no different, it needs to receive notificatons from the NDK about, but it only needs to receive a particular notification type - its own configuration updates. +Whenever we configure the `/greeter/name` leaf and commit this configuration, our app needs to receive this update and act on it. + +Since our app logic is super simple, all the greeter needs to do is + +1. to take the configured `name` value +2. query SR Linux state to retrieve the last booted time +3. create the `greeting` message with the two values above + +So it all starts with our app requesting the NDK to stream its own configuration updates back. And this is exactly what happens in `a.StartConfigNotificationStream(ctx)`. Let's zoom in. + +```{.go title="greeter/notification.go"} +--8<-- "http://172.17.0.1:49080/greeter/notification.go:start-cfg-notif-stream" +``` + +Let's again break down the function into smaller pieces. + +## Creating Notification Stream + +First, on [ln 2](#__codelineno-1-2), we create a notification stream as explained in the [Creating Notification Stream][operations-create-notif-stream] section. + +```{.go title="greeter/notification.go"} +--8<-- "http://172.17.0.1:49080/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. + +## Adding Config Subscription + +With the notification stream created, we can now request the NDK to deliver the configuration updates to our app. This is done by crafting a [`NotificationRegisterRequest`][notif_reg_req_doc] on [lines 8-16](#__codelineno-1-8:16). + +Note that we use `streamID` we received after creating the notification stream to specify the stream we want to receive the notifications on. + +We also set the `SubscriptionTypes` to the `&ndk.NotificationRegisterRequest_Config` value to indicate that we would like to subscribe to the configuration updates. + +```{.go title="greeter/notification.go, createNotificationStream func"} +SubscriptionTypes: &ndk.NotificationRegisterRequest_Config{ // config service + Config: &ndk.ConfigSubscriptionRequest{}, + }, +``` + +We pass the empty [`ConfigSubscriptionRequest`][cfg_sub_req_doc] request since we don't want to apply any filtering on the notifications we receive. + +## Starting Notification Stream + +With notification Stream ID allocated and [`NotificationRegisterRequest`][notif_reg_req_doc] for `Config` messages created, we can now start the notification stream. + +And here is where Go channels come really handy because we can use them to deliver the notifications to our app. + +On [lines 18-21](#__codelineno-1-18:21) we create a channel of type [`NotificationStreamResponse`][notif_stream_resp_doc], because this is the type of the messages the NDK will send us, and we pass it to the `StartNotificationStream` function that is started in its own goroutine. + +```{.go title="greeter/notification.go"} +--8<-- "http://172.17.0.1:49080/greeter/notification.go:start-notif-stream" +``` + +[operations-subscr-to-notif]: ../../operations.md#subscribing-to-notifications +[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 diff --git a/docs/ndk/guide/dev/go/greeter-go.md b/docs/ndk/guide/dev/go/temp.md similarity index 80% rename from docs/ndk/guide/dev/go/greeter-go.md rename to docs/ndk/guide/dev/go/temp.md index d6555627..5936d3ed 100644 --- a/docs/ndk/guide/dev/go/greeter-go.md +++ b/docs/ndk/guide/dev/go/temp.md @@ -1,53 +1,3 @@ -# Greeter NDK Application in Go - -This code walkthrough tutorial explains how developers consume the NDK service to develop NDK agents with Go[^1] programming language. Our goal is to provide a step-by-step guide on how to develop a simple NDK agent in Go which introduces the basic concepts of the NDK service and proposes a project structure that can be used as a starting point for developing more complex NDK agents. - -## 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 -`👋 Hello ${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 `greeter` - -Before taking a deep dive into the code, let's deploy the `greeter` app to SR Linux using containerlab and see how it works. 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.5 version is used in this tutorial. -/// - -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 -``` - ## Establish gRPC channel with NDK manager and instantiate an NDK client [:octicons-question-24: Additional information](../architecture.md#grpc-channel-and-ndk-manager-client) @@ -263,12 +213,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.). -[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. - -[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 -[greeter-go-repo]: https://github.com/srl-labs/ndk-greeter-go -[cfg_svc_doc]: https://rawcdn.githack.com/nokia/srlinux-ndk-protobufs/v0.2.0/doc/index.html#ndk%2fconfig_service.proto - -[^10]: We use [`./run.sh` runner script](https://github.com/srl-labs/ndk-greeter-go/blob/main/run.sh) that is a sane alternative to Makefile. Functions in this file perform actions like building the app, destroying the lab, etc. t with memory safety, garbage collection, structural typing, and CSP-style concurrency. +[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. \ No newline at end of file diff --git a/docs/stylesheets/nokia.css b/docs/stylesheets/nokia.css index 838c8b50..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 */ diff --git a/mkdocs.yml b/mkdocs.yml index a1590947..1af01b3e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -42,7 +42,10 @@ nav: - Developing with NDK: - Go: - ndk/guide/dev/go/index.md - - Greeter App: ndk/guide/dev/go/greeter-go.md + - Main function: ndk/guide/dev/go/main.md + - App Instance: ndk/guide/dev/go/app-instance.md + - Notification Stream: ndk/guide/dev/go/notif-stream.md + - Logging: ndk/guide/dev/go/logging.md - Python: - ndk/guide/dev/py/index.md - Greeter App: ndk/guide/dev/py/greeter-py.md