TSC(Time Stamp Counter)の取得と設定 - 備忘録
TSC(Time Stamp Counter)の取得と設定 - 備忘録
仕事でちょっと調べることになったのでメモしておく。
モデル固有レジスタ(MSR)
IA-32 インテル® アーキテクチャ ソフトウェア・デベロッパーズ・ マニュアル 下巻:システム・プログラミング・ガイドには以下のように記載されています。(P.832)
レジスタアドレス (10進) |
レジスタ名 (フィールド) |
モデル利用 | 共有 | ビット説明 |
---|---|---|---|---|
0x10H (16) | IA32_TIME_STAMP_ COUNTER (63:0) | 0, 1, 2, 3 | 独自 | タイムスタンプ・カウンタ。15.7. 節「タイムスタンプ・カウンタ」を参照のこと。タイムスタンプ・カウント値。(R/W) 現在のタイムスタンプ・カウント値を返す。 全64ビットが読み取り可能だが、書き込みできるのは下位32ビットだけである。下位32ビットに書き込みを行なうときは、上位32ビットがクリアされる。 |
msr-toolsを取得してコンパイルする
- kernel.orgからmsr-toolsを取得する。
- コンパイルする
# tar zxf msr-tools-1.1.2.tar.gz && cd msr-tools-1.1.2 # make gcc -Wall -g -O2 -fomit-frame-pointer -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -o wrmsr wrmsr.c gcc -Wall -g -O2 -fomit-frame-pointer -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -o rdmsr rdmsr.c
- rdmsr,wrmsrコマンドのできあがり
# ls -lart total 68 -rwxrwxr-x. 1 1026 1026 231 Oct 10 2001 MAKEDEV-cpuid-msr -rw-rw-r--. 1 1026 1026 2719 Jul 21 2004 wrmsr.c -rw-rw-r--. 1 1026 1026 104 Jul 21 2004 version.h -rw-rw-r--. 1 1026 1026 7105 Jul 21 2004 rdmsr.c -rw-rw-r--. 1 1026 1026 1030 Jul 21 2004 Makefile dr-xr-x---. 4 root root 4096 Jan 5 03:23 .. -rwxr-xr-x. 1 root root 14013 Jan 5 03:24 wrmsr <--コレと... drwxrwxr-x. 2 1026 1026 4096 Jan 5 03:24 . -rwxr-xr-x. 1 root root 19370 Jan 5 03:24 rdmsr <--コレね
MSRからTSCの値を取得する
rdmsrコマンドでMSRからTSCの値を取得する
- この例ではCPU0(-p0)からTSC(0x10h)の値を取得する
# ./rdmsr -u -p0 0x10h 10781437437367
MSRにTSCの値を設定する
wrmsコマンドでMSRにTSCの値をセットする
- この例ではTSCを0に設定する
# ./wrmsr -p0 0x10h 0
じゃ、休み明けから再現テストしようかな。
参考資料
OpenStack ~ Quick Start ~を最新(2013/12/29現在)のopenstack-packstackに対応させる
RDO & Packstackで本当にQuickStartする
このBlogは日本OpenStackユーザ会が主催しているAdvent Calendar 2013の12/29版として、前日の @kkitase さんの12/28版の後を受けて書いています。
インストールにはPackstackを利用しているのですが、結構な頻度でバージョンアップしていて、構築に利用するanswer-fileがすぐに古くなってしまいます。
# | Package Name | Date |
---|---|---|
0 | openstack-packstack-2013.2.1-0.1.dev642.el6.noarch.rpm | 16-Jul-2013 15:20 |
1 | openstack-packstack-2013.2.1-0.1.dev691.el6.noarch.rpm | 01-Aug-2013 12:34 |
2 | openstack-packstack-2013.2.1-0.2.dev702.el6.noarch.rpm | 13-Aug-2013 17:21 |
3 | openstack-packstack-2013.2.1-0.4.dev740.el6.noarch.rpm | 02-Sep-2013 15:36 |
4 | openstack-packstack-2013.2.1-0.6.dev749.el6.noarch.rpm | 09-Sep-2013 10:12 |
5 | openstack-packstack-2013.2.1-0.7.dev752.el6.noarch.rpm | 09-Sep-2013 18:09 |
6 | openstack-packstack-2013.2.1-0.9.dev756.el6.noarch.rpm | 10-Sep-2013 17:06 |
7 | openstack-packstack-2013.2.1-0.10.dev763.el6.noarch.rpm | 24-Sep-2013 17:50 |
8 | openstack-packstack-2013.2.1-0.11.dev806.el6.noarch.rpm | 17-Oct-2013 15:03 |
9 | openstack-packstack-2013.2.1-0.13.dev840.el6.noarch.rpm | 04-Nov-2013 16:25 |
10 | openstack-packstack-2013.2.1-0.15.dev847.el6.noarch.rpm | 14-Nov-2013 15:33 |
11 | openstack-packstack-2013.2.1-0.16.dev870.el6.noarch.rpm | 03-Dec-2013 09:11 |
12 | openstack-packstack-2013.2.1-0.17.dev876.el6.noarch.rpm | 03-Dec-2013 15:47 |
13 | openstack-packstack-2013.2.1-0.25.dev936.el6.noarch.rpm | 20-Dec-2013 13:44 |
そこで、OpenStack QuickStartを改訂(v1.8)して、Packstackとanswer-fileのバージョンを現時点での最新版に決め打ちしてインストール作業を行うことにより、バージョンの差異によるインストールの失敗が発生しないように修正しました。
answer-file
github上では、バージョン番号でtagを打ちました
2013/12/29現在では、以下の2つのバージョンに対応しています。
$ git tag
openstack-packstack-2013.2.1-0.17.dev876
openstack-packstack-2013.2.1-0.25.dev936
パスワードとトークンは変更してね
answer-file(all-in-one.conf, minimal.conf)のデフォルトパスワードは"changeme"にしてあります。
※packstackコマンド実行前に必ず変更するようにしてください。
CONFIG_MYSQL_PW=changeme
CONFIG_QPID_NSS_CERTDB_PW=changeme
CONFIG_QPID_AUTH_PASSWORD=changeme
CONFIG_KEYSTONE_DB_PW=changeme
CONFIG_KEYSTONE_ADMIN_TOKEN=changeme
CONFIG_KEYSTONE_ADMIN_PW=changeme
CONFIG_KEYSTONE_DEMO_PW=changeme
CONFIG_GLANCE_DB_PW=changeme
CONFIG_GLANCE_KS_PW=changeme
CONFIG_CINDER_DB_PW=changeme
CONFIG_CINDER_KS_PW=changeme
CONFIG_NOVA_DB_PW=changeme
CONFIG_NOVA_KS_PW=changeme
CONFIG_NEUTRON_KS_PW=changeme
CONFIG_NEUTRON_DB_PW=changeme
CONFIG_NEUTRON_METADATA_PW=changeme
CONFIG_SWIFT_KS_PW=changeme
CONFIG_SWIFT_HASH=changeme
CONFIG_HEAT_DB_PW=changeme
CONFIG_HEAT_KS_PW=changeme
CONFIG_CEILOMETER_SECRET=changeme
CONFIG_CEILOMETER_KS_PW=changeme
CONFIG_NAGIOS_PW=changeme
all-in-oneホストのIPアドレスの変更をする場合
all-in-oneホストのIPアドレス
以下のパラメータにall-in-oneホストのIPアドレスを指定してください。
VMware Fusionを利用したりしていて、各セグメントのホストアドレスの先頭がHostOSで利用されてしまうため、OpenStack QuickStartに記載されている例とは別のIPアドレスをall-in-oneホストに設定するような場合は変更が必要となります。
CONFIG_MYSQL_HOST=
CONFIG_QPID_HOST=
CONFIG_KEYSTONE_HOST=
CONFIG_GLANCE_HOST=
CONFIG_CINDER_HOST=
CONFIG_NOVA_API_HOST=
CONFIG_NOVA_CERT_HOST=
CONFIG_NOVA_VNCPROXY_HOST=
CONFIG_NOVA_COMPUTE_HOSTS=
CONFIG_NOVA_CONDUCTOR_HOST=
CONFIG_NOVA_SCHED_HOST=
CONFIG_NOVA_NETWORK_HOSTS=
CONFIG_NEUTRON_SERVER_HOST=
CONFIG_NEUTRON_L3_HOSTS=
CONFIG_NEUTRON_DHCP_HOSTS=
CONFIG_NEUTRON_METADATA_HOSTS=
CONFIG_OSCLIENT_HOST=
CONFIG_HORIZON_HOST=
CONFIG_SWIFT_PROXY_HOSTS=
CONFIG_SWIFT_STORAGE_HOSTS=
CONFIG_HEAT_HOST=
CONFIG_HEAT_CLOUDWATCH_HOST=
CONFIG_HEAT_CFN_HOST=
CONFIG_CEILOMETER_HOST=
CONFIG_NAGIOS_HOST=
FloatingIPアドレス
前項と同様、FloatingIPアドレスを変更する場合には、以下のパラメータを変更してください。
CONFIG_NOVA_NETWORK_FLOATRANGE=192.168.0.0/24 <-- ココを適切に変更
CONFIG_PROVISION_DEMO_FLOATRANGE=192.168.0.0/24 <-- ココを適切に変更
まとめ
PackstackでのOpenStack構築は、これで恐らく問題なくQuickStartできるハズです。 みなさんも年末年始、是非お手元でOpenStackを動かしてみてください。 それではみなさん良いお年を:-)
Special Thanks
- 初版から付き合ってくれている横田愛美さん、いつもありがとう
- Okinawa Open Days の開催期間中、夜遅くまでデバッグに付き合ってくれた @ii__ya さん @MTomokim さん
- テストしてくれた @ritchey98 さん @kkitase さん @giraffeforestg さん
- RDO & Packstackの開発に関わっている全てのみなさん
みなさんのおかげでQuickStartできそうです。ありがとうございます。
感謝 m(__)m
続:クリスマスはOpenStackをPythonワンライナーで決める!
続:クリスマスはOpenStackをPythonワンライナーで決める!
みなさんメリークリスマス。
OpenStackのAPIを叩いて仕事をさせる方法
OpenStackの各コンポーネントは、REST-APIを持っています。 このAPIを適切に利用することにより、novaコマンドなどを利用しなくてもOpenStackに仕事をさせることが可能です。
忘れもしない昨年のクリスマスに書いたクリスマスはOpenStackをPythonワンライナーで決める!のワンライナーを例にして、OpenStackをREST-APIで操作する流れを解説したいと思います。そもそも去年書いたときは動作解説も無しだったとは.....
※ワンライナーのコード部分は新たに書こうかとも思いましたが、なんか最近会議ばっかりで、すっかり腕がナマってしまったのであきらめました:)
環境
all-in-one環境を用意してテストしてみました。
------+------------------------ 192.168.0.0/24 (br-ex)
|
|
|.1
+-----------+ +-----------+
| OpenStack | | Client PC |
+-----+-----+ +-----+-----+
|.10 |.1
| |
| |
------+----------------+------- 172.16.0.0/24 (eth1)
1. アクセス用トークンとエンドポイントURLを取得する
まずは、keystoneからトークンを取得する必要があります。このトークンを利用して認可されたエンドポイントにREST-APIでアクセスします。
+-----------+ +-----------+
| client-PC | | keystone |
+-----+-----+ +-----+-----+
|(0) |
|---------(1)-------->|
|<--------(2)---------|
(0) アクセス用環境変数を設定する
export OS_USERNAME=demo
export OS_TENANT_NAME=demo
export OS_PASSWORD=3150a051ead74394
export OS_AUTH_URL=http://172.16.0.10:35357/v2.0
(1) ユーザID,パスワード,テナント名をリクエスト情報に設定てトークンを取得するためのリクエストをkeystoneに送る
- URL: http://172.16.0.10/v2.0/tokens
- Method: POST
リクエスト
{
"auth":{
"tenantName":"demo",
"passwordCredentials":{
"username": "demo",
"password": "3150a051ead74394"
}
}
}
(2) レスポンスの中からこのトークンと認可された操作対象コンポーネントAPIのエンドポイントURLを取得する
取得したエンドポイントの中で、操作したいコンポーネントのURLに対して操作リクエストを送る。 ただし、移行はリクエストヘッダに"X-Auth-Token: トークンの値"を付加することによって認可を受けているリクエストであることを証明する必要がある。
レスポンス
{
"access": {
"token": {
"issued_at": "2013-12-25T12:00:57.818128",
"expires": "2013-12-26T12:00:57Z",
"id": "ココにトークンが入る",
"tenant": {
"enabled": true,
"description": "default tenant",
"name": "demo",
"id": "8b6834b5bb184a809160db3616a4d7f0"
}
},
"serviceCatalog": [{
"endpoints_links": [],
"endpoints": [{"adminURL": "http://172.16.0.10:8774/v2/8b6834b5bb184a809160db3616a4d7f0", "region": "RegionOne", "publicURL": "http://172.16.0.10:8774/v2/8b6834b5bb184a809160db3616a4d7f0", "internalURL": "http://172.16.0.10:8774/v2/8b6834b5bb184a809160db3616a4d7f0", "id": "2821d2cff5d141a8b529ed593d2017bd"}],
"type": "compute",
"name": "nova"
},{
"endpoints_links": [],
"endpoints": [{"adminURL": "http://172.16.0.10:9696/", "region": "RegionOne", "publicURL": "http://172.16.0.10:9696/", "internalURL": "http://172.16.0.10:9696/", "id": "55e53543410449d0b88b3005128373dc"}],
"type": "network",
"name": "neutron"
},{
"endpoints_links": [],
"endpoints": [{"adminURL": "http://172.16.0.10:9292", "region": "RegionOne", "publicURL": "http://172.16.0.10:9292", "internalURL": "http://172.16.0.10:9292", "id": "3b85971ffa9c465d848a4ab36ca1c634"}],
"type": "image",
"name": "glance"
},{
"endpoints_links": [],
"endpoints": [{"adminURL": "http://172.16.0.10:8776/v1/8b6834b5bb184a809160db3616a4d7f0", "region": "RegionOne", "publicURL": "http://172.16.0.10:8776/v1/8b6834b5bb184a809160db3616a4d7f0", "internalURL": "http://172.16.0.10:8776/v1/8b6834b5bb184a809160db3616a4d7f0", "id": "69ddc96be8cd4f4b861cdc83d94d1c2f"}],
"type": "volume",
"name": "cinder"
},{
"endpoints_links": [],
"endpoints": [{"adminURL": "http://172.16.0.10:8773/services/Admin", "region": "RegionOne", "publicURL": "http://172.16.0.10:8773/services/Cloud", "internalURL": "http://172.16.0.10:8773/services/Cloud", "id": "54c30c98b21d4c6ba0aa954a89238e1b"}],
"type": "ec2",
"name": "nova_ec2"
},{
"endpoints_links": [],
"endpoints": [{"adminURL": "http://172.16.0.10:35357/v2.0", "region": "RegionOne", "publicURL": "http://172.16.0.10:5000/v2.0", "internalURL": "http://172.16.0.10:5000/v2.0", "id": "0d9415c8c285442394cd2eccbc8d8563"}],
"type": "identity",
"name": "keystone"
}],
"user": {
"username": "demo",
"roles_links": [],
"id": "87c6f92c39c24d9bae0af37b27c77c51",
"roles":[{"name": "_member_"}],
"name": "demo"
},
"metadata": {
"is_admin": 0,
"roles": ["9fe2ff9ee4384b1894a90878d3e92bab"]
}
}
}
普通に書いたコード
#!/usr/bin/env python
from getpass import getpass
from httplib import HTTPConnection
import json
HOST = "172.16.0.10"
PORT = "35357"
user = raw_input("user: ")
password = getpass("password: ")
tenant = raw_input("tenant: ")
def token(tenant, user, password, session):
token_path = "/v2.0/tokens"
header = { "Content-Type": "application/json" }
request='''{
"auth":{
"tenantName":"%s",
"passwordCredentials":{
"username": "%s",
"password": "%s"
}
}
}''' % (tenant, user, password)
session.request("POST", token_path, request, header)
return json.load(session.getresponse())
if __name__ == "__main__":
session = HTTPConnection("%s:%s" % (HOST, PORT))
auth_result = token(tenant, user, password, session)
session.close()
print json.dumps(auth_result)
##
## [EOF]
##
ワンライナー
特に工夫もなく普通に書いてみた
python -c 'import re,os;from sys import stdout as s;from httplib import HTTPConnection as c;import json as j;(globals().__setitem__("e",os.environ))or(globals().__setitem__("r",r"^(http|https)://(.*)(/.*)$"))or(globals().__setitem__("f0",lambda a,b:re.match(a,b).groups()))or(globals().__setitem__("f1",lambda b:c(b)))or(globals().__setitem__("p",f1(f0(r,e["OS_AUTH_URL"])[1])))or(p.request("POST",f0(r,e["OS_AUTH_URL"])[2]+"/tokens","{\"auth\":{\"tenantName\":\"%s\",\"passwordCredentials\":{\"username\":\"%s\",\"password\":\"%s\"}}}"%(e["OS_TENANT_NAME"],e["OS_USERNAME"],e["OS_PASSWORD"]),{"Content-Type":"application/json"}))or(s.write(j.dumps(j.load(p.getresponse()),indent=2)))or(p.close())'
2.仮想マシンのリストをnova-apiから取得してみる
前項「アクセス用トークンとエンドポイントURLを取得する」の方法で取得したトークンを使用して、novaが管理している仮想マシンのリストを取得してみます。 他の操作(nova以外)でも基本的には受け取ったトークンを利用してエンドポイントURLにREST-APIでアクセスするという操作は変わりません。
+-----------+ +-----------+
| client-PC | | nova |
+-----+-----+ +-----+-----+
|(0) |
|---------(1)-------->|
|<--------(2)---------|
|---------(3)-------->|
|<--------(4)---------|
(0) アクセス用環境変数を設定する
前項と同様
(1) ユーザID,パスワード,テナント名をリクエスト情報に設定してトークンを取得するためのリクエストをkeystoneに送る
前項と同様
(2) レスポンスの中からこのトークンと認可された操作対象コンポーネントAPIのエンドポイントURLを取得する
前項と同様
(3) エンドポイントURLの中から"compute"用のエンドポイントURLに向けてリクエストを送信
今回のリスト取得対象となるのは権限を持っているテナントに所属している仮想マシンのみです。
- URL: http://172.16.0.10:8774/v2/8b6834b5bb184a809160db3616a4d7f0/servers
- Method: GET
リクエストヘッダ
{
"Content-Type": "application/json",
"X-Auth-Token": "取得したトークンをセット"
}
(4) 仮想マシンリストをレスポンスとして受け取る
demoユーザの管理下にある仮想マシン 2台(server1/server2)が取得できました。
{
"servers": [
{
"id": "31e65072-defa-419f-a019-6a6a316fc2ee",
"links": [
{
"href": "http://172.16.0.10:8774/v2/8b6834b5bb184a809160db3616a4d7f0/servers/31e65072-defa-419f-a019-6a6a316fc2ee",
"rel": "self"
},
{
"href": "http://172.16.0.10:8774/8b6834b5bb184a809160db3616a4d7f0/servers/31e65072-defa-419f-a019-6a6a316fc2ee",
"rel": "bookmark"
}
],
"name": "server2"
},
{
"id": "fad29e21-831c-41e1-b4c7-6df6b8362f6f",
"links": [
{
"href": "http://172.16.0.10:8774/v2/8b6834b5bb184a809160db3616a4d7f0/servers/fad29e21-831c-41e1-b4c7-6df6b8362f6f",
"rel": "self"
},
{
"href": "http://172.16.0.10:8774/8b6834b5bb184a809160db3616a4d7f0/servers/fad29e21-831c-41e1-b4c7-6df6b8362f6f",
"rel": "bookmark"
}
],
"name": "server1"
}
]
}
ワンライナー
setitemとかlambdaを使ってそれらしく書いてみた。タプルの戻り値の副作用を使って or でパイプライン的に処理しています。
$ python -c 'import re,os;from sys import stdout as s;from httplib import HTTPConnection as c;import json as j;(globals().__setitem__("e",os.environ))or(globals().__setitem__("r0",r"^(http|https)://(.*)(/.*)$"))or(globals().__setitem__("r1",r"^(http|https)://(.*:\d+)(/.*)$"))or(globals().__setitem__("f0",lambda a,b:re.match(a,b).groups()))or(globals().__setitem__("f1",lambda b:c(b)))or(globals().__setitem__("p",f1(f0(r0,e["OS_AUTH_URL"])[1])))or(p.request("POST",f0(r0,e["OS_AUTH_URL"])[2]+"/tokens","{\"auth\":{\"tenantName\":\"%s\",\"passwordCredentials\":{\"username\":\"%s\",\"password\":\"%s\"}}}"%(e["OS_TENANT_NAME"],e["OS_USERNAME"],e["OS_PASSWORD"]),{"Content-Type":"application/json"}))or(globals().__setitem__("z",j.load(p.getresponse())))or(globals().__setitem__("t",z["access"]["token"]["id"]))or(globals().__setitem__("u",f0(r1,[z["endpoints"][0]["publicURL"] for z in z["access"]["serviceCatalog"] if z["type"]=="compute"][0])))or(p.close())or(globals().__setitem__("p",f1(u[1])))or(p.request("GET",u[2]+"/servers","",{"Content-Type":"application/json","X-Auth-Token":t}))or(s.write(j.dumps(j.load(p.getresponse()),indent=2)))or(p.close())'
テナント(プロジェクト)情報を確認する(おまけ)
admin_tokenの使いどころ
一般ユーザがAPI経由でOpenStackを操作するためのtokenを取得するためには、対象ユーザが所属するテナント情報が必要となります。そのテナント情報は、、、もちろんあらかじめ管理者から知らされている訳です。 では、管理者が自身の管理下にあるテナントの情報を取得するためには、どうしたら良いのでしょうか?
/etc/keystone.conf内で設定しているadmin_tokenの使いどころはココです!
以下のワンライナーではkeystone.conf内で定義したadmin_tokenを環境変数のADMIN_TOKENの値として設定しています。
リクエストヘッダ
{
"Content-Type": "application/json",
"X-Auth-Token": "トークン"",
}
レスポンス
{
"tenants":
[
{
"enabled": true,
"description": "Tenant for the openstack services",
"name": "services",
"id": "0b30ec27e81345a4b34d95e10fe8cbc7"
},
{
"enabled": true,
"description": "alt tenant",
"name": "alt_demo",
"id": "213da8890d644b3d9420ede8c4fb0a38"
},
{
"enabled": true,
"description": "demo1",
"name": "demo1",
"id": "554bed77ffbb4c14b678ff3c27d76566"
},
{
"enabled": true,
"description": "default tenant",
"name": "demo",
"id": "8b6834b5bb184a809160db3616a4d7f0"
},
{
"enabled": true,
"description": "admin tenant",
"name": "admin",
"id": "ccd340020fbd4ee79cfd376fc447212a"
}
],
"tenants_links": []
}
普通に書いたコード
- Header情報にトークンをセットする
- http://172.16.0.10/v2.0/tenantsにGETリクエストを投げる
- レスポンスはJSON形式で返される
#!/usr/bin/env python
from getpass import getpass
from httplib import HTTPConnection
import json
HOST = "172.16.0.10"
PORT = "35357"
token = "044b77537ed54c2dba5d71906d2458ec"
def tenants(id, session):
tenant_path = "/v2.0/tenants"
header = {
"Content-Type": "application/json",
"X-Auth-Token": token,
}
print json.dumps(header)
session.request("GET", tenant_path, "", header)
return json.load(session.getresponse())
if __name__ == "__main__":
session = HTTPConnection("%s:%s" % (HOST, PORT))
tenant_list = tenants(id, session)
session.close()
print json.dumps(tenant_list)
##
## [EOF]
##
まとめ
APIへのリクエストをまとめて管理してくれるリクエストブローカー的な仕組みがあるとコードが利用するためのコードを書きやすいんだけどな。 それぞれのコンポーネントAPIがエンドポイントを持つというのは、ひと手間かかりますね。
はてなダイアリーから はてなブログに引越し
やっぱりMarkdownで書けるのが大きいので引越しました:)
Ansible Quickstart書いた
12月4日 クラウドマネジメントツール勉強会 第2回(東京都)でAnsibleについて話します。
ということで、Ansible quickstartを書いてみました。
他のひとのネタとかぶらないといいなぁ
RDO & packstack & VirtualBox でOpenStackを簡単インストール
RDO & packstack & VirtualBox でOpenStackを簡単インストール
とある場所で話すことになったので書いてみた。
おれ、次はちゃんと2週間前くらいから書き始めるんだ...
OpenStack QuickStart - havana
AnsibleのJUNOS設定モジュール書いてみた(VLAN編)
Ansibleのお勉強のためにJUNOS向けにNETCONFでVLANを作成・削除するモジュールを書いてみた。
動いてるけど、VC環境だとcommit syncするときにタイムアウトするかも。
Ex3300とEx2200で動作確認した。意外にちゃんとうごいて冪等性も担保できてる。
引数
- state: 作成する時(present),削除する時(absent)
- node: 操作対象ネットワーク機器のノード名またはIPアドレス
- port: 操作対象ネットワーク機器がNETCONF用に口をあけているポート(デフォルト:830)
- user: 操作対象ネットワーク機器のログインアカウント
- password: 操作対象ネットワーク機器のパスワード
- vlan_name: VLAN名
- vlan_id: VLANID
- vlan_desc: VLANのDescription
テストしてみる
VLANを作成する
$ ~/ansible/hacking/test-module -m ./junos_vlan -a "node='switch00' port=830 user='foo' password='bar' state='present' vlan_name='VLAN3000' vlan_id=3000 vlan_desc='test vlan'"
VLANを削除する
もちろん対象VLANが存在しなければ何もしない。
$ ~/ansible/hacking/test-module -m ./junos_vlan -a "node='switch00' port=830 user='foo' password='bar' state='absent' vlan_name='VLAN3000' vlan_id=3000 vlan_desc='test vlan'"
モジュール
#!/usr/bin/env python # -*- coding: utf-8 -*- DOCUMENTATION = ''' --- module: junos_vlan author: Hideki Saito short_description: Manage VLAN resources requirements: - ncclient v0.3.2 description: - Manage VLAN resources on JUNOS devices. This module requires the ncclient. [link] http://ncclient.grnet.gr/ options: node: desctiption - the hostname or IP address to connect requred: true port: description - the port number to connect user: description - junos user id required: true password: description - junos user password requre: true vlan_name: description: - name of vlan required: false vlan_id: description: - the vlan id required: true vlan_desc: description: - a descriptive name for the vlan required: false state: description: - describe the desired state of the vlan related to the config required: false default: 'present' choices: [ 'present', 'absent' ] logging: description: - enables or disables the syslog facility for this module required: false choices: [ 'true', 'false', 'yes', 'no' ] notes: - The ncclient module must be installed. - See http://ncclient.grnet.gr/ for details ''' import json from jinja2 import Template from ncclient import manager from ncclient.operations.errors import TimeoutExpiredError from xml.etree import ElementTree create_vlan = """ <config> <configuration> <vlans> <vlan operation="create"> <name>{{ vlan_name }}</name> <description>{{ vlan_desc }}</description> <vlan-id>{{ vlan_id }}</vlan-id> </vlan> </vlans> </configuration> </config> """ delete_vlan = """ <config> <configuration> <vlans> <vlan operation="delete"> <name>{{ vlan_name }}</name> <vlan-id>{{ vlan_id }}</vlan-id> </vlan> </vlans> </configuration> </config> """ set_description = """ <config> <configuration> <vlans> <vlan> <name>{{ vlan_name }}</name> <description>{{ vlan_desc }}</description> </vlan> </vlans> </configuration> </config> """ class JunosVlan(object): def __init__(self, module): self.module = module self.node = module.params['node'] self.port = module.params['port'] self.user = module.params['user'] self.password = module.params['password'] self.vlan_name = module.params['vlan_name'] self.vlan_id = module.params['vlan_id'] self.vlan_desc = module.params['vlan_desc'] self.state = module.params['state'] with manager.connect(host=self.node, port=self.port, username=self.user, password=self.password, hostkey_verify=False) as m: self.config = m.get_config(source='candidate').data_xml def push(self, config): try: with manager.connect(host=self.node, port=self.port, username=self.user, password=self.password, unknown_host_cb=_always_unknown_true) as mgr: mgr.edit_config(target="candidate", config=config, test_option="test-then-set") mgr.commit() rc = 0 out = 'commit succeeded' err = '' except TimeoutExpiredError as e: rc = 1 out = 'commit failure' err = 'operation timeout' return (rc, out, err) def vlans(self): elm = ElementTree.fromstring(self.config) elm_vlans = elm.find('.//vlans') vlan_info = dict() for elm_vlan in elm_vlans.findall('.//vlan'): vlan_id = elm_vlan.findtext('.//vlan-id') name = elm_vlan.findtext('.//name') description = elm_vlan.findtext('.//description') vlan_info[name] = dict(vlan_id=vlan_id, description=description) return vlan_info.keys() def _always_unknown_true(host, fingerprint): return True def main(): module = AnsibleModule( argument_spec = dict( node=dict(default=None, requred=True, type='str'), port=dict(default=830, requred=True, type='int'), user=dict(default=None, requred=True, type='str'), password=dict(default=None, requred=True, type='str'), vlan_name=dict(default=None, required=True, type='str'), vlan_id=dict(default=None, type='int'), vlan_desc=dict(default=None, type='str'), state=dict(default='present', choices=['present', 'absent']), ), supports_check_mode = True ) obj = JunosVlan(module) rc = None result = dict() changed = False if obj.state == 'absent': if obj.vlan_name in obj.vlans(): template = Template(delete_vlan) config = template.render(vlan_name=obj.vlan_name, vlan_id=obj.vlan_id) if module.check_mode: module.exit_json(changed=True) (rc, out, err) = obj.push(config) result['results'] = out elif obj.state == 'present': if obj.vlan_name in obj.vlans(): template = Template(set_description) config = template.render(vlan_name=obj.vlan_name, vlan_desc=obj.vlan_desc) else: template = Template(create_vlan) config = template.render(vlan_name=obj.vlan_name, vlan_id=obj.vlan_id, vlan_desc=obj.vlan_desc) if module.check_mode: module.exit_json(changed=True) (rc, out, err) = obj.push(config) result['results'] = out if rc is not None and rc != 0: module.fail_json(msg=err, rc=rc) if rc is None: result['changed'] = False else: result['changed'] = True module.exit_json(**result) # include magic from lib/ansible/module_common.py #<<INCLUDE_ANSIBLE_MODULE_COMMON>> main() ## ## [EOF] ##