More Related Content Similar to OpenStack + Common Lisp Similar to OpenStack + Common Lisp (20) OpenStack + Common Lisp1. (Advent Calendar 2012 JP)
(openstack)
(Open source software to build public and private clouds.)
( イヴの夜は OpenStack を
(Common Lisp) でキメる! )
( まあ、 ( キメなくてもいいんだけどね・・・ ( いや、キメてください。 ( お願いします。 ))))
(2012.12.24 @irix_jp)
1
3. 気を取り直して・・・
● OpenStack は全ての命令を API サーバが「 ReST
形式」で受付けます。
Client
Command DB
Admin
or
Users OpenStack
Client
Horizon Component
Library
(python) API
Server AMQP
OpenStack
Component
Other External
Program Software
3
4. ということは・・・
● HTTP のリクエストが投げられれば、何にからでも
操作可能!
● そう、 Common Lisp からでも!
– おまけで Ruby とか Perl とか Curl とか Wget でも。
4
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 5
6. 早速動かす
● Clozure CL を使います( Linux 想定)
● ここから DL
– http://ccl.clozure.com/download.html
● 自分の環境に合わせたものを選択
● 解凍して適当に配置。
– /opt/ccl とか
● とりあえず実行してみる
– /opt/ccl/lx86cl ・・・ 32bit 環境はこちら
– /opt/ccl/lx86cl64 ・・・64 bit 環境はこちら
6
7. 早速動かす
● これで OK
$ /opt/ccl/lx86cl64
Welcome to Clozure Common Lisp Version 1.8-r15286M (LinuxX8664)!
? ← これがプロンプト
● とりあえず Hello World
? "Hello World"
"Hello World"
● 超簡単!!!
7
8. もう少し Lisp っぽく
● format 関数を使った例
? (format t "~a" "Hello World")
Hello World
NIL
● エラーしたら
? (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
Debbuger が起動するので冷静に「 q 」
● 終了
? (quit)
8
9. プロンプト入力が低機能杉www
● 補完も履歴参照もできねーよwww
● 案ずるな、バッチ実行できる。
– 適当なファイルを準備
● test.lisp
(format t "~a" "Hello World")
– コマンドラインから
$ /opt/ccl/lx86cl64 -Q -b < test.lisp
Hello World
NIL
● -Q だんまりモード。プロンプトや起動メッセージを表示しない。
● -b バッチモード。標準入力から受け取った式を評価して終了。
9
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/
10
11. パッケージ管理
● Python に PIP 、 Ruby に GEM 、 Perl に CPAN が
あるように・・・
● Common Lisp にも Quiacklisp というパッケージ
管理の仕組みがあります。
● サイトはこちら
– http://www.quicklisp.org/
11
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)
. プロキシを経由する場合 (quicklisp-quickstart:install :proxy "http://192.168.128.254:8080")
..
...
? (ql:add-to-init-file)
.
..
...
? (quit)
12
13. パッケージの導入
● HTTP リクエストを投げるパッケージを導入
? (ql:quickload :drakma)
● 初回のみ、パッケージの DL が実行される。
● ついでに json パッケージも
? (ql:quickload :cl-json)
● パッケージの検索
? (ql:system-apropos "json")
13
14. drakma がエラーするヤツ
● OS の環境によってはエラーが起きる
[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.
● libssl のファイル名の問題だけなので、適当にリン
ク貼ればおk
$ find / |grep libssl.so
$ sudo ln -s /usr/lib64/libssl.so.10 /usr/lib64/libssl.so
● 再実行
? (ql:quickload :drakma)
14
16. keystone へリクエストを投げる
● とりあえず何の工夫も無く openstack.lisp
(ql:quickload :drakma)
(drakma:http-request "http://cc:5000/v2.0/tokens")
● 実行すると当然エラー( 404 Not Found )
● 指定されたリクエスト方式で、ヘッダ・データをちゃんと
含める必要がある。
16
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"}}}")
17
18. keystone へリクエストを投げる
● keystone に認証をかけるには
● Content-tyep: application/json で、
● データ部に json 形式で以下の内容を含めて、
– テナント名
– ユーザ名
– パスワード
● http://keystone-address:5000/v2.0/tokens
– へ POST する。
18
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")
19
20. keystone へリクエストを投げる
● 前のページの続き
● リクエストを投げる関数も作る
(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")
● これを実行すると、それっぽい JSON が返ってくる(と思
う
20
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)))
21
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/", "i
d": "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.13
3.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": "dadd326ef5904cecaf4a058a73
4003f2", "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"]}}}"
できてるっぽい 22
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..))
23
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..)
一つカッコが取れる。
24
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..)
と、上から一つずつ要素が取り出せる。
25
26. リスト操作
● ここから TOKEN を取り出してみる。
(cdr
(third
(second
(first
(json:decode-json-from-string (auth-post-json "http://cc-myvps:5000/v2.0/tokens" "demo" "demo" "^openstack2012$"))))))
→ "ce26db6126e342a8b604019463f34bb7"
● 芸はないけど、こんな感じで TOKEN が取り出せ
る。
● しかしこの方法は場所決め打ちで取り出しているので、
汎用性がなさすぎる。
– TOKEN の場所が
● :TOKEN → :ID にあることを利用すると・・・
26
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"
27
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"
28
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")))
29
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")
30
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"))
.......
31
32. エンドポイントの抽出
● まずエンドポイントの要素1つを調べて、それが欲し
いデータならば t(true) を返す関数を作る
(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))
(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
32
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
33
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
34
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)
35
36. コラム2:ベクター
● 同じ型の要素を持ったリスト(配列)をベクターと呼
ぶ。
● (1 2 3 4 5) や ("a" "b" "c" "d" "e")
● このベクターに対して map 関数のような処理を実
行する時に、 CPU が各要素に対する演算を並列に
行えるように作られた CPU をベクタープロセッサと
いう
● ベクターデータに対する並列計算が得意な CPU
● 対して、スカラープロセッサは for で要素を一つずつ取
り出して計算されるイメージ。
10 年以上前に聞いた話で、記憶も曖昧なので間違ってるかも 36
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)))
37
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))
38
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)))
39
41. さらに学習するには
● マクロ
● コンディション(例外処理)
● CLOS (オブジェクト)
● パッケージ
などなど・・・
41
42. とりあえずまとめ
● 今回は JSON を一旦リストへ変換して、様々なリス
ト操作を試してみました。
● マクロやオブジェクトを使うともっと効率的にデータ
抽出が可能です。
● もしこれで Common Lisp に興味を持った方がい
れば、後述の参考書籍を見て、本格的に学習してみ
てください。
42
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
43
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
44