ようへいの日々精進XP

よかろうもん

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

tl;dr

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

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 は絶賛開発中とのことですので、ここに書いてある内容は本記事を書いた時点の内容となりますのでご注意くださいませ。