Upload
irixjp
View
4.342
Download
4
Embed Size (px)
Citation preview
1
(Advent Calendar 2012 JP)
(2012.12.24 @irix_jp)
(openstack)(Open source software to build public and private clouds.)
(イヴの夜は OpenStackを(Common Lisp) でキメる! )
(まあ、 (キメなくてもいいんだけどね・・・ (いや、キメてください。 (お願いします。 ))))
2
m9(^Д^) プギャーwww
● イヴの夜にこんな資料作ってるヤツwww
orz
3
気を取り直して・・・
● OpenStack は全ての命令を API サーバが「 ReST形式」で受付けます。
Adminor
UsersHorizon
ClientLibrary(python)
ClientCommand
OpenStackComponent
OtherProgram
APIServer
DB
AMQP
OpenStackComponent
ExternalSoftware
4
ということは・・・
● HTTP のリクエストが投げられれば、何にからでも操作可能!● そう、 Common Lisp からでも!
– おまけで Ruby とか Perl とか Curl とか Wget でも。
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
6
早速動かす
● Clozure CL を使います( Linux 想定)● ここから DL
– http://ccl.clozure.com/download.html● 自分の環境に合わせたものを選択
● 解凍して適当に配置。– /opt/ccl とか
● とりあえず実行してみる– /opt/ccl/lx86cl ・・・ 32bit 環境はこちら– /opt/ccl/lx86cl64 ・・・64 bit 環境はこちら
7
早速動かす
● これで OK
● とりあえず Hello World
● 超簡単!!!
$ /opt/ccl/lx86cl64 Welcome to Clozure Common Lisp Version 1.8-r15286M (LinuxX8664)!? ← これがプロンプト
? "Hello World""Hello World"
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)
9
プロンプト入力が低機能杉www
● 補完も履歴参照もできねーよwww● 案ずるな、バッチ実行できる。
– 適当なファイルを準備● test.lisp
– コマンドラインから
● -Q だんまりモード。プロンプトや起動メッセージを表示しない。● -b バッチモード。標準入力から受け取った式を評価して終了。
(format t "~a" "Hello World")
$ /opt/ccl/lx86cl64 -Q -b < test.lisp Hello WorldNIL
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/
11
パッケージ管理
● Python に PIP 、 Ruby にGEM 、 Perl に CPANがあるように・・・
● Common Lisp にも Quiacklisp というパッケージ管理の仕組みがあります。● サイトはこちら
– http://www.quicklisp.org/
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")
13
パッケージの導入
● HTTP リクエストを投げるパッケージを導入
● 初回のみ、パッケージの DL が実行される。
● ついでに jsonパッケージも
● パッケージの検索
? (ql:quickload :drakma)
? (ql:quickload :cl-json)
? (ql:system-apropos "json")
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)
15
ここから本編
● ようやくだよ!
16
keystoneへリクエストを投げる
● とりあえず何の工夫も無く openstack.lisp
● 実行すると当然エラー( 404 Not Found )● 指定されたリクエスト方式で、ヘッダ・データをちゃんと含める必要がある。
(ql:quickload :drakma)(drakma:http-request "http://cc:5000/v2.0/tokens")
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\"}}}")
18
keystoneへリクエストを投げる
● keystone に認証をかけるには● Content-tyep: application/json で、● データ部に json 形式で以下の内容を含めて、
– テナント名– ユーザ名– パスワード
● http://keystone-address:5000/v2.0/tokens– へ POST する。
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")
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")
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)))
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\"]}}}"
● さっきの関数を実行してみると・・・
できてるっぽい
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..))
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..)
一つカッコが取れる。
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..)
と、上から一つずつ要素が取り出せる。
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"
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"
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"
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")))
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")
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")) .......
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))
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
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
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)
36
コラム2:ベクター
● 同じ型の要素を持ったリスト(配列)をベクターと呼ぶ。● (1 2 3 4 5) や ("a" "b" "c" "d" "e")
● このベクターに対して map 関数のような処理を実行する時に、 CPUが各要素に対する演算を並列に行えるように作られた CPUをベクタープロセッサという● ベクターデータに対する並列計算が得意な CPU● 対して、スカラープロセッサは for で要素を一つずつ取
り出して計算されるイメージ。10年以上前に聞いた話で、記憶も曖昧なので間違ってるかも
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)))
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))
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)))
40
これまでに出てきたこと
● 以下の基礎● リスト操作● 関数● マップ● 条件分岐● ループ
41
さらに学習するには
● マクロ● コンディション(例外処理)● CLOS (オブジェクト)● パッケージ
などなど・・・
42
とりあえずまとめ
● 今回は JSONを一旦リストへ変換して、様々なリスト操作を試してみました。
● マクロやオブジェクトを使うともっと効率的にデータ抽出が可能です。
● もしこれで 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
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