LoginSignup
4
1

More than 5 years have passed since last update.

C アプリケーションから varlink の API を使って Container を操作する

Posted at

※記事は全て個人の見解です。会社・組織を代表するものではありません。

はじめに

varlink の C 言語用のライブラリ libvarlinkを使ってみた、および、libvarlink を使って Container を操作してみたことの紹介です。

  • libvarlink を使って、varlink を使用するクライアントアプリケーションを実装します。
  • 作成したアプリケーションを使って、Container を操作します。
  • Container の操作には podman を使います。 podman には varlink を使った APIが用意されており、API を用いてアプリケーションから Pod や Container の操作等が可能です。

環境

  • Fedora 29

準備

  • 必要なパッケージのインストール
]# dnf install libvarlink libvarlink-devel varlink-cli podman gcc jq
  • Podman Remote API Service を start
]# systemctl start io.podman.service

これで、API 通信用の socket が /run/podman/io.podman に作成されます。
varlink コマンドを実行して以下が確認できれば準備OK。

]$ varlink call -m unix:/run/podman/io.podman/io.podman.Ping
{
  "ping": {
    "message": "OK"
  }
}

ちなみに、この varlink コマンドは Rust で実装されているようです。

クライアントアプリケーション実装と動作確認

/usr/include/varlink.h に記載されている varlink のライブラリ関数を使ってよろしくコーディング
(サンプルコードは記事の末尾に記載)して、コンパイル。

]$ gcc -lvarlink -o varlink-c varlink-c.c 

]# ./varlink-c 
Usage: varlink-c -a varlink-address -m varlink-method [-p parameter]
Ex. varlink-c -a unix:/run/podman/io.podman -m io.podman.Ping
]# 

できあがったバイナリ varlink-c には、API 通信用の socket、メソッド名、メソッドのパラメータを引数として渡します。

Podman の API は API.md に記載されています。
試しに func Ping を実行してみます。socket 名に unix:/run/podman/io.podman、メソッド名に io.podman.Ping を渡して
実行してみると・・・

]# ./varlink-c -a unix:/run/podman/io.podman -m io.podman.Ping | jq
{
  "ping": {
    "message": "OK"
  }
}
]# 

前述の varlink コマンドと同じ結果になり、うまく動作しているようです。
ちなみに 標準出力を jq コマンドに渡しているのは、JSON の処理が面倒くさくて jq 先生に頼っているためです。

Container 操作

さっそく Container を作ってみます。nginx の Container を func CreateContainer を使って作ります。
メソッド名は io.podman.CreateContainer、パラメータには以下の JSON を渡しています。

{
  "create": {
    "image": "nginx",
    "command": [
      "nginx",
      "-g",
      "daemon off;"
    ]
  }
}
]# ./varlink-c -a unix:/run/podman/io.podman -m io.podman.CreateContainer -p "{\"create\": {\"image\" : \"nginx\", \"command\" : [\"nginx\", \"-g\", \"daemon off;\"] }}" | jq
{
  "container": "66adaf4953a61061b99ac94d687a908e6cc72e0884e4301bdbf4eb3bc37b6190"
}
]#

ID が 66adaf4953a6 の nginx の Container が作成されました。
つづいて、この Container を動かしてみます。

]# ./varlink-c -a unix:/run/podman/io.podman -m io.podman.StartContainer -p "{\"name\": \"66adaf4953a6\"}"  | jq
{
  "container": "66adaf4953a61061b99ac94d687a908e6cc72e0884e4301bdbf4eb3bc37b6190"
}

podman ps コマンドで確認してみると・・・

]# podman ps
CONTAINER ID  IMAGE  COMMAND               CREATED         STATUS             PORTS  NAMES
66adaf4953a6  nginx  nginx -g daemon o...  51 seconds ago  Up 32 seconds ago         stoic_jang
]#

ちゃんと動いているようです。

おわりに

私が調べた限りですが、libvarlink を使ったサンプルが見当たらなかったので、本記事が何らかの参考になれば幸いです。
varlink には Go、Rust、Python 用のライブラリがあるので、あえて C で書かなくてもいいよね、という話かも。。。

本記事ではクライアント側を実装してみましたが、libvarlink には API Server を実装するためのライブラリも用意されています。

サンプルコード

libvarlink のライブラリ関数の説明については、/usr/include/varlink.h のコメントを参照。

varlink-c.c
#include <varlink.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/epoll.h>

#define TIMEOUT 100

const char *progname = "varlink-c";

static long check_event(VarlinkConnection *connection)
{
    struct epoll_event events;
    int epollfd;
    long nfds;
    long ret = 0;

    epollfd = epoll_create1(EPOLL_CLOEXEC);
    if (epollfd == -1) {
        fprintf(stderr, "ERR: epoll_create1: %s (errno: %d)\n", strerror(errno), errno);
        return errno;
    }

    events.events = varlink_connection_get_events(connection);
    events.data.ptr = connection;

    ret = epoll_ctl(epollfd, EPOLL_CTL_ADD,
            varlink_connection_get_fd(connection),
            &events);
    if (ret == -1) {
        fprintf(stderr, "ERR: epoll_ctl: %s (errno: %d)\n", strerror(errno), errno);
        ret = errno;
        goto free_fd;
    }

    for (;;) {
        nfds = epoll_wait(epollfd, &events, 1, TIMEOUT);
        if (nfds == -1) {
            fprintf(stderr, "ERR: epoll_wait: %s (errno: %d)\n", strerror(errno), errno);
            ret = errno;
            break;
        } else if (!nfds)
            continue;
        else {
            ret = varlink_connection_process_events(connection, events.events);
            if (ret < 0)
                fprintf(stderr, "ERR: varlink_connection_process_events: %s\n",
                        varlink_error_string(labs(ret)));
            break;
        }
    }

free_fd:
    close(epollfd);

    return ret;
}

static long callback(VarlinkConnection *connection,
              const char *error,
              VarlinkObject *parameters,
              uint64_t flags,
              void *userdata)
{
    VarlinkObject **out = userdata;
    long ret = 0;

    if (error) {
        fprintf(stderr, "ERR: %s: %s\n", __func__, error);
        ret = -1;
    } else
        *out = varlink_object_ref(parameters);

    return ret;
}

static void usage(void)
{
    fprintf(stderr, "Usage: %s -a varlink-address -m varlink-method [-p parameter]\n"
            "Ex. %s -a unix:/run/podman/io.podman -m io.podman.Ping\n",
            progname, progname);
    exit(EXIT_FAILURE);
}

int main(int argc, char **argv)
{
    VarlinkConnection *connection;
    VarlinkObject *parameters;
    VarlinkObject *out;
    char *varlinkaddr = NULL;
    char *varlinkmethod = NULL;
    char *param;
    int addr_flg = 0, method_flg = 0, param_flg = 0;
    int status = EXIT_FAILURE;
    long ret;
    int c;
    char *result = NULL;

    while ((c = getopt(argc, argv, "a:m:p:")) != EOF)
        switch (c) {
        case 'a':
            varlinkaddr = optarg;
            addr_flg = 1;
            break;
        case 'm':
            varlinkmethod = optarg;
            method_flg = 1;
            break;
        case 'p':
            param = optarg;
            param_flg = 1;
            break;
        default:
            usage();
        }

    if (!addr_flg || !method_flg)
        usage();

    ret = varlink_connection_new(&connection, varlinkaddr);
    if (ret < 0) {
        fprintf(stderr, "ERR: varlink_connection_new: ret: %s\n",
                    varlink_error_string(labs(ret)));
        exit(status);
    }

    if (param_flg) {
        ret = varlink_object_new_from_json(&parameters, param);
        if (ret < 0) {
            fprintf(stderr, "ERR: varlink_object_new_from_json: %s\n",
                    varlink_error_string(labs(ret)));
            goto free_connection;
        }
    } else {
        ret = varlink_object_new(&parameters);
        if (ret < 0) {
            fprintf(stderr, "ERR: varlink_object_new: %s\n",
                    varlink_error_string(labs(ret)));
            goto free_connection;
        }
    }

    ret = varlink_connection_call(connection, varlinkmethod, parameters, 0,
                       callback, &out);
    if (ret < 0) {
        fprintf(stderr, "ERR: varlink_connection_call: %s\n",
                    varlink_error_string(labs(ret)));
        goto free_object;
    }

    ret = check_event(connection);
    if (ret)
        goto free_object;

    ret = varlink_object_to_json(out, &result);
    if (ret < 0) {
        varlink_object_unref(out);
        goto free_object;
    }

    fprintf(stdout, "%s\n", result);

    free(result);

    status = EXIT_SUCCESS;

    varlink_object_unref(out);
free_object:
    varlink_object_unref(parameters);
free_connection:
    varlink_connection_free(connection);

    exit(status);
}
4
1
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
4
1