※記事は全て個人の見解です。会社・組織を代表するものではありません。
はじめに
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 のコメントを参照。
#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(¶meters, 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(¶meters);
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);
}