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

Juniper cRPD integration #226

Merged
merged 13 commits into from
Jan 13, 2021
12 changes: 12 additions & 0 deletions clab/clab.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package clab

import (
"context"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -123,6 +124,17 @@ func (c *CLab) ExecPostDeployTasks(ctx context.Context, node *Node) error {
if err != nil {
return err
}
case "crpd":
// exec `service ssh restart` to start ssh service and take into account mounted sshd_config
execConfig := types.ExecConfig{Tty: false, AttachStdout: false, AttachStderr: false, Cmd: strings.Fields("service ssh restart")}
respID, err := c.DockerClient.ContainerExecCreate(context.Background(), node.ContainerID, execConfig)
if err != nil {
return err
}
_, err = c.DockerClient.ContainerExecAttach(context.Background(), respID.ID, execConfig)
if err != nil {
return err
}

case "linux":
log.Debugf("Running postdeploy actions for Linux '%s' node", node.ShortName)
Expand Down
38 changes: 34 additions & 4 deletions clab/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package clab
import (
"fmt"
"os"
"path"
"path/filepath"
"strings"

Expand Down Expand Up @@ -30,6 +31,7 @@ var kinds = []string{"srl", "ceos", "linux", "alpine", "bridge"}
var defaultConfigTemplates = map[string]string{
"srl": "/etc/containerlab/templates/srl/srlconfig.tpl",
"ceos": "/etc/containerlab/templates/arista/ceos.cfg.tpl",
"crpd": "/etc/containerlab/templates/crpd/juniper.conf",
}

var srlTypes = map[string]string{
Expand Down Expand Up @@ -94,7 +96,7 @@ type Node struct {
ShortName string
LongName string
Fqdn string
LabDir string
LabDir string // LabDir is a directory related to the node, it contains config items and/or other persistent state
Index int
Group string
Kind string
Expand Down Expand Up @@ -291,7 +293,10 @@ func (c *CLab) licenseInit(nodeCfg *NodeConfig, node *Node) (string, error) {
case c.Config.Topology.Defaults.License != "":
return c.Config.Topology.Defaults.License, nil
default:
return "", fmt.Errorf("no license found for node '%s' of kind '%s'", node.ShortName, node.Kind)
if node.Kind == "srl" {
return "", fmt.Errorf("no license found for node '%s' of kind '%s'", node.ShortName, node.Kind)
}
return "", nil
}
}

Expand Down Expand Up @@ -478,12 +483,34 @@ func (c *CLab) NewNode(nodeName string, nodeCfg NodeConfig, idx int) error {
topoPath := filepath.Join(node.LabDir, "topology.yml")
node.Binds = append(node.Binds, fmt.Sprint(topoPath, ":/tmp/topology.yml:ro"))

case "crpd":
node.Config = c.configInitialization(&nodeCfg, node.Kind)
node.Image = c.imageInitialization(&nodeCfg, node.Kind)
node.Group = c.groupInitialization(&nodeCfg, node.Kind)
node.Position = c.positionInitialization(&nodeCfg, node.Kind)
node.User = user

// initialize license file
lp, err := c.licenseInit(&nodeCfg, node)
if err != nil {
return err
}
lp, err = resolvePath(lp)
if err != nil {
return err
}
node.License = lp

// mount config and log dirs
node.Binds = append(node.Binds, fmt.Sprint(path.Join(node.LabDir, "config"), ":/config"))
node.Binds = append(node.Binds, fmt.Sprint(path.Join(node.LabDir, "log"), ":/var/log"))
// mount sshd_config
node.Binds = append(node.Binds, fmt.Sprint(path.Join(node.LabDir, "config/sshd_config"), ":/etc/ssh/sshd_config"))

case "alpine", "linux":
node.Config = c.configInitialization(&nodeCfg, node.Kind)
node.License = ""
node.Image = c.imageInitialization(&nodeCfg, node.Kind)
node.Group = c.groupInitialization(&nodeCfg, node.Kind)
node.NodeType = c.typeInit(&nodeCfg, node.Kind)
node.Position = c.positionInitialization(&nodeCfg, node.Kind)
node.Cmd = c.cmdInit(&nodeCfg, node.Kind)
node.User = user
Expand Down Expand Up @@ -575,6 +602,9 @@ func (c *CLab) VerifyBridgesExist() error {

//resolvePath resolves a string path by expanding `~` to home dir or getting Abs path for the given path
func resolvePath(p string) (string, error) {
if p == "" {
return "", nil
}
var err error
switch {
// resolve ~/ path
Expand Down
39 changes: 39 additions & 0 deletions clab/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ func (c *CLab) CreateNodeDirStructure(node *Node) (err error) {
defer c.m.RUnlock()

// create node directory in the lab directory
// skip creation of node directory for linux/bridge kinds
// since they don't keep any state normally
if node.Kind != "linux" && node.Kind != "bridge" {
CreateDirectory(node.LabDir, 0777)
}
Expand All @@ -163,6 +165,8 @@ func (c *CLab) CreateNodeDirStructure(node *Node) (err error) {
}

// generate a config file if the destination does not exist
// if the node has a `config:` statement, the file specified in that section
// will be used as a template in nodeGenerateConfig()
CreateDirectory(path.Join(node.LabDir, "config"), 0777)
dst = path.Join(node.LabDir, "config", "config.json")
if !fileExists(dst) {
Expand Down Expand Up @@ -197,6 +201,41 @@ func (c *CLab) CreateNodeDirStructure(node *Node) (err error) {
} else {
log.Debugf("Config file exists for node %s", node.ShortName)
}

case "crpd":
// create config and logs directory that will be bind mounted to crpd
CreateDirectory(path.Join(node.LabDir, "config"), 0777)
CreateDirectory(path.Join(node.LabDir, "log"), 0777)

// copy crpd config from default template or user-provided conf file
cfg := path.Join(node.LabDir, "/config/juniper.conf")
if !fileExists(cfg) {
err = node.generateConfig(cfg)
if err != nil {
log.Errorf("node=%s, failed to generate config: %v", node.ShortName, err)
}
} else {
log.Debugf("Config file exists for node %s", node.ShortName)
}
// copy crpd sshd conf file to crpd node dir
src := "/etc/containerlab/templates/crpd/sshd_config"
dst := node.LabDir + "/config/sshd_config"
err = copyFile(src, dst)
if err != nil {
return fmt.Errorf("file copy [src %s -> dst %s] failed %v", src, dst, err)
}
log.Debugf("CopyFile src %s -> dst %s succeeded\n", src, dst)

if node.License != "" {
// copy license file to node specific lab directory
src = node.License
dst = path.Join(node.LabDir, "/config/license.conf")
if err = copyFile(src, dst); err != nil {
return fmt.Errorf("file copy [src %s -> dst %s] failed %v", src, dst, err)
}
log.Debugf("CopyFile src %s -> dst %s succeeded", src, dst)
}

case "bridge":
default:
}
Expand Down
183 changes: 183 additions & 0 deletions docs/lab-examples/srl-crpd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
| | |
| ----------------------------- | ---------------------------------------------------------------------------------- |
| **Description** | A Nokia SR Linux connected back-to-back with Juniper cRPD |
| **Components** | [Nokia SR Linux][srl], [Juniper cRPD][crpd] |
| **Resource requirements**[^1] | :fontawesome-solid-microchip: 1 <br/>:fontawesome-solid-memory: 2 GB |
| **Topology file** | [srlcrpd01.yml][topofile] |
| **Name** | srlcrpd01 |
| **Version information**[^2] | `containerlab:0.9.0`, `srlinux:20.6.3-145`, `crpd:20.2R1.10`, `docker-ce:19.03.13` |

## Description
A lab consists of an SR Linux node connected with Juniper cRPD via a point-to-point ethernet link. Both nodes are also connected with their management interfaces to the `clab` docker network.

<div class="mxgraph" style="max-width:100%;border:1px solid transparent;margin:0 auto; display:block;" data-mxgraph="{&quot;page&quot;:0,&quot;zoom&quot;:1.5,&quot;highlight&quot;:&quot;#0000ff&quot;,&quot;nav&quot;:true,&quot;check-visible-state&quot;:true,&quot;resize&quot;:true,&quot;url&quot;:&quot;https://raw.githubusercontent.com/srl-wim/container-lab/diagrams/srlcrpd01&quot;}"></div>

## Use cases
This lab allows users to launch basic interoperability scenarios between Nokia SR Linux and Juniper cRPD network operating systems.

Below you will find configuration instructions to setup IS-IS routing protocol on both nodes and verify the successful control and data plane operation.
### OSPF
<div class="mxgraph" style="max-width:100%;border:1px solid transparent;margin:0 auto; display:block;" data-mxgraph="{&quot;page&quot;:2,&quot;zoom&quot;:1.5,&quot;highlight&quot;:&quot;#0000ff&quot;,&quot;nav&quot;:true,&quot;check-visible-state&quot;:true,&quot;resize&quot;:true,&quot;url&quot;:&quot;https://raw.githubusercontent.com/srl-wim/container-lab/diagrams/srlcrpd01&quot;}"></div>

#### Configuration
Once the lab is deployed with containerlab, use the following configuration instructions to make interfaces configuration and enable OSPF on both nodes.

=== "srl"
Get into SR Linux CLI with `docker exec -it clab-srlcrpd01-srl sr_cli` and start configuration
```bash
# enter candidate datastore
enter candidate

# configure loopback and data interfaces
set / interface ethernet-1/1 admin-state enable
set / interface ethernet-1/1 subinterface 0 admin-state enable
set / interface ethernet-1/1 subinterface 0 ipv4 address 192.168.1.1/24

set / interface lo0 subinterface 0 admin-state enable
set / interface lo0 subinterface 0 ipv4 address 10.10.10.1/32

# configure OSPF
set / network-instance default router-id 10.10.10.1
set / network-instance default interface ethernet-1/1.0
set / network-instance default interface lo0.0
set / network-instance default protocols ospf instance main admin-state enable
set / network-instance default protocols ospf instance main version ospf-v2
set / network-instance default protocols ospf instance main area 0.0.0.0 interface ethernet-1/1.0 interface-type point-to-point
set / network-instance default protocols ospf instance main area 0.0.0.0 interface ethernet-1/1.0

# commit config
commit now
```
=== "crpd"
cRPD configuration needs to be done both from the container process, as well as within the CLI.
First attach to the container process `bash` shell and configure interfaces: `docker exec -it clab-srlcrpd01-crpd bash`
```bash
# configure linux interfaces
ip addr add 192.168.1.2/24 dev eth1
ip addr add 10.10.10.2/32 dev lo
```
Then launch the CLI and continue configuration `docker exec -it clab-srlcrpd01-crpd cli`:
```bash
# enter configuration mode
configure
set routing-options router-id 10.10.10.2

set protocols ospf area 0.0.0.0 interface eth1 interface-type p2p
set protocols ospf area 0.0.0.0 interface lo.0 interface-type nbma

# commit configuration
commit
```

#### Verificaton
After the configuration is done on both nodes, verify the control plane by checking the route tables on both ends and ensuring dataplane was programmed as well by pinging the remote loopback

=== "srl"
```bash
# control plane verification
A:srl# / show network-instance default route-table ipv4-unicast summary | grep ospf
| 10.10.10.2/32 | 0 | true | ospfv2 | 1 | 10 | 192.168.1.2 (direct) | ethernet-1/1.0 |
```
```
# data plane verification
A:srl# ping 10.10.10.2 network-instance default
Using network instance default
PING 10.10.10.2 (10.10.10.2) 56(84) bytes of data.
64 bytes from 10.10.10.2: icmp_seq=1 ttl=64 time=1.15 ms
```
=== "crpd"
```bash
# control plane verification
root@crpd> show route | match OSPF
10.10.10.1/32 *[OSPF/10] 00:01:24, metric 1
224.0.0.5/32 *[OSPF/10] 00:05:49, metric 1
```

### IS-IS
<div class="mxgraph" style="max-width:100%;border:1px solid transparent;margin:0 auto; display:block;" data-mxgraph="{&quot;page&quot;:1,&quot;zoom&quot;:1.5,&quot;highlight&quot;:&quot;#0000ff&quot;,&quot;nav&quot;:true,&quot;check-visible-state&quot;:true,&quot;resize&quot;:true,&quot;url&quot;:&quot;https://raw.githubusercontent.com/srl-wim/container-lab/diagrams/srlcrpd01&quot;}"></div>

#### Configuration
Once the lab is deployed with containerlab, use the following configuration instructions to make interfaces configuration and enable IS-IS on both nodes.

=== "srl"
Get into SR Linux CLI with `docker exec -it clab-srlcrpd01-srl sr_cli` and start configuration
```bash
# enter candidate datastore
enter candidate

# configure loopback and data interfaces
set / interface ethernet-1/1 admin-state enable
set / interface ethernet-1/1 subinterface 0 admin-state enable
set / interface ethernet-1/1 subinterface 0 ipv4 address 192.168.1.1/24

set / interface lo0 subinterface 0 admin-state enable
set / interface lo0 subinterface 0 ipv4 address 10.10.10.1/32

# configure IS-IS
set / network-instance default router-id 10.10.10.1
set / network-instance default interface ethernet-1/1.0
set / network-instance default interface lo0.0
set / network-instance default protocols isis instance main admin-state enable
set / network-instance default protocols isis instance main net [ 49.0001.0100.1001.0001.00 ]
set / network-instance default protocols isis instance main interface ethernet-1/1.0 admin-state enable
set / network-instance default protocols isis instance main interface ethernet-1/1.0 circuit-type point-to-point
set / network-instance default protocols isis instance main interface lo0.0

# commit config
commit now
```
=== "crpd"
cRPD configuration needs to be done both from the container process, as well as within the CLI.
First attach to the container process `bash` shell and configure interfaces: `docker exec -it clab-srlcrpd01-crpd bash`
```bash
# configure linux interfaces
ip addr add 192.168.1.2/24 dev eth1
ip addr add 10.10.10.2/32 dev lo
```
Then launch the CLI and continue configuration `docker exec -it clab-srlcrpd01-crpd cli`:
```bash
# enter configuration mode
configure
set interfaces lo0 unit 0 family iso address 49.0001.0100.1001.0002.00
set routing-options router-id 10.10.10.2

set protocols isis interface all point-to-point
set protocols isis interface lo0.0
set protocols isis level 1 wide-metrics-only
set protocols isis level 2 wide-metrics-only
set protocols isis reference-bandwidth 100g

# commit configuration
commit
```

#### Verification
=== "srl"
```bash
# control plane verification
A:srl# / show network-instance default route-table ipv4-unicast summary | grep isis
| 10.10.10.2/32 | 0 | true | isis | 10 | 18 | 192.168.1.2 (direct) | ethernet-1/1.0 |
| 172.20.20.0/24 | 0 | true | isis | 110 | 18 | 192.168.1.2 (direct) | ethernet-1/1.0 |
```
```
# data plane verification
A:srl# ping 10.10.10.2 network-instance default
Using network instance default
PING 10.10.10.2 (10.10.10.2) 56(84) bytes of data.
64 bytes from 10.10.10.2: icmp_seq=1 ttl=64 time=1.15 ms
```
=== "crpd"
```bash
# control plane verification
root@crpd> show route table inet.0 | match IS-IS
10.10.10.1/32 *[IS-IS/18] 00:00:13, metric 100
```

[srl]: https://www.nokia.com/networks/products/service-router-linux-NOS/
[crpd]: https://www.juniper.net/documentation/us/en/software/crpd/crpd-deployment/topics/concept/understanding-crpd.html
[topofile]: https://github.com/srl-wim/container-lab/tree/master/lab-examples/srlcrpd01/srlcrpd01.yml

[^1]: Resource requirements are provisional. Consult with the installation guides for additional information.
[^2]: The lab has been validated using these versions of the required tools/components. Using versions other than stated might lead to a non-operational setup process.

<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/hellt/drawio-js@main/embed2.js" async></script>
Loading