1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「体験しながら学ぶネットワーク管理技術入門」のテストネットワークをContainerlabで試す方法

Posted at

はじめに

「体験しながら学ぶネットワーク管理技術入門」という本

のテストネットワークは、tinet

で試すようになっています。

これをContainerlab

で試すための変換ツールを開発しました。

そのツールをここで紹介します。

tinetとcontainerlabのネットワーク定義

tinetのネットワーク定義は、

spec.yaml
---
nodes:
- name: R1
  image: tinynetwork/pmacctd:develop
  interfaces:
  - { name: net0, type: direct, args: R2#net0 }
- name: R2
  image: tinynetwork/pmacctd:develop
  interfaces:
  - { name: net0, type: direct, args: R1#net0 }

node_configs:
- name: R1
  cmds:
  - cmd: ip addr add 10.2.0.1/24 dev net0
- name: R2
  cmds:
  - cmd: ip addr add 10.2.0.2/24 dev net0
  - cmd: ip route add default via 10.2.0.1

のようにnodeとnodeで実行する設定コマンドで構成されます。

この定義をcontainerlabの

topology.yaml
name: test
topology:
  nodes:
    R1:
      kind: linux
      image: tinynetwork/pmacctd:develop
      network-mode: none
      exec:
        - ip addr add 10.2.0.1/24 dev net0
    R2:
      kind: linux
      image: tinynetwork/pmacctd:develop
      network-mode: none
      exec:
        - ip addr add 10.2.0.2/24 dev net0
        - ip route add default via 10.2.0.1
  links:
    - endpoints: ["R1:net0","R2:net0"]

のように変更します。

変換のポイント

変換処理のポイントは

  • nameにネットワーク名を設定
  • topologyの配下にnodesとlinksを配置
  • nodesは名前をキーに設定
  • kindをlinuxに設定
  • imageは同じ値を設定
  • network-modeをnoneに設定
  • node_configsの設定コマンドはノード単位のexecに設定
  • nodeのinterfacesから接続関係のlinksを作成
  • mountはbindに変更
  • dnsの設定も以降

です。

変換ツール

変換処理のポイントを元に変換ツールをGo言語で作成しました。

$go build -o tinet2clab .
$cat spec.yaml | tinet2clab > topology.yaml

のように変換します。

「体験しながら学ぶネットワーク管理技術入門」のspec_01.yamlも変換できました。

変換ツールのソースコードは、以下の通りです。

main.go
package main

import (
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/spf13/viper"
)

func main() {
	c, err := loadTinetSpec()
	if err != nil {
		log.Fatalln(err)
	}
	err = saveClabTopology(c)
	if err != nil {
		log.Fatalln(err)
	}
}

type Tn struct {
	PreCmd      []PreCmd     `yaml:"precmd"`
	PreInit     []PreInit    `yaml:"preinit"`
	PreConf     []PreConf    `yaml:"preconf"`
	PostInit    []PostInit   `yaml:"postinit"`
	PostFini    []PostFini   `yaml:"postfini"`
	Nodes       []Node       `yaml:"nodes" mapstructure:"nodes"`
	Switches    []Switch     `yaml:"switches" mapstructure:"switches"`
	NodeConfigs []NodeConfig `yaml:"node_configs" mapstructure:"node_configs"`
	Test        []Test       `yaml:"test"`
}

// PreCmd
type PreCmd struct {
	// Cmds []Cmd `yaml:"cmds"`
	Cmds []Cmd `yaml:"cmds" mapstructure:"cmds"`
}

// PreInit
type PreInit struct {
	Cmds []Cmd `yaml:"cmds" mapstructure:"cmds"`
}

// PreConf
type PreConf struct {
	Cmds []Cmd `yaml:"cmds" mapstructure:"cmds"`
}

// PostInit
type PostInit struct {
	Cmds []Cmd `yaml:"cmds" mapstructure:"cmds"`
}

// PostFini
type PostFini struct {
	Cmds []Cmd `yaml:"cmds" mapstructure:"cmds"`
}

// Node
type Node struct {
	Name           string                 `yaml:"name" mapstructure:"name"`
	Type           string                 `yaml:"type" mapstructure:"type"`
	NetBase        string                 `yaml:"net_base" mapstructure:"net_base"`
	VolumeBase     string                 `yaml:"volume" mapstructure:"volume"`
	Image          string                 `yaml:"image" mapstructure:"image"`
	BuildFile      string                 `yaml:"buildfile" mapstructure:"buildfile"`
	BuildContext   string                 `yaml:"buildcontext" mapstructure:"buildcontext"`
	Interfaces     []Interface            `yaml:"interfaces" mapstructure:"interfaces"`
	Sysctls        []Sysctl               `yaml:"sysctls" mapstructure:"sysctls"`
	Mounts         []string               `yaml:"mounts,flow" mapstructure:"mounts,flow"`
	DNS            []string               `yaml:"dns,flow" mapstructure:"dns,flow"`
	DNSSearches    []string               `yaml:"dns_search,flow" mapstructure:"dns_search,flow"`
	HostNameIgnore bool                   `yaml:"hostname_ignore" mapstructure:"hostname_ignore"`
	EntryPoint     string                 `yaml:"entrypoint" mapstructure:"entrypoint"`
	ExtraArgs      string                 `yaml:"docker_run_extra_args" mapstructure:"docker_run_extra_args"`
	Vars           map[string]interface{} `yaml:"vars" mapstructure:"vars"`
	Templates      []Template             `yaml:"templates" mapstructure:"templates"`
}

// Interface
type Interface struct {
	Name  string `yaml:"name"`
	Type  string `yaml:"type"`
	Args  string `yaml:"args"`
	Addr  string `yaml:"addr"`
	Label string `yaml:"label"`
}

// Sysctl
type Sysctl struct {
	Sysctl string `yaml:"string"`
}

type Template struct {
	Src     string `yaml:"src"`
	Dst     string `yaml:"dst"`
	Content string `yaml:"content"`
}

// Switch
type Switch struct {
	Name       string      `yaml:"name"`
	Interfaces []Interface `yaml:"interfaces" mapstructure:"interfaces"`
}

// NodeConfig
type NodeConfig struct {
	Name string `yaml:"name"`
	Cmds []Cmd  `yaml:"cmds" mapstructure:"cmds"`
}

// Cmd
type Cmd struct {
	Cmd string `yaml:"cmd"`
}

// Test
type Test struct {
	Name string
	Cmds []Cmd `yaml:"cmds" mapstructure:"cmds"`
}

func loadTinetSpec() (Tn, error) {
	var r Tn
	var err error
	viper.SetConfigType("yaml")

	if err = viper.ReadConfig(os.Stdin); err != nil {
		return r, err
	}

	err = viper.Unmarshal(&r)
	return r, err
}

func saveClabTopology(c Tn) error {
	name := "test"
	if len(os.Args) > 1 {
		name = os.Args[1]
	}
	fmt.Printf("name: %s\n", name)
	fmt.Printf("topology:\n")
	fmt.Printf("  nodes:\n")
	for _, n := range c.Nodes {
		fmt.Printf("    %s:\n", n.Name)
		fmt.Printf("      kind: linux\n")
		fmt.Printf("      image: %s\n", n.Image)
		if n.NetBase == "" {
			n.NetBase = "none"
		}
		fmt.Printf("      network-mode: %s\n", n.NetBase)
		if len(n.DNS) > 0 {
			fmt.Printf("      dns:\n")
			fmt.Printf("        servers:\n")
			for _, d := range n.DNS {
				fmt.Printf("          - %s\n", d)
			}
			if len(n.DNSSearches) > 0 {
				fmt.Printf("        search:\n")
				for _, d := range n.DNSSearches {
					fmt.Printf("          - %s\n", d)
				}
			}
		}
		if len(n.Mounts) > 0 {
			fmt.Printf("      binds:\n")
			for _, m := range n.Mounts {
				fmt.Printf("        - %s\n", m)
			}
		}
		for _, nc := range c.NodeConfigs {
			if nc.Name == n.Name && len(nc.Cmds) > 0 {
				fmt.Printf("      exec:\n")
				for _, cmd := range nc.Cmds {
					fmt.Printf("        - %s\n", strings.ReplaceAll(cmd.Cmd, "\\$", "$$"))
				}
				break
			}
		}
		if n.BuildFile != "" {
			buildContext := "."
			if n.BuildContext != "" {
				buildContext = n.BuildContext
			}
			fmt.Printf("#docker build -t %s -f %s %s\n", n.Image, n.BuildFile, buildContext)
		}
		if n.ExtraArgs != "" {
			a := strings.Fields(n.ExtraArgs)
			ports := []string{}
			for i, p := range a {
				if p == "-p" && i+1 < len(a) {
					ports = append(ports, a[i+1])
				}
			}
			if len(ports) > 0 {
				fmt.Printf("      ports:\n")
				for _, e := range ports {
					fmt.Printf("        - %s\n", e)
				}
			}
		}
	}
	linkMap := make(map[string]bool)
	fmt.Printf("  links:\n")
	for _, n := range c.Nodes {
		for _, i := range n.Interfaces {
			if i.Type == "direct" {
				a := strings.SplitN(i.Args, "#", 2)
				if len(a) == 2 {
					k := fmt.Sprintf("%s:%s:%s:%s", a[0], a[1], n.Name, i.Name)
					if _, ok := linkMap[k]; !ok {
						fmt.Printf("    - endpoints: [\"%s:%s\",\"%s:%s\"]\n", n.Name, i.Name, a[0], a[1])
						linkMap[k] = true
						linkMap[fmt.Sprintf("%s:%s:%s:%s", n.Name, i.Name, a[0], a[1])] = true
					}
				}
			}

		}
	}
	return nil
}

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?