GitLab CIによるLagopusのRyu Certificationテスト

[Lagopus](http://www.lagopus.org/)がDockerコンテナとDPDK virtio_userドライバでつながりました！
DPDKでコンテナ接続ができるようになったことで、DPDKアプリケーションをコンテナで仮想化することができるようになります。

https://github.com/lagopus/lagopus/blob/master/docs/how-to-use-virtio-user.md


# 概要

この記事では、DockerベースのCI環境であるGitLab CIを用いて、Lagopusを題材にDPDKアプリケーションの自動テストをおこなう方法を紹介します。

ここでは、単体テストではなくDPDK接続を利用した統合テストを扱います。
物理接続による統合テストでは結線や接続本数などのハードウェア制約がありますが、仮想ドライバを用いることで柔軟な接続や切り替えが実現できるようになります。


統合テストの例として、OpenFlowのプロトコル準拠テストである[Ryu Certification](https://osrg.github.io/ryu-book/ja/html/switch_test_tool.html)をLagopusに対して実行する方法を紹介します。


# GitLab, GitLab CIのインストール

GitLabはオンプレ版のCommunity Editionを利用します。
GitLabとGitLab CIについては、以下の公式ドキュメントを参考にしてインストールしてください。

- GitLabのインストール
  - https://docs.gitlab.com/ce/install/installation.html
- GitLab CI Multi Runnerのインストール
  - https://docs.gitlab.com/runner/install/linux-repository.html

# Runner環境の設定

Runnerのサーバ環境では、LagopusをDockerで動かす設定をする必要があります。
主に以下の２点についての設定が必要になります。

- Hugepagesの確保
- hugetlbfsの確保
    - コンテナごとに確保する必要がある

詳細は以下のドキュメントを参考にしてください。

https://github.com/lagopus/lagopus/blob/master/docs/how-to-use-virtio-user.md

# CI Multi Runnerの立ち上げ

公式の手順と同様で、executorにはDockerを使うように設定してください。

```
$ sudo gitlab-ci-multi-runner register
sudo gitlab-ci-multi-runner register
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com )
https://gitlab.com
Please enter the gitlab-ci token for this runner
xxx
Please enter the gitlab-ci description for this runner
my-runner
INFO[0034] fcf5c619 Registering runner... succeeded
Please enter the executor: shell, docker, docker-ssh, ssh?
docker
Please enter the Docker image (eg. ruby:2.1):
ruby:2.1 # なんでもよい ubuntu:16.04など
INFO[0037] Runner registered successfully. Feel free to start it, but if it's
running already the config should be automatically reloaded!
```


# CI Multi Runnerの設定

Docker executorでDockerイメージをビルドする場合などは、[Docker in Dockerの構成](http://docs.gitlab.com/ce/ci/docker/using_docker_build.html#use-docker-in-docker-executor)で利用することが多いですが、ここでは[ホストのDockerを使う設定](http://docs.gitlab.com/ce/ci/docker/using_docker_build.html#use-docker-socket-binding)を利用します。
DPDKの動作にはHugepages等の割当が必要で、ホストに直接アクセスできるほうが都合がよいためです。

CI Multi Runnerの設定で、`volumes`の項目にホストの`/var/run/docker.sock`を追加して、ホストのDockerソケットをコンテナにマウントするようにします。


- `/etc/gitlab-runner/config.toml`

```toml
concurrent = 1
check_interval = 0

[[runners]]
  name = "my-runner"
  token = "xxxxxxxxxxxx"
  executor = "docker"
  [runners.docker]
    tls_verify = false
    image = "ubuntu:16.04"
    privileged = false
    disable_cache = false
    volumes = ["/cache", "/var/run/docker.sock:/var/run/docker.sock"]
```



# `.gitlab-ci.yml`の準備

今回題材となるLagopusのRyu Certificationを実行する`.gitlab-ci.yml`は以下の通りです。

CIは次の流れで実行されます。

1. test
  - テスト実行
2. cleanup_test
  - コンテナの停止と削除

なお、LagopusのDockerイメージはこちらにあります。
https://hub.docker.com/r/falcon8823/lagopus-docker/


```yaml:.gitlab-ci.yml
# .gitlab-ci.yml
variables:
  CONT_TEST_IMAGE: falcon8823/lagopus-docker
  RYU_NAME: ci-${CI_PIPELINE_ID}-ryu01
  LAGO1_NAME: ci-${CI_PIPELINE_ID}-lago1
  LAGO2_NAME: ci-${CI_PIPELINE_ID}-lago2
  MAX_TIME: '1800' # judge.shのタイムアウト時間（秒）

stages:
  - test
  - cleanup_test

# テスト
test:
  stage: test
  image: docker:latest
  script:
    # イメージをpull
    - docker pull $CONT_TEST_IMAGE
    # vhostのsocketファイルを削除
    - docker run --rm -v /tmp/dpdk:/tmp/dpdk busybox sh -c 'rm -rf /tmp/dpdk/vhost*'
    # テスターのRyuを起動（デタッチモード）
    - docker run -d --rm --name $RYU_NAME osrg/ryu ryu-manager ryu/ryu/tests/switch/tester.py --test-switch-dir ryu/ryu/tests/switch/of13/action/00_OUTPUT.json
    # Lagopusを起動（デタッチモード）
    - docker run -d --link=$RYU_NAME:ryu01 --name $LAGO1_NAME -v /path/to/vhost.dsl:/tmp/lago/vhost.dsl -v /tmp/dpdk:/tmp/dpdk -v /mnt/huge_c0:/mnt/huge_c0 $CONT_TEST_IMAGE lagopus -d -l /dev/stderr -C /tmp/lago/vhost.dsl -- -c 0xf -n 2 -m 1024 --no-pci --huge-dir=/mnt/huge_c0 --
    # Lagopusを起動（デタッチモード）
    - docker run -d --link=$RYU_NAME:ryu01 --name $LAGO2_NAME -v /path/to/virtio.dsl:/tmp/lago/virtio.dsl -v /tmp/dpdk:/tmp/dpdk -v /mnt/huge_c1:/mnt/huge_c1 $CONT_TEST_IMAGE lagopus -d -l /dev/stderr -C /tmp/lago/virtio.dsl -- -c 0xf0 -n 2 -m 1024 --no-pci --huge-dir=/mnt/huge_c1 --
    # テスト判定スクリプトを実行
    - ./judge.sh


# コンテナの終了と片付け
cleanup_test:
  stage: cleanup_test
  image: docker:latest
  script:
    - docker kill $RYU_NAME $LAGO1_NAME $LAGO2_NAME || true
    - docker rm $RYU_NAME $LAGO1_NAME $LAGO2_NAME || true
  when: always
```

# Lagopusの設定ファイルの配置

docker runコマンドで、`-v /path/to/vhost.dsl:/tmp/lago/vhost.dsl`と指定して、LagopusのDSLファイルをコンテナ内に渡しています。
この部分はイケてないのですがホストのDockerで動かす以上、Runnerが動いているコンテナにあるデータを別のコンテナに渡すことが難しくなります。

従って、ここでは妥協的にホストマシンの`/path/to`に、vhost.dslとvirtio.dslをあらかじめ配置しておきます。


```:vhost.dsl
channel channel01 create -dst-addr ryu01 -protocol tcp
controller controller01 create -channel channel01 -role equal -connection-type main

interface interface1 create -type ethernet-dpdk-phy -device eth_vhost0,iface=/tmp/dpdk/vhost0
interface interface2 create -type ethernet-dpdk-phy -device eth_vhost1,iface=/tmp/dpdk/vhost1
interface interface3 create -type ethernet-dpdk-phy -device eth_vhost2,iface=/tmp/dpdk/vhost2

port port01 create -interface interface1
port port02 create -interface interface2
port port03 create -interface interface3

bridge bridge01 create -controller controller01 -port port01 1 -port port02 2 -port port03 3 -dpid 0x1
bridge bridge01 enable
```



```:virtio.dsl
channel channel01 create -dst-addr ryu01 -protocol tcp
controller controller01 create -channel channel01 -role equal -connection-type main

interface interface1 create -type ethernet-dpdk-phy -device virtio_user0,path=/tmp/dpdk/vhost0
interface interface2 create -type ethernet-dpdk-phy -device virtio_user1,path=/tmp/dpdk/vhost1
interface interface3 create -type ethernet-dpdk-phy -device virtio_user2,path=/tmp/dpdk/vhost2

port port01 create -interface interface1
port port02 create -interface interface2
port port03 create -interface interface3

bridge bridge01 create -controller controller01 -port port01 1 -port port02 2 -port port03 3 -dpid 0x2
bridge bridge01 enable
```


# テストの成功・失敗判定

GitLab CIでは、scriptの終了コードに基づいてSuccess, Failedを判定します。
従って、テストの正否を判定するためには、テストスクリプトが終了コードを返す必要があります。

Ryu Certificationを実行する`tester.py`は、テスト結果を標準出力にテキストで出力する上に、起動し続けるため終了コードを得ることができません。
従って、ログを定期的に読み込み、終了時の判定を行う以下のスクリプトを作成しました。

- `judge.sh`

```sh
#!/bin/sh

# Ryuのテスターはテストが完了しても終了しないため、
# この監視スクリプトでsuccess or failedの判定をする

MAX_TIME=${MAX_TIME:-120}

# logが流れるようにバックグラウンド動作
docker logs -f $RYU_NAME &
log_pid=$!

# MAX_TIMEまでログを監視
for i in `seq 1 $MAX_TIME`; do
  sleep 1

  # 終了チェック
  docker logs $RYU_NAME 2>&1 | grep  'Test report' 1> /dev/null
  if test $? = 0; then
    # OKとエラーの数を取得
    result=`docker logs $RYU_NAME 2>&1 | grep 'OK\(.*\) / ERROR\(.*\)'`
    ok_num=`echo "$result" | sed -e "s/OK(\(.*\)) \/ ERROR(\(.*\))/\1/"`
    ng_num=`echo "$result" | sed -e "s/OK(\(.*\)) \/ ERROR(\(.*\))/\2/"`

    # docker logsでログを表示していたプロセスをkill
    kill $log_pid

    # NGが0でなければエラー(failed),そうでなければ正常終了(success)
    if test $ng_num != 0; then
      exit 1
    else
      exit 0
    fi
  fi
done
```

# CIの実行

あとは、`.gitlab-ci.yml`の入ったGitリポジトリをGitLabにPushすることで、CIが実行されます。


このドキュメントで紹介したファイルは以下においてあります。
https://github.com/falcon8823/lagopus-gitlab-ci