Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

srlinux license provisioning via secrets #12

Merged
merged 18 commits into from
Jul 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/workflows/linters/.golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,20 @@ linters:
- goerr113
- nestif
- prealloc
- testpackage
- wsl
linters-settings:
lll:
line-length: 140

issues:
# https://github.com/golangci/golangci-lint/issues/2439#issuecomment-1002912465
exclude-use-default: false
# exclude-use-default: false
exclude-rules:
- path: _test\.go
linters:
- gomnd
- dupl
- structcheck
- unused
- unparam
# - gomnd
# - dupl
# - structcheck
# - unused
# - unparam
- funlen
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# Binaries for programs and plugins
*.exe
*.exe~
Expand All @@ -24,3 +23,5 @@ testbin/*
*.swo
*~
.vscode

private
5 changes: 4 additions & 1 deletion .mk/lint.mk
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ GODOT_FLAGS := -w .
GOLINES_CMD := docker run --rm -it -v $(shell pwd):/work ghcr.io/hellt/golines:0.10.0 golines
GOLINES_FLAGS := -w .

GOLANGCI_CMD := docker run -it --rm -v $$(pwd):/app -w /app golangci/golangci-lint:v1.47.2 golangci-lint
GOLANGCI_CMD := docker run -it --rm -v $(shell pwd):/app -w /app golangci/golangci-lint:v1.47.2 golangci-lint
GOLANGCI_FLAGS := --config ./.github/workflows/linters/.golangci.yml run -v --fix


# when running in a CI env we use locally installed bind
ifdef CI
GOFUMPT_CMD := gofumpt
GODOT_CMD := godot
GOLINES_CMD := golines
GOLANGCI_CMD := golangci-lint
endif


Expand Down
82 changes: 43 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# SR Linux Controller for KNE

This is a k8s controller for running and managing SR Linux nodes launched from [openconfig/kne](https://github.com/openconfig/kne) topology.

## Install

To install the latest version of this controller on a cluster referenced in `~/.kube/config` issue the following command:

```bash
# latest version
kubectl apply -k https://github.com/srl-labs/srl-controller/config/default
Expand All @@ -11,7 +15,8 @@ kubectl apply -k https://github.com/srl-labs/srl-controller/config/default?ref=v
```

The resources of this controller will be scoped under `srlinux-controller` namespace.
```

```text
❯ kubectl get all -n srlinux-controller

NAME READY STATUS RESTARTS AGE
Expand All @@ -28,91 +33,90 @@ replicaset.apps/srlinux-controller-controller-manager-c7495dcc7 1 1
```

### Installing from a repo

The controller can be installed with make directly from the repo:
```

```text
make deploy IMG=ghcr.io/srl-labs/srl-controller:0.3.1
```

Make sure to check which controller versions are [available](https://github.com/srl-labs/srl-controller/pkgs/container/srl-controller/versions)
Make sure to check which controller versions are [available](https://github.com/srl-labs/srl-controller/pkgs/container/srl-controller/versions).

## Uninstall

To uninstall the controller from the cluster:
```

```text
kubectl delete -k https://github.com/srl-labs/srl-controller/config/default
```

## Testing with `kind`
To run this controller in a test cluster deployed with [`kind`](https://kind.sigs.k8s.io/) follow the steps outlined below.

1. Install `kind`
2. Clone and enter into [openconfig/kne](https://github.com/openconfig/kne) repo.
3. Build the kne cli with
`cd kne_cli && go build -o kne && chmod +x ./kne && mv ./kne /usr/local/bin`
4. deploy kind cluster and the necessary CNI with `kne deploy deploy/kne/kind.yaml` where the path to `kind.yaml` is a relative path from the root of the kne repo.
## Testing with `kne` & `kind`

This will install the `kind` cluster named `kne` with [`meshnet-cni`](https://github.com/networkop/meshnet-cni) and [`metallb`](https://metallb.universe.tf/).

A quick test may be performed to verify that SR Linux pods can communicate over the newly deployed cluster:

```bash
# apply this manifest https://gist.github.com/hellt/43cfade6178be32ea7dfa5cb64715822
# which has two srlinux pods and two linux pods
kubectl apply -f https://gist.githubusercontent.com/hellt/43cfade6178be32ea7dfa5cb64715822/raw/847a10a57dca996432be7c4a9743c0e0c5b75814/srl.yml

# once the pods are deployed and running, verify that srlinux pods can reach each other with ssh
# check what IP the srl2 has
kubectl exec -it srl1 -- ip netns exec srbase-mgmt ssh admin@10.244.0.7
admin@10.244.0.7's password:
Last login: Fri Sep 24 13:58:05 2021 from 10.244.0.6
Using configuration file(s): ['/etc/opt/srlinux/srlinux.rc']
Welcome to the srlinux CLI.
Type 'help' (and press <ENTER>) if you need any help using this.
--{ [FACTORY] running }--[ ]--
```
To run this controller in a test cluster deployed with [`kne`](https://github.com/openconfig/kne) and [`kind`](https://kind.sigs.k8s.io/) follow the steps outlined in the [KNE repository](https://github.com/openconfig/kne/tree/main/docs).

If all works as expected a [demo topology with three SR Linux nodes](https://github.com/openconfig/kne/blob/main/examples/3node-srl.pb.txt) may be deployed as follows:
Once the kne+kind cluster is created, a [demo topology with two SR Linux nodes](https://github.com/openconfig/kne/blob/db5fe5be01a1b6b65bd79e740e2c819c5aeb50b0/examples/srlinux/2node-srl-with-config.pbtxt) may be deployed as follows:

```bash
kne create ~/kne/examples/3node-srl.pb.txt
kne create examples/srlinux/2node-srl-with-config.pbtxt
```

This will deploy the SR Linux nodes and will create k8s services as per the topology configuration. The services will be exposed via MetalLB and can be queried as:

```
```text
❯ kubectl -n 3node-srlinux get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service-r1 LoadBalancer 10.96.151.84 172.19.0.50 57400:30006/TCP,443:30004/TCP,22:30005/TCP 6m10s
service-r2 LoadBalancer 10.96.34.36 172.19.0.51 443:30010/TCP,22:30011/TCP,57400:30009/TCP 6m9s
service-r3 LoadBalancer 10.96.159.220 172.19.0.52 443:30015/TCP,22:30016/TCP,57400:30014/TCP 6m9s
```

To connect with SSH to the `r1` node, use `ssh admin@172.19.0.50` command.

### Loading images to kind cluster

[Public SR Linux container image](https://github.com/nokia/srlinux-container-image) will be pulled by kind automatically, if Internet access is present. Images which are not available publicy can be uploaded to kind manually:

```bash
# default kne kind cluster name is `kne`
# which is the last argument in the command
kind load docker-image srlinux:0.0.0-38566 --name kne
```

## Using license files

To remove the packets-per-second limit of a public container image or to launch chassis-based variants of SR Linux (ixr-6e/10e) KNE users should provide a valid license file to the `srl-controller`.

Navigate to [Using license files](docs/using-licenses.md) document to have a detailed explanation on that topic.

## Controller operations

The controller is designed to manage the `Srlinux` custom resource defined with [the following CRD](https://doc.crds.dev/github.com/srl-labs/srl-controller).

The request to create/delete a resource of kind `Srlinux` is typically coming from `openconfig/kne` topology.

### Creation
When a request to create an `Srlinux` resource named `r1` in namespace `ns` comes in, the controller's reconcile loop does the following:

1. Checks if the pods exist within a namespace `ns` with a name `r1`
When a request to create a `Srlinux` resource named `r1` in namespace `ns` comes in, the controller's reconcile loop does the following:

1. Checks if the pods exist within a namespace `ns` with a name `r1`
2. If the pod hasn't been found, then the controller first ensures that the necessary config maps exist in namespace `ns` and creates them otherwise.
3. When config maps are sorted out, the controller schedules a pod with the name `r1` and requeue the request
4. In a requeue run, the pod is now found and the controller updates the status of `Srlinux` resource with the image name that was used in the pod spec.

### Deletion

When a deletion happens on `Srlinux` resource, the reconcile loop does nothing.

### API access

This repo contains a clientset for API access to the `Srlinux` custom resource. Check [kne repo](https://github.com/openconfig/kne/blob/fc195a73035bcbf344791979ca3e067be47a249c/topo/node/srl/srl.go#L46) to see how this can be done.

## Building srl-controller container image
## Building `srl-controller` container image

To build `srl-controller` container image execute:

```bash
# don't forget to set the correct tag
make docker-build IMG=ghcr.io/srl-labs/srl-controller:${tag}
```

Then upload the image to the registry.
Then upload the image to the registry and update the controller version in [manager/kustomization.yaml](config/manager/kustomization.yaml).
39 changes: 39 additions & 0 deletions api/types/v1alpha1/srl_version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package v1alpha1

import (
"errors"
"regexp"
"strings"
)

// ErrVersionParse is an error which is raised when srlinux version is failed to parse.
var ErrVersionParse = errors.New("version parsing failed")

// SrlVersion represents an sr linux version as a set of fields.
type SrlVersion struct {
Major string `json:"major,omitempty"`
Minor string `json:"minor,omitempty"`
Patch string `json:"patch,omitempty"`
Build string `json:"build,omitempty"`
Commit string `json:"commit,omitempty"`
}

func parseVersionString(s string) (*SrlVersion, error) {
// for latest or missing tag we consider the version to be an engineering build
// with major = 0
if strings.ToLower(s) == "latest" || s == "" {
return &SrlVersion{"0", "", "", "", ""}, nil
}

// https://regex101.com/r/eWS6Ms/1
re := regexp.MustCompile(
`^v?(?P<major>\d{1,3})\.(?P<minor>\d{1,2})\.?(?P<patch>\d{1,2})?-?(?P<build>\d{1,10})?-?(?P<commit>\S+)?`,
)

v := re.FindStringSubmatch(s)
if v == nil {
return nil, ErrVersionParse
}

return &SrlVersion{v[1], v[2], v[3], v[4], v[5]}, nil
}
98 changes: 98 additions & 0 deletions api/types/v1alpha1/srl_version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package v1alpha1

import (
"errors"
"testing"

"github.com/google/go-cmp/cmp"
)

func TestParseVersionString(t *testing.T) {
tests := []struct {
desc string
got string
want *SrlVersion
err error
}{
{
desc: "maj, minor, patch",
got: "21.6.4",
want: &SrlVersion{"21", "6", "4", "", ""},
},
{
desc: "maj, minor",
got: "21.6",
want: &SrlVersion{"21", "6", "", "", ""},
},
{
desc: "maj, minor and extra string",
got: "21.6-test",
want: &SrlVersion{"21", "6", "", "", "test"},
},
{
desc: "maj, minor, patch and extra string",
got: "21.6.11-test",
want: &SrlVersion{"21", "6", "11", "", "test"},
},
{
desc: "maj, minor, patch, build and extra string",
got: "21.6.11-235-test",
want: &SrlVersion{"21", "6", "11", "235", "test"},
},
{
desc: "maj, minor, patch and build",
got: "21.6.11-235",
want: &SrlVersion{"21", "6", "11", "235", ""},
},
{
desc: "0.0",
got: "0.0",
want: &SrlVersion{"0", "0", "", "", ""},
},
{
desc: "0.0.0",
got: "0.0.0",
want: &SrlVersion{"0", "0", "0", "", ""},
},
{
desc: "0.0.0-34652",
got: "0.0.0-34652",
want: &SrlVersion{"0", "0", "0", "34652", ""},
},
{
desc: "latest",
got: "latest",
want: &SrlVersion{"0", "", "", "", ""},
},
{
desc: "empty",
got: "",
want: &SrlVersion{"0", "", "", "", ""},
},
{
desc: "invalid1",
got: "abcd",
want: nil,
err: ErrVersionParse,
},
}

for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
ver, err := parseVersionString(tt.got)
if !errors.Is(err, tt.err) {
t.Fatalf("got error '%v' but expected '%v'", err, tt.err)
}

if !cmp.Equal(ver, tt.want) {
t.Fatalf(
"%s: actual and expected inputs do not match\nactual: %+v\nexpected:%+v",
tt.desc,
ver,
tt.want,
)
}
},
)
}
}
Loading