Skip to content

Commit

Permalink
added more stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
hellt committed Dec 1, 2023
1 parent 34bce6b commit 4b56a81
Show file tree
Hide file tree
Showing 8 changed files with 477 additions and 83 deletions.
155 changes: 155 additions & 0 deletions docs/ndk/guide/dev/go/app-instance.md
Original file line number Diff line number Diff line change
@@ -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
137 changes: 116 additions & 21 deletions docs/ndk/guide/dev/go/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

<div class="iframe-container">
<iframe width="100%" src="https://www.youtube.com/embed/CmYML_ttCjA" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>

## 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 <ENTER>) 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:

<!-- --8<-- [start:prj-struct] -->
```bash
❯ tree
.
Expand Down Expand Up @@ -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.
<!-- --8<-- [end:prj-struct] -->
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
3 changes: 3 additions & 0 deletions docs/ndk/guide/dev/go/logging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Logging

Under construction.
Loading

0 comments on commit 4b56a81

Please sign in to comment.