share facebook facebook2 twitter menu hatena pocket slack

2016.03.07 MON

超メモで走り切る 2015 年(4) LXD の REST API クライアントを作っている

川原 洋平

WRITTEN BY川原 洋平

tl;dr

LXD の REST API クライアントを作っているのでメモ。

inokappa/oreno_lxdapi
Contribute to oreno_lxdapi development by creating an account on GitHub.

github.com

メモ

使い方

  • インストール
$ git clone https://github.com/inokappa/oreno_lxdapi.git
$ cd oreno_lxdapi
$ bundle install
  • 以下、pry を使って説明。

  • 初期化

$ bundle exec pry
[1] pry(main)> require 'oreno_lxdapi'
=> true
[2] pry(main)> c = OrenoLxdapi::Client.new("oreno-ubuntu-image", "test-container")
=> #<OrenoLxdapi::Client:0x007f1ad0f5e348 @container_name="test-container", @image_name="oreno-ubuntu-image", @uri="unix:///var/lib/lxd/unix.socket">
  • コンテナ作成
[3] pry(main)> c.create_container
=> "{"type":"async","status":"OK","status_code":100,"metadata":{"id":"1a9e85e6-ce3a-4f60-9e80-6b5977184043","class":"task","created_at":"2015-12-30T10:06:42.084161349+09:00","updated_at":"2015-12-30T10:06:42.084161349+09:00","status":"Running","status_code":103,"resources":{"containers":["/1.0/containers/test-container"]},"metadata":null,"may_cancel":false,"err":""},"operation":"/1.0/operations/1a9e85e6-ce3a-4f60-9e80-6b5977184043"}n
  • コンテナ起動
[4] pry(main)> c.run_container
still starting...
still starting...
still starting...
=> "{"type":"async","status":"OK","status_code":100,"metadata":{"id":"3cae9859-159f-4cc5-b444-d99e01767480","class":"task","created_at":"2015-12-30T10:06:47.970125199+09:00","updated_at":"2015-12-30T10:06:47.970125199+09:00","status":"Running","status_code":103,"resources":{"containers":["/1.0/containers/test-container"]},"metadata":null,"may_cancel":false,"err":""},"operation":"/1.0/operations/3cae9859-159f-4cc5-b444-d99e01767480"}n"
  • コンテナ一覧
[5] pry(main)> c.list_containers
=> "{"type":"sync","status":"Success","status_code":200,"metadata":["/1.0/containers/oreno-container","/1.0/containers/oreno-ubuntu","/1.0/containers/oreno-ubuntu01","/1.0/containers/test-container"],"operation":""}n"
  • コマンド実行
[6] pry(main)> c.run_lxc_exec("pwd")
/root
=> true
  • コンテナ停止
[7] pry(main)> c.stop_container
=> "{"type":"async","status":"OK","status_code":100,"metadata":{"id":"8052b60d-32dd-4e0c-988b-469eab82b3a4","class":"task","created_at":"2015-12-30T10:07:23.500527489+09:00","updated_at":"2015-12-30T10:07:23.500527489+09:00","status":"Running","status_code":103,"resources":{"containers":["/1.0/containers/test-container"]},"metadata":null,"may_cancel":false,"err":""},"operation":"/1.0/operations/8052b60d-32dd-4e0c-988b-469eab82b3a4"}n"

何が出来ていないのか

  • コマンド実行

コンテナ内でコマンドを実行する場合には /1.0/containers//exec を利用するが、以下のようにコマンド実行時の出力は Websocket 経由で取得することになるらしい。

見よう見まねで以下のように実装してみるが…

#!/usr/bin/env ruby

require 'net_http_unix'
require 'json'
require 'websocket-client-simple'

class LxdApi

  def initialize(image_name, container_name)
    @uri = "unix:///var/lib/lxd/unix.socket"
    @image_name = image_name
    @container_name = container_name
  end

  def client
    NetX::HTTPUnix.new(@uri)
  end

  def create_exec(command)
    commands = command.split(" ")
    req = Net::HTTP::Post.new("/1.0/containers/#{@container_name}/exec")
    req["Content-Type"] = "application/json"
    payload = {
      "command" =>  commands,
      "environment" => {
        "HOME" => "/root",
        "TERM" => "screen",
        "USER" => "root",
      },
      "wait-for-websocket" => true,
      "interactive" => true,
    }
    req.body = payload.to_json

    resp = client.request(req)
    json = JSON.parse(resp.body)

    operation_id = ""
    secret = ""

    if json['metadata']
      operation_id = json['metadata']['id']
      unless json['metadata']['metadata'] == nil
        secret = json['metadata']['metadata']['fds']['0']
        return operation_id, secret
      else
        return operation_id
      end
    end

  end

  def run_command(http_endpoint, id, secret)
    url = "#{http_endpoint}/1.0/operations/#{id}/websocket?secret=#{secret}"
    ws = WebSocket::Client::Simple.connect url

    ws.on :message do |msg|
      puts ">> #{msg.data}"
    end

    ws.on :open do
      puts "-- websocket open (#{ws.url})"
    end

    loop do
      ws.send STDIN.gets.strip
    end
  end

end

c = LxdApi.new("oreno-ubuntu-image", "oreno-container")
metadata = c.create_exec("pwd")
c.run_command("https://127.0.0.1:8443", metadata[0], metadata[1])

実行すると…以下のように実行結果は取得出来る。

-- websocket open (https://127.0.0.1:8443/1.0/operations/27308a13-079d-4aed-9412-cf1f71c63a6d/websocket?secret=56c5ab8397e9072276996fc7184c414ce8b765ac47ec5abdda1b2996aa4dabcb)
>> /root
>>

しかし、通常のコマンドの実行結果のようにコマンドが終了した時点でプロンプトに戻る(戻す)方法が解らなかった。

ということで…コマンドの実行だけは以下のように直接 lxc execコマンドを直接実行することにした。

   def run_lxc_exec(command)
      lxc_exec = "lxc exec #{@container_name} -- "
      run_command = lxc_exec + command
      status = system(run_command)
      return status
    end

残念…引き続きの課題にしたい。

以上

test-kitchen のドライバ作ってみたい

次はこのオレオレライブラリを使って test-kitchen のドライバを作ってみたいけど。

注意

LXD の REST API は絶賛開発中とのことですので、ここに書いてある内容は本記事を書いた時点の内容となりますのでご注意くださいませ。

元記事はこちら

超メモで走り切る 2015 年(4) LXD の REST API クライアントを作っている