44
1 (Advent Calendar 2012 JP) (2012.12.24 @irix_jp) (openstack) (Open source software to build public and private clouds.) ( イヴの夜は OpenStack (Common Lisp) でキメる! ) ( まあ、 ( キメなくてもいいんだけどね・・・ ( いや、キメてください。 ( お願いします。 ))))

OpenStack + Common Lisp

  • Upload
    irixjp

  • View
    4.342

  • Download
    4

Embed Size (px)

Citation preview

Page 1: OpenStack + Common Lisp

1

(Advent Calendar 2012 JP)

(2012.12.24 @irix_jp)

(openstack)(Open source software to build public and private clouds.)

(イヴの夜は OpenStackを(Common Lisp) でキメる! )

(まあ、 (キメなくてもいいんだけどね・・・ (いや、キメてください。 (お願いします。 ))))

Page 2: OpenStack + Common Lisp

2

m9(^Д^) プギャーwww

● イヴの夜にこんな資料作ってるヤツwww

orz

Page 3: OpenStack + Common Lisp

3

気を取り直して・・・

● OpenStack は全ての命令を API サーバが「 ReST形式」で受付けます。

Adminor

UsersHorizon

ClientLibrary(python)

ClientCommand

OpenStackComponent

OtherProgram

APIServer

DB

AMQP

OpenStackComponent

ExternalSoftware

Page 4: OpenStack + Common Lisp

4

ということは・・・

● HTTP のリクエストが投げられれば、何にからでも操作可能!● そう、 Common Lisp からでも!

– おまけで Ruby とか Perl とか Curl とか Wget でも。

Page 5: OpenStack + Common Lisp

5

Common Lisp のはじめ方

● まずは好みの処理系を選ぶ● Common Lisp は厳密には「言語」では無く、「仕様」● この仕様に準拠して実装されたプログラムを

「 Common Lisp 処理系」と呼びます。

● 代表的な処理系– Clozure CL ・・・おすすめ!( Win/Linux/Mac で動く)– SBCL ・・・処理が早い。 Linux なら安心。 Win だと苦しい。– Allegro CL ・・・商用。評価版あり。リッチな IDE 付き。– Lisp Works ・・・商用。使った事無いです。

より詳しい情報はこちら http://ja.wikipedia.org/wiki/Common_Lisp

Page 6: OpenStack + Common Lisp

6

早速動かす

● Clozure CL を使います( Linux 想定)● ここから DL

– http://ccl.clozure.com/download.html● 自分の環境に合わせたものを選択

● 解凍して適当に配置。– /opt/ccl とか

● とりあえず実行してみる– /opt/ccl/lx86cl   ・・・ 32bit 環境はこちら– /opt/ccl/lx86cl64  ・・・64 bit 環境はこちら

Page 7: OpenStack + Common Lisp

7

早速動かす

● これで OK

● とりあえず Hello World

● 超簡単!!!

$ /opt/ccl/lx86cl64 Welcome to Clozure Common Lisp Version 1.8-r15286M (LinuxX8664)!? ← これがプロンプト

? "Hello World""Hello World"

Page 8: OpenStack + Common Lisp

8

もう少し Lisp っぽく

● format 関数を使った例

● エラーしたら

Debbuger が起動するので冷静に「 q 」

● 終了

? (format t "~a" "Hello World")Hello WorldNIL

? (format r "~a" "Hello World")> Error: Unbound variable: R> While executing: CCL::CHEAP-EVAL-IN-ENVIRONMENT, in process listener(1).> Type :GO to continue, :POP to abort, :R for a list of available restarts.> If continued: Retry getting the value of R.> Type :? for other options.1 > q

? (quit)

Page 9: OpenStack + Common Lisp

9

プロンプト入力が低機能杉www

● 補完も履歴参照もできねーよwww● 案ずるな、バッチ実行できる。

– 適当なファイルを準備● test.lisp

– コマンドラインから

● -Q だんまりモード。プロンプトや起動メッセージを表示しない。● -b バッチモード。標準入力から受け取った式を評価して終了。

(format t "~a" "Hello World")

$ /opt/ccl/lx86cl64 -Q -b < test.lisp Hello WorldNIL

Page 10: OpenStack + Common Lisp

10

エディタ

● Emacs でも、 VIM でも xxxx.lisp とファイル名をつければ、 Lisp Mode になるので好きな方で。

● Emacs だと Slime という Lisp開発をインタラクティブに行える便利ツールがあるので、慣れてきたらコレを使うのがおすすめ。● Superior Lisp Interaction Mode for Emacs

– http://common-lisp.net/project/slime/

● Emacsユーザなら Lispbox を使うのもあり。いろいろ揃った All in One 環境

– http://common-lisp.net/project/lispbox/

Page 11: OpenStack + Common Lisp

11

パッケージ管理

● Python に PIP 、 Ruby にGEM 、 Perl に CPANがあるように・・・

● Common Lisp にも Quiacklisp というパッケージ管理の仕組みがあります。● サイトはこちら

– http://www.quicklisp.org/

Page 12: OpenStack + Common Lisp

12

パッケージ管理

● 導入は簡単$ wget http://beta.quicklisp.org/quicklisp.lisp$ /opt/ccl/lx86cl64 Welcome to Clozure Common Lisp Version 1.8-r15286M (LinuxX8664)!? ? (load "quicklisp.lisp")......? (quicklisp-quickstart:install)......? (ql:add-to-init-file)......? (quit)

プロキシを経由する場合 (quicklisp-quickstart:install :proxy "http://192.168.128.254:8080")

Page 13: OpenStack + Common Lisp

13

パッケージの導入

● HTTP リクエストを投げるパッケージを導入

● 初回のみ、パッケージの DL が実行される。

● ついでに jsonパッケージも

● パッケージの検索

? (ql:quickload :drakma)

? (ql:quickload :cl-json)

? (ql:system-apropos "json")

Page 14: OpenStack + Common Lisp

14

drakma がエラーするヤツ

● OS の環境によってはエラーが起きる

● libssl のファイル名の問題だけなので、適当にリンク貼ればおk

● 再実行

[package cl+ssl]> Error: Unable to load any of the alternatives:> ("libssl.so.1.0.0" "libssl.so.0.9.8" "libssl.so" "libssl.so.4")> While executing: CFFI::FL-ERROR, in process listener(1).> Type :POP to abort, :R for a list of available restarts.> Type :? for other options.

$ find / |grep libssl.so$ sudo ln -s /usr/lib64/libssl.so.10 /usr/lib64/libssl.so

? (ql:quickload :drakma)

Page 15: OpenStack + Common Lisp

15

ここから本編

● ようやくだよ!

Page 16: OpenStack + Common Lisp

16

keystoneへリクエストを投げる

● とりあえず何の工夫も無く openstack.lisp

● 実行すると当然エラー( 404 Not Found )● 指定されたリクエスト方式で、ヘッダ・データをちゃんと含める必要がある。

(ql:quickload :drakma)(drakma:http-request "http://cc:5000/v2.0/tokens")

Page 17: OpenStack + Common Lisp

17

keystoneへリクエストを投げる

● ちゃんと投げてみる● 最初の方はおまじない; ライブラリのロード(ql:quickload :drakma) ; HTTP クライアント(ql:quickload :cl-json) ; JSON パーサ

; デフォルトエンコードを指定(setf drakma:*drakma-default-external-format* :utf-8)

; application/json をテキストとして扱う(pushnew (cons "application" "json") drakma:*text-content-types* :test #'equal)

(drakma:http-request "http://192.168.100.52:5000/v2.0/tokens" :content-type "application/json" :method :post :content "{\"auth\": {\"tenantName\": \"demo\", \"passwordCredentials\": {\"username\": \"demo\", \"password\": \"openstack\"}}}")

Page 18: OpenStack + Common Lisp

18

keystoneへリクエストを投げる

● keystone に認証をかけるには● Content-tyep: application/json で、● データ部に json 形式で以下の内容を含めて、

– テナント名– ユーザ名– パスワード

● http://keystone-address:5000/v2.0/tokens– へ POST する。

Page 19: OpenStack + Common Lisp

19

keystoneへリクエストを投げる

● もう少しスマートに、● まず json を生成する関数を作ってみる; ライブラリのロード(ql:quickload :drakma) ; HTTP クライアント(ql:quickload :cl-json) ; JSON パーサ

; デフォルトエンコードを指定(setf drakma:*drakma-default-external-format* :utf-8)

; application/json をテキストとして扱う(pushnew (cons "application" "json") drakma:*text-content-types* :test #'equal)

(defun auth-create-json (tenantName username password) "keystone へ送る JSON を生成する " (json:encode-json-to-string `(("auth" ("tenantName" . ,tenantName) ("passwordCredentials" ("username" . ,username) ("password" . ,password))))))

(auth-create-json "demo" "demo" "openstack")

Page 20: OpenStack + Common Lisp

20

keystoneへリクエストを投げる

● 前のページの続き● リクエストを投げる関数も作る

● これを実行すると、それっぽい JSONが返ってくる(と思う

(defun auth-post-json (url tenantName username password) "JSON を keystone へ送付し、認証情報を得る " (drakma:http-request url :content-type "application/json" :method :post :content (auth-create-json tenantName username password)))

(auth-post-json "http://192.168.100.52:5000/v2.0/tokens" "demo" "demo" "openstack")

Page 21: OpenStack + Common Lisp

21

ここまでのコード

● これ以降のコードはこの下に追記していく● openstack.lisp; ライブラリのロード(ql:quickload :drakma) ; HTTP クライアント(ql:quickload :cl-json) ; JSON パーサ

; デフォルトエンコードを指定(setf drakma:*drakma-default-external-format* :utf-8)

; application/json をテキストとして扱う(pushnew (cons "application" "json") drakma:*text-content-types* :test #'equal)

(defun auth-create-json (tenantName username password) "keystone へ送る JSON を生成する " (json:encode-json-to-string `(("auth" ("tenantName" . ,tenantName) ("passwordCredentials" ("username" . ,username) ("password" . ,password))))))

(defun auth-post-json (url tenantName username password) "JSON を keystone へ送付し、認証情報を得る " (drakma:http-request url :content-type "application/json" :method :post :content (auth-create-json tenantName username password)))

Page 22: OpenStack + Common Lisp

22

認証結果

(auth-post-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "password")

"{\"access\": {\"token\": {\"expires\": \"2012-12-21T15:24:01Z\", \"id\": \"27ae438762604ffe824fdc5a12b08b4e\", \"tenant\": {\"enabled\": true, \"description\": \"\", \"name\": \"demo\", \"id\": \"ad6bc57213b04c7f867aadbab97519e3\"}}, \"serviceCatalog\": [{\"endpoints\": [{\"adminURL\": \"http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3\", \"region\": \"RegionOne\", \"internalURL\": \"http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3\", \"id\": \"aecda37bbc384097bb38e0ac2de8264a\", \"publicURL\": \"http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3\"}], \"endpoints_links\": [], \"type\": \"compute\", \"name\": \"nova\"}, {\"endpoints\": [{\"adminURL\": \"http://157.7.133.23:9696/\", \"region\": \"RegionOne\", \"internalURL\": \"http://157.7.133.23:9696/\", \"id\": \"1753832bfd754f7f8d61e13dda948be6\", \"publicURL\": \"http://157.7.133.23:9696/\"}], \"endpoints_links\": [], \"type\": \"network\", \"name\": \"quantum\"}, {\"endpoints\": [{\"adminURL\": \"http://157.7.133.23:9292\", \"region\": \"RegionOne\", \"internalURL\": \"http://157.7.133.23:9292\", \"id\": \"940eef79e0ad4716b5035cfcf77a8916\", \"publicURL\": \"http://157.7.133.23:9292\"}], \"endpoints_links\": [], \"type\": \"image\", \"name\": \"glance\"}, {\"endpoints\": [{\"adminURL\": \"http://157.7.133.23:8776/v1/ad6bc57213b04c7f867aadbab97519e3\", \"region\": \"RegionOne\", \"internalURL\": \"http://157.7.133.23:8776/v1/ad6bc57213b04c7f867aadbab97519e3\", \"id\": \"557a9527f0a94d908613260107738e3a\", \"publicURL\": \"http://157.7.133.23:8776/v1/ad6bc57213b04c7f867aadbab97519e3\"}], \"endpoints_links\": [], \"type\": \"volume\", \"name\": \"cinder\"}, {\"endpoints\": [{\"adminURL\": \"http://157.7.133.23:8773/services/Admin\", \"region\": \"RegionOne\", \"internalURL\": \"http://157.7.133.23:8773/services/Cloud\", \"id\": \"dadd326ef5904cecaf4a058a734003f2\", \"publicURL\": \"http://157.7.133.23:8773/services/Cloud\"}], \"endpoints_links\": [], \"type\": \"ec2\", \"name\": \"ec2\"}, {\"endpoints\": [{\"adminURL\": \"http://157.7.133.23:35357/v2.0\", \"region\": \"RegionOne\", \"internalURL\": \"http://157.7.133.23:5000/v2.0\", \"id\": \"1d763970d0b24d49be57eb7368044fd6\", \"publicURL\": \"http://157.7.133.23:5000/v2.0\"}], \"endpoints_links\": [], \"type\": \"identity\", \"name\": \"keystone\"}], \"user\": {\"username\": \"demo\", \"roles_links\": [], \"id\": \"5f003e1815ea4f76991a8b824402a918\", \"roles\": [{\"name\": \"Member\"}], \"name\": \"demo\"}, \"metadata\": {\"is_admin\": 0, \"roles\": [\"f55588c94cba4cce9f1cf2e4a15f490b\"]}}}"

● さっきの関数を実行してみると・・・

できてるっぽい

Page 23: OpenStack + Common Lisp

23

JSONをパースする

● cl-json のデコーダーを使って、リスト形式へ変換(json:decode-json-from-string (auth-post-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$"))

((:ACCESS (:TOKEN (:EXPIRES . "2012-12-21T15:45:52Z") (:ID . "729038529271455c849b8ce5249d1af3") (:TENANT (:ENABLED . T) (:DESCRIPTION . "") (:NAME . "demo") (:ID . "ad6bc57213b04c7f867aadbab97519e3"))) (:SERVICE-CATALOG ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3") (:ID . "aecda37bbc384097bb38e0ac2de8264a") (:PUBLIC-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3"))) (:ENDPOINTS--LINKS) (:TYPE . "compute") (:NAME . "nova")) ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:9696/") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:9696/") (:ID . "1753832bfd754f7f8d61e13dda948be6") (:PUBLIC-+URL+ . "http://157.7.133.23:9696/"))) (:ENDPOINTS--LINKS) (:TYPE . "network") (:NAME . "quantum")) ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:9292") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:9292") (:ID . "940eef79e0ad4716b5035cfcf77a8916") (:PUBLIC-+URL+ . "http://157.7.133.23:9292"))) (:ENDPOINTS--LINKS) (:TYPE . "image") (:NAME . "glance")) ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:8776/v1/ad6bc57213b04c7f867aadbab97519e3") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:8776/v1/ad6bc57213b04c7f867aadbab97519e3") (:ID . "557a9527f0a94d908613260107738e3a") (:PUBLIC-+URL+ . "http://157.7.133.23:8776/v1/ad6bc57213b04c7f867aadbab97519e3"))) (:ENDPOINTS--LINKS) (:TYPE . "volume") (:NAME . "cinder")) ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:8773/services/Admin") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:8773/services/Cloud") (:ID . "dadd326ef5904cecaf4a058a734003f2") (:PUBLIC-+URL+ . "http://157.7.133.23:8773/services/Cloud"))) (:ENDPOINTS--LINKS) (:TYPE . "ec2") (:NAME . "ec2")) ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:35357/v2.0") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:5000/v2.0") (:ID . "1d763970d0b24d49be57eb7368044fd6") (:PUBLIC-+URL+ . "http://157.7.133.23:5000/v2.0"))) (:ENDPOINTS--LINKS) (:TYPE . "identity") (:NAME . "keystone"))) (:USER (:USERNAME . "demo") (:ROLES--LINKS) (:ID . "5f003e1815ea4f76991a8b824402a918") (:ROLES ((:NAME . "Member"))) (:NAME . "demo")) (:METADATA (:IS--ADMIN . 0) (:ROLES "f55588c94cba4cce9f1cf2e4a15f490b"))))

このリストをよく見ると、以下のような構造になっている。

((:ACCESS (:TOKEN ....) (:SERVICE-CATALOG...) (:USER ...) (:METADATA..))

Page 24: OpenStack + Common Lisp

24

リスト操作

● First 関数でリストから最初の要素を取り出す(first (json:decode-json-from-string (auth-post-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$")))

(:ACCESS (:TOKEN (:EXPIRES . "2012-12-21T16:05:39Z") (:ID . "e79e348b0115468891a8c7bf752f4d64") (:TENANT (:ENABLED . T) (:DESCRIPTION . "") (:NAME . "demo") (:ID . "ad6bc57213b04c7f867aadbab97519e3"))) (:SERVICE-CATALOG ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3") (:ID . "aecda37bbc384097bb38e0ac2de8264a") (:PUBLIC-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3"))) (:ENDPOINTS--LINKS) (:TYPE . "compute") (:NAME . "nova")) ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:9696/") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:9696/") (:ID . "1753832bfd754f7f8d61e13dda948be6") (:PUBLIC-+URL+ . "http://157.7.133.23:9696/"))) (:ENDPOINTS--LINKS) (:TYPE . "network") (:NAME . "quantum")) ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:9292") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:9292") (:ID . "940eef79e0ad4716b5035cfcf77a8916") (:PUBLIC-+URL+ . "http://157.7.133.23:9292"))) (:ENDPOINTS--LINKS) (:TYPE . "image") (:NAME . "glance")) ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:8776/v1/ad6bc57213b04c7f867aadbab97519e3") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:8776/v1/ad6bc57213b04c7f867aadbab97519e3") (:ID . "557a9527f0a94d908613260107738e3a") (:PUBLIC-+URL+ . "http://157.7.133.23:8776/v1/ad6bc57213b04c7f867aadbab97519e3"))) (:ENDPOINTS--LINKS) (:TYPE . "volume") (:NAME . "cinder")) ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:8773/services/Admin") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:8773/services/Cloud") (:ID . "dadd326ef5904cecaf4a058a734003f2") (:PUBLIC-+URL+ . "http://157.7.133.23:8773/services/Cloud"))) (:ENDPOINTS--LINKS) (:TYPE . "ec2") (:NAME . "ec2")) ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:35357/v2.0") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:5000/v2.0") (:ID . "1d763970d0b24d49be57eb7368044fd6") (:PUBLIC-+URL+ . "http://157.7.133.23:5000/v2.0"))) (:ENDPOINTS--LINKS) (:TYPE . "identity") (:NAME . "keystone"))) (:USER (:USERNAME . "demo") (:ROLES--LINKS) (:ID . "5f003e1815ea4f76991a8b824402a918") (:ROLES ((:NAME . "Member"))) (:NAME . "demo")) (:METADATA (:IS--ADMIN . 0) (:ROLES "f55588c94cba4cce9f1cf2e4a15f490b")))

(:ACCESS (:TOKEN ....) (:SERVICE-CATALOG...) (:USER ...) (:METADATA..)

一つカッコが取れる。

Page 25: OpenStack + Common Lisp

25

リスト操作

● さらに一つづつ取り出してみると(first (first (json:decode-json-from-string (auth-post-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$"))))

→ :ACCESS

(second (first (json:decode-json-from-string (auth-post-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$"))))

→ (:TOKEN (:EXPIRES . "2012-12-21T16:08:44Z") (:ID . "e23f631ca4c74d64970f3bc21c21147c") (:TENANT (:ENABLED . T) (:DESCRIPTION . "") (:NAME . "demo") (:ID . "ad6bc57213b04c7f867aadbab97519e3")))

(fourth (first (json:decode-json-from-string (auth-post-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$"))))

→ (:USER (:USERNAME . "demo") (:ROLES--LINKS) (:ID . "5f003e1815ea4f76991a8b824402a918") (:ROLES ((:NAME . "Member"))) (:NAME . "demo"))

(:ACCESS (:TOKEN ....) (:SERVICE-CATALOG...) (:USER ...) (:METADATA..)

と、上から一つずつ要素が取り出せる。

Page 26: OpenStack + Common Lisp

26

リスト操作

● ここから TOKENを取り出してみる。

● 芸はないけど、こんな感じで TOKENが取り出せる。● しかしこの方法は場所決め打ちで取り出しているので、汎用性がなさすぎる。– TOKENの場所が

● :TOKEN → :ID にあることを利用すると・・・

(cdr (third (second (first (json:decode-json-from-string (auth-post-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$"))))))

→ "ce26db6126e342a8b604019463f34bb7"

Page 27: OpenStack + Common Lisp

27

ループと判定

● ループで回して if で判定すると TOKENが取得できる。

(block exit (dolist (x (first (json:decode-json-from-string (auth-post-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$")))) (if (listp x) (if (eql :TOKEN (first x)) (dolist (y x) (if (listp y) (if (eql :ID (first y)) (return-from exit (cdr y)))))))))

→ "e2f35b1dca014a38a4eb9afb55eb752d"

Page 28: OpenStack + Common Lisp

28

TOKENを取得する関数

● JSONを受け取り、 TOKENを返す関数

● 実行すると・・・

(defun auth-return-token-from-json (json) "keystone が返す JSON を受け取り、 TOKEN を抽出して返す関数 " (block exit (dolist (x (first (json:decode-json-from-string json))) (if (listp x) (if (eql :TOKEN (first x)) (dolist (y x) (if (listp y) (if (eql :ID (first y)) (return-from exit (cdr y))))))))))

(auth-return-token-from-json (auth-post-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$"))

→ "c71bb1f6e88b4032a4413c94a7f2bd46"

Page 29: OpenStack + Common Lisp

29

もう少し汎用的に

● keystone から JSONを受け取って、リスト変換して返す関数

● リスト変換された JSONとキーを受け取ってキーが存在するリストを返す関数

(defun auth-return-list-from-json (url tenantName username password) "keystone から認証 JSON を受け取り、リストへ変換して返す " (first (json:decode-json-from-string (auth-post-json url tenantName username password))))

(defun auth-return-list-from-json (json key) "json とキーを一つ受け取り、キーがマッチしたリストを返す " (block exit (dolist (x (first (json:decode-json-from-string json))) (if (listp x) (if (eql key (first x)) (return-from exit x))))))

(auth-return-key-from-json (auth-return-list-from-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$") :TOKEN)

(:TOKEN (:EXPIRES . "2012-12-24T14:52:59Z") (:ID . "f116354e97494b52a1f06ad04e45580c") (:TENANT (:ENABLED . T) (:DESCRIPTION . "") (:NAME . "demo") (:ID . "ad6bc57213b04c7f867aadbab97519e3")))

Page 30: OpenStack + Common Lisp

30

任意のデータを取り出す(defun auth-return-specified-key-from-json-list (json-list &rest keys) " 指定されたキーを取り出す " (let ((x json-list)) (dolist (key keys) (setf x (auth-return-key-from-json x key))) x))

(auth-return-specified-key-from-json-list (auth-return-list-from-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$") :TOKEN :ID)→ (:ID . "68013432d7274ad78b3630461cb385ef")

(auth-return-specified-key-from-json-list (auth-return-list-from-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$") :SERVICE-CATALOG)→ (:SERVICE-CATALOG ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3") (:ID . "aecda37bbc384097bb38e0ac2de8264a") (:PUBLIC-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3"))) (:ENDPOINTS--LINKS) (:TYPE . "compute") (:NAME . "nova")) ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:9696/") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:9696/") (:ID . "1753832bfd754f7f8d61e13dda948be6") (:PUBLIC-+URL+ . "http://157.7.133.23:9696/"))) (:ENDPOINTS--LINKS) (:TYPE . "network") (:NAME . "quantum")) ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:9292") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:9292") (:ID . "940eef79e0ad4716b5035cfcf77a8916") (:PUBLIC-+URL+ . "http://157.7.133.23:9292"))) (:ENDPOINTS--LINKS) (:TYPE . "image") (:NAME . "glance")) ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:8776/v1/ad6bc57213b04c7f867aadbab97519e3") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:8776/v1/ad6bc57213b04c7f867aadbab97519e3") (:ID . "557a9527f0a94d908613260107738e3a") (:PUBLIC-+URL+ . "http://157.7.133.23:8776/v1/ad6bc57213b04c7f867aadbab97519e3"))) (:ENDPOINTS--LINKS) (:TYPE . "volume") (:NAME . "cinder")) ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:8773/services/Admin") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:8773/services/Cloud") (:ID . "dadd326ef5904cecaf4a058a734003f2") (:PUBLIC-+URL+ . "http://157.7.133.23:8773/services/Cloud"))) (:ENDPOINTS--LINKS) (:TYPE . "ec2") (:NAME . "ec2")) ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:35357/v2.0") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:5000/v2.0") (:ID . "1d763970d0b24d49be57eb7368044fd6") (:PUBLIC-+URL+ . "http://157.7.133.23:5000/v2.0"))) (:ENDPOINTS--LINKS) (:TYPE . "identity") (:NAME . "keystone")))

(auth-return-specified-key-from-json-list (auth-return-list-from-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$") :METADATA :ROLES)→ (:ROLES "f55588c94cba4cce9f1cf2e4a15f490b")

(auth-return-specified-key-from-json-list (auth-return-list-from-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$") :USER :USERNAME)→ (:USERNAME . "demo")

Page 31: OpenStack + Common Lisp

31

エンドポイントの抽出

● auth-return-specified-key-from-json-list 関数で大体のデータは取り出せるが・・・● ENDPOINT はデータ形式がめんどくさい

(:SERVICE-CATALOG ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3") (:ID . "aecda37bbc384097bb38e0ac2de8264a") (:PUBLIC-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3"))) (:ENDPOINTS—LINKS) (:TYPE . "compute") (:NAME . "nova"))

((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:9696/") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:9696/") (:ID . "1753832bfd754f7f8d61e13dda948be6") (:PUBLIC-+URL+ . "http://157.7.133.23:9696/"))) (:ENDPOINTS--LINKS) (:TYPE . "network") (:NAME . "quantum"))

((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:9292") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:9292") (:ID . "940eef79e0ad4716b5035cfcf77a8916") (:PUBLIC-+URL+ . "http://157.7.133.23:9292"))) (:ENDPOINTS--LINKS) (:TYPE . "image") (:NAME . "glance")) .......

Page 32: OpenStack + Common Lisp

32

エンドポイントの抽出

● まずエンドポイントの要素1つを調べて、それが欲しいデータならば t(true) を返す関数を作る

(second (auth-return-specified-key-from-json-list (auth-return-list-from-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$") :SERVICE-CATALOG))

→ ((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3") (:ID . "aecda37bbc384097bb38e0ac2de8264a") (:PUBLIC-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3"))) (:ENDPOINTS--LINKS) (:TYPE . "compute") (:NAME . "nova"))

(auth-check-endpoint-name (second (auth-return-specified-key-from-json-list (auth-return-list-from-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$") :SERVICE-CATALOG)) "nova")→ T

(auth-check-endpoint-name (second (auth-return-specified-key-from-json-list (auth-return-list-from-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$") :SERVICE-CATALOG)) "quantum")→ NIL

(defun auth-check-endpoint-name (endpoint-list servicename) "1 つのエンドポイントを受け取り、それが servicename にマッチするエンドポイントならば t" (if (consp endpoint-list) (dolist (x endpoint-list) (if (consp x) (if (eql (first x) :NAME) (progn (if (string-equal (cdr x) servicename) (return-from auth-check-endpoint-name t)))))) nil))

Page 33: OpenStack + Common Lisp

33

エンドポイントの抽出

● マップ関数を使ったリスト操作● マップ関数を使うとリストの要素に対して、一括した操作

が行える。● 以下は指定した文字列にマッチするサービス名を含む要素のみを残して、残りを破棄する例

CL-USER> (remove-if-not #'(lambda (x) (auth-check-endpoint-name x "nova")) (auth-return-specified-key-from-json-list (auth-return-list-from-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$") :SERVICE-CATALOG))→ (((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3") (:ID . "aecda37bbc384097bb38e0ac2de8264a") (:PUBLIC-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3"))) (:ENDPOINTS--LINKS) (:TYPE . "compute") (:NAME . "nova")))

CL-USER> (remove-if-not #'(lambda (x) (auth-check-endpoint-name x "quantum")) (auth-return-specified-key-from-json-list (auth-return-list-from-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$") :SERVICE-CATALOG))→ (((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:9696/") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:9696/") (:ID . "1753832bfd754f7f8d61e13dda948be6") (:PUBLIC-+URL+ . "http://157.7.133.23:9696/"))) (:ENDPOINTS--LINKS) (:TYPE . "network") (:NAME . "quantum")))

CL-USER> (remove-if-not #'(lambda (x) (auth-check-endpoint-name x "notmatch")) (auth-return-specified-key-from-json-list (auth-return-list-from-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$") :SERVICE-CATALOG))→ NIL

Page 34: OpenStack + Common Lisp

34

エンドポイントの抽出

● 関数化しておく(defun auth-return-specified-endpoint (json-list servicename) " サービスカタログから指定のエンドポイントのみを抽出する " (remove-if-not #'(lambda (x) (auth-check-endpoint-name x servicename)) (auth-return-specified-key-from-json-list json-list :SERVICE-CATALOG)))

CL-USER> (auth-return-specified-endpoint (auth-return-list-from-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$") "nova")→ (((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3") (:ID . "aecda37bbc384097bb38e0ac2de8264a") (:PUBLIC-+URL+ . "http://157.7.133.23:8774/v2/ad6bc57213b04c7f867aadbab97519e3"))) (:ENDPOINTS--LINKS) (:TYPE . "compute") (:NAME . "nova")))

CL-USER> (auth-return-specified-endpoint (auth-return-list-from-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$") "quantum")→ (((:ENDPOINTS ((:ADMIN-+URL+ . "http://157.7.133.23:9696/") (:REGION . "RegionOne") (:INTERNAL-+URL+ . "http://157.7.133.23:9696/") (:ID . "1753832bfd754f7f8d61e13dda948be6") (:PUBLIC-+URL+ . "http://157.7.133.23:9696/"))) (:ENDPOINTS--LINKS) (:TYPE . "network") (:NAME . "quantum")))

CL-USER> (auth-return-specified-endpoint (auth-return-list-from-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$") "nomatch")→ NIL

Page 35: OpenStack + Common Lisp

35

コラム1:マップ関数

● 「マップ」というのは、 Hadoop の MapReduce のMap と似た処理。● (1 2 3 4 5) みたいなリストの要素全てに計算を適用さ

せる関数の事をマップ関数と読んでいる。

● 全ての要素に x 2を適用。– (mapcar #'(lambda (x) (* x 2)) '(1 2 3 4 5))

→ (1 4 6 8 10)

Page 36: OpenStack + Common Lisp

36

コラム2:ベクター

● 同じ型の要素を持ったリスト(配列)をベクターと呼ぶ。● (1 2 3 4 5) や ("a" "b" "c" "d" "e")

● このベクターに対して map 関数のような処理を実行する時に、 CPUが各要素に対する演算を並列に行えるように作られた CPUをベクタープロセッサという● ベクターデータに対する並列計算が得意な CPU● 対して、スカラープロセッサは for で要素を一つずつ取

り出して計算されるイメージ。10年以上前に聞いた話で、記憶も曖昧なので間違ってるかも

Page 37: OpenStack + Common Lisp

37

後少し

● ここまでくると、コレまでの関数の組合せで、ほぼ任意のデータが取得できるようになる。● いったんコレまでのコードを整理

; ライブラリのロード(ql:quickload :drakma) ; HTTP クライアント(ql:quickload :cl-json) ; JSON パーサ

; デフォルトエンコードを指定(setf drakma:*drakma-default-external-format* :utf-8)

; application/json をテキストとして扱う(pushnew (cons "application" "json") drakma:*text-content-types* :test #'equal)

(defun auth-create-json (tenantName username password) "keystone へ送る JSON を生成する " (json:encode-json-to-string `(("auth" ("tenantName" . ,tenantName) ("passwordCredentials" ("username" . ,username) ("password" . ,password))))))

(defun auth-post-json (url tenantName username password) "JSON を keystone へ送付し、認証情報を得る " (drakma:http-request url :content-type "application/json" :method :post :content (auth-create-json tenantName username password)))

Page 38: OpenStack + Common Lisp

38

後少し

● 続き(defun auth-return-list-from-json (url tenantName username password) "keystone から認証 JSON を受け取り、リストへ変換して返す " (first (json:decode-json-from-string (auth-post-json url tenantName username password))))

(defun auth-return-key-from-json (json-list key) " リスト変換された json とキーを一つ受け取り、キーがマッチしたリストを返す " (block exit (dolist (x json-list) (if (listp x) (if (eql key (first x)) (return-from exit x))))))

(defun auth-return-specified-key-from-json-list (json-list &rest keys) " 指定されたキーを取り出す " (let ((x json-list)) (dolist (key keys) (setf x (auth-return-key-from-json x key))) x))

Page 39: OpenStack + Common Lisp

39

後少し

● 続き(defun auth-check-endpoint-name (endpoint-list servicename) "1 つのエンドポイントを受け取り、それが servicename にマッチするエンドポイントならば t" (if (consp endpoint-list) (dolist (x endpoint-list) (if (consp x) (if (eql (first x) :NAME) (progn (if (string-equal (cdr x) servicename) (return-from auth-check-endpoint-name t)))))) nil))

(defun auth-return-specified-endpoint (json-list servicename) " サービスカタログから指定のエンドポイントのみを抽出する " (remove-if-not #'(lambda (x) (auth-check-endpoint-name x servicename)) (auth-return-specified-key-from-json-list json-list :SERVICE-CATALOG)))

Page 40: OpenStack + Common Lisp

40

これまでに出てきたこと

● 以下の基礎● リスト操作● 関数● マップ● 条件分岐● ループ

Page 41: OpenStack + Common Lisp

41

さらに学習するには

● マクロ● コンディション(例外処理)● CLOS (オブジェクト)● パッケージ

などなど・・・

Page 42: OpenStack + Common Lisp

42

とりあえずまとめ

● 今回は JSONを一旦リストへ変換して、様々なリスト操作を試してみました。

● マクロやオブジェクトを使うともっと効率的にデータ抽出が可能です。

● もしこれで Common Lisp に興味を持った方がいれば、後述の参考書籍を見て、本格的に学習してみてください。

Page 43: OpenStack + Common Lisp

43

おすすめ書籍

● 実践Common Lisp● Practical Common Lisp

● 実用 Common Lisp● Paradigms of Artificial Intelligence Programming

● 計算機プログラムの構造と解釈● Structure and Interpretation of Computer Programs

– 英語版がオンラインで読めます。

– http://mitpress.mit.edu/sicp/

● On Lisp● LET OVER LAMBD

Page 44: OpenStack + Common Lisp

44

おすすめサイト

● xyzzy Lisp Programming● http://www.geocities.jp/m_hiroi/xyzzy_lisp.html

● 逆引き CommonLisp● http://tips.lisp-users.org/common-lisp/

● モダン Common Lisp● http://dev.ariel-networks.com/wp/archives/tag/common-lisp