はじめに
この記事はシスコの有志による Cisco Systems Japan Advent Calendar 2018 の 10 日目として投稿しています。
- 2017年版: https://qiita.com/advent-calendar/2017/cisco
- 2018年版: https://qiita.com/advent-calendar/2018/cisco
目次
データセンターとは
データセンターとは、サーバーやネットワーク機器などのIT機器を設置、運用する施設・建物の総称です。
(参考) 用語集|データセンター (IDC フロンティア様ホームページから)
実際の雰囲気は、Googleさんのデータセンターの紹介ビデオをご覧ください。
ご覧いただいてわかる通り、下記の様な特徴が言えます。
- 窓がない
- 厳重なセキュリティで守られている
こんな現場で働いている人には、こんな悩みがあります。
休憩で外に出たら、雨が降っていた。。。
傘を取りに戻るには、たくさんのセキュリティゲートを通過する必要がある。。
外に出る前に天気が分かればいいのに。。。。
外に出る直前、手元にあるのは、ネットワーク機器と、操作する端末のみ。。
このネットワーク機器で天気予報を参照しよう!!
ということで、実装してみました。
今回作ったもの
データセンター向けスイッチである Cisco Nexus 9000 シリーズ上で、天気予報を見れる様にしました。
ただ表示するだけではなく、他の設定と、同様にパラメータを設定できる様にしました。
今回利用した機器
-
Cisco Nexus 92160YC-X (N9K-C92160YC-X)
- version 9.2(2)
show commandとは
Ciscoをはじめとする、様々なネットワーク機器には、状況を確認するための show というコマンドが実装されております。
こちらを利用して、稼働状況や実際の設定などを参照し、機器を運用いたします。
NX-SDK
NX-SDKは、Cisco Nexus 9000 上で任意のアプリケーションを稼働させるためのSDKです。
C++で書かれたライブラリが提供されているため、これらを利用することが可能です。
また、PythonやGoなどに向けライブラリもあり、様々な言語で実装できるのもポイントです。
詳細は、昨年のCisco Advent Calendar の こちらの記事 をご覧ください。
プログラムを書く
実際のプログラムは、こちらです。
weatherPyApp.pyの本体
#!/isan/bin/nxpython
# -*- coding:utf-8 -*-
################################################################
# File: Show Weather
#
##################################################################
import signal
import time
import threading
import sys
import commands
import json
### Imports NX-OS SDK package
import nx_sdk_py
def get_location_id(location = "tokyo"):
location_code = {
"tokyo": "130010",
"sapporo": "016010",
"sendai": "040010",
"nagoya": "230010",
"osaka": "270000",
"hiroshima": "340010",
"fukuoka": "400010"
}
return location_code.get(location, "130010")
def get_weather(location = "tokyo", proxy = "", vrf = "default"):
global tmsg
command = "ip netns exec " + vrf
base_url = "http://weather.livedoor.com/forecast/webservice/json/v1?city="
url = base_url + get_location_id(location)
command += " curl -s " + url
if proxy != "":
command += " -x " + proxy
result = commands.getoutput(command)
if len(result) == 0:
return None
return json.loads(result)
def weather_detail(result):
location = result["location"]["city"].encode('utf-8')
summary = result["forecasts"][0]['telop'].encode('utf-8')
max_temp = result["forecasts"][0]['temperature']['max']
if max_temp is not None:
max_temp = float(max_temp['celsius'])
min_temp = result["forecasts"][0]['temperature']['min']
if min_temp is not None:
min_temp = float(min_temp['celsius'])
link = result["link"].encode('utf-8')
message = "今日の%sの天気は、%s\n" % (location, summary)
if max_temp is not None:
message += "最高気温は、%.2f度\n" % max_temp
if min_temp is not None:
message += "最低気温は、%.2f度\n" % min_temp
message += link + '\n'
return message
def weather_summary(result):
return result["forecasts"][0]['telop'].encode('utf-8')
class pyCmdHandler(nx_sdk_py.NxCmdHandler):
location = "tokyo"
vrf = "default"
proxy = ""
def postCliCb(self,clicmd):
global cliP, location
if "show_weather" in clicmd.getCmdName():
weather_result = get_weather(self.location, self.proxy, self.vrf)
if weather_result is None:
clicmd.printConsole("Could Not access weather API\n")
return False
message = ""
if "detail" in clicmd.getCmdLineStr():
message = weather_detail(weather_result)
else:
message = weather_summary(weather_result) + "\n"
clicmd.printConsole(message)
elif "config_weather_location" in clicmd.getCmdName():
if "no" in clicmd.getCmdLineStr():
self.location = "tokyo";
else:
self.location = nx_sdk_py.void_to_string(clicmd.getParamValue("<location>"))
elif "config_weather_vrf" in clicmd.getCmdName():
if "no" in clicmd.getCmdLineStr():
self.vrf = "default";
else:
temp_vrf = nx_sdk_py.void_to_string(clicmd.getParamValue("<vrf>"))
if temp_vrf == "all":
clicmd.printConsole("Can not configure VRF to all")
tmsg.event("Can not configure VRF to all")
return False
self.vrf = temp_vrf
elif "config_weather_proxy" in clicmd.getCmdName():
if "no" in clicmd.getCmdLineStr():
self.proxy = "";
else:
self.proxy = nx_sdk_py.void_to_string(clicmd.getParamValue("<proxy>"))
return True
def sdkThread(name,val):
global cliP, sdk, event_hdlr, tmsg
sdk = nx_sdk_py.NxSdk.getSdkInst(len(sys.argv), sys.argv)
if not sdk:
return
sdk.setAppDesc('Weather App for Nexus')
tmsg = sdk.getTracer()
tmsg.event("[%s] Started service" % sdk.getAppName())
cliP = sdk.getCliParser()
nxcmd = cliP.newShowCmd("show_weather", "[detail]")
nxcmd.updateKeyword("detail", "For Detail Weather Information")
nxcmd1 = cliP.newConfigCmd("config_weather_location", "location <location>")
nxcmd1.updateKeyword("location", "Location to check weather")
str_attr = nx_sdk_py.cli_param_type_string_attr()
str_attr.length = 25;
str_attr.regex_pattern = "^[a-zA-Z]+$";
nxcmd1.updateParam("<location>", "Location Name", nx_sdk_py.P_STRING, str_attr, len(str_attr))
nxcmd2 = cliP.newConfigCmd("config_weather_vrf", "use-vrf <vrf>")
nxcmd2.updateKeyword("use-vrf", "Configures Weather to use the selected VRF to fetch weather information")
nxcmd2.updateParam("<vrf>", "VRF name", nx_sdk_py.P_VRF)
nxcmd3 = cliP.newConfigCmd("config_weather_proxy", "proxy <proxy>")
nxcmd3.updateKeyword("proxy", "Configures Weather to use the proxy server Ex) proxy.esl.cisco.com:8080 ")
str_attr_url = nx_sdk_py.cli_param_type_string_attr()
str_attr_url.length = 128;
str_attr_url.regex_pattern = "^([^/ :]+):?([0-9]*)$";
nxcmd3.updateParam("<proxy>", "Proxy Server", nx_sdk_py.P_STRING, str_attr_url, len(str_attr_url))
mycmd = pyCmdHandler()
cliP.setCmdHandler(mycmd)
cliP.addToParseTree()
sdk.startEventLoop()
tmsg.event("Service Quitting...!")
nx_sdk_py.NxSdk.__swig_destroy__(sdk)
cliP = 0
sdk = 0
tmsg = 0
### create a new sdkThread to setup SDK service and handle events.
sdk_thread = threading.Thread(target=sdkThread, args=("sdkThread",0))
sdk_thread.start()
sdk_thread.join()
長いですね。。。。
ポイントを何点か。
新しいコマンドの定義
実際にshow commandと config commandを作成しているところをピックアップしてみました。
nxcmd = cliP.newShowCmd("show_weather", "[detail]")
nxcmd.updateKeyword("detail", "For Detail Weather Information")
nxcmd1 = cliP.newConfigCmd("config_weather_location", "location <location>")
nxcmd1.updateKeyword("location", "Location to check weather")
str_attr = nx_sdk_py.cli_param_type_string_attr()
str_attr.length = 25;
str_attr.regex_pattern = "^[a-zA-Z]+$";
nxcmd1.updateParam("<location>", "Location Name", nx_sdk_py.P_STRING, str_attr, len(str_attr))
newShowCmd
と newConfigCmd
が、新しいコマンドを定義するメソッドです。
第1引数で指定しているものが、内部で呼び出されるコマンド名です。
第2引数には、それ以降に指定するオプションを入力いたします。
"show_weather"
の場合、"[detail]"
とすることで、detailがある場合、ない場合の両方を同時に定義しています。
show + このプログラム名
というコマンドを実行した時に、このプログラムが呼び出されます。
updateKeyword
は、コマンドを実行する際の説明を定義するメソッドです。
コマンドを途中まで入力し、"?"を入力することで、コマンドの説明が表示されますが、そちらで利用されます。
n92160-01# show weather ?
<CR>
> Redirect it to a file
>> Redirect it to a file in append mode
detail For Detail Weather Information
nxsdk Auto-generated commands by NXSDK
| Pipe command output to filter
updateParam
が実際にconfigを設定した際のパラメータになります。
nx_sdk_py.cli_param_type_string_attr()
が、パラメータの制限事項を書きます。
正規表現を利用することで、こちらで入力する文字列の制限が可能です。
文字列以外にも、integer (数値)なども制限することができます。
詳細は、ソースコード (include/types/nx_cli.h )をご覧ください。
新しいコマンドの動作の定義
上の方法で、各コマンドの定義をしました。
それでは、こちらで実際にコマンドを叩いた時の動作を定義します。
def postCliCb(self,clicmd):
global cliP, location
if "show_weather" in clicmd.getCmdName():
weather_result = get_weather(self.location, self.proxy, self.vrf)
## 省略 ##
message = weather_summary(weather_result) + "\n"
clicmd.printConsole(message)
elif "config_weather_location" in clicmd.getCmdName():
if "no" in clicmd.getCmdLineStr():
self.location = "tokyo";
else:
self.location = nx_sdk_py.void_to_string(clicmd.getParamValue("<location>"))
このプログラム内で定義されたコマンド (show コマンドも config コマンドも)は、どちらも同一のメソッド postCliCb
で呼び出されます。
そのため、clicmd.getCmdName()
を利用し、実際に叩かれたコマンドを取得しています。
それに応じて条件を分岐させて、処理をしています。
画面に出力するには、clicmd.printConsole
が利用可能です。
また、設定したパラメータの内容を取得するのには、nx_sdk_py.void_to_string
を利用します。
global変数を利用して、パラメータを保持することも可能ですが、
他のメソッドやクラスから変数を参照することがないので、今回は、インスタンス変数として保持しています。
また、config時に"no"が含まれていたら、デフォルト値 (locationの場合は、"tokyo")を設定する箇所も自分で作ります。
VRFを意識して、外部接続
Nexus上には、複数のVRFができ、それぞれ別のRoutingテーブルを持ちます。
インターネットアクセスする際には、こちらを意識する必要があるが、
なかなか良い方法が見つからず、python上から bash上のcurlを実行することで任意のwebページを取得しています。
天気予報は、Weather Hacksを利用しています。
プログラムを動かす
自作したプログラムを実行するには、下記手順が必要となります。
1. 必要な機能を有効にする
NX-SDKを利用するには、bashとNX-SDKを有効にする必要があります。
n92160-01# conf t
Enter configuration commands, one per line. End with CNTL/Z.
n92160-01(config)# feature nxsdk
n92160-01(config)# feature bash-shell
2. プログラムをNexus上にコピーする
前の章で作ったプログラムをNexusに転送します。
n92160-01# copy tftp://x.x.x.x/weatherPyApp.py bootflash://weather
この際にポイントになるのは、ファイル名がそのまま、コマンドになるので、
拡張子などを取り除き、weather だけにしてます。
3. Bashにログインする
Nexus上からBashにログインするには、下記コマンドを実行します。
n92160-01# run bash sudo su
実際にプログラムを起動させる際に、rootになる必要があるため、sudo su
を付け加えてます。
4. Bash上でプログラムを実行する
NexusのBash上でプログラムを実行します。
ただし、Bashから抜け出しても、プログラムを継続させるために、nohup
と &
を利用しています。
bash-4.3# nohup /isan/bin/python /bootflash/weather &
こちらで自作のコマンドを動かすことができました。
本プログラムの使い方
show コマンド
2種類の出力が可能です。
n92160-01# show weather
曇時々晴
n92160-01# show weather detail
今日の東京の天気は、晴時々曇
最高気温は、10.00度
最低気温は、5.00度
http://weather.livedoor.com/area/forecast/130010
detailをつけると、場所、気温(取得できれば)、ソースのURLを追加して表示します。
config コマンド
3つの項目を設定可能にしました。
n92160-01(config)# weather ?
location Location to check weather
proxy Configures Weather to use the proxy server Ex)
proxy.esl.cisco.com:8080
use-vrf Configures Weather to use the selected VRF to fetch weather
information
天気予報を表示する場所をlocation
情報を取得するために利用するweb proxy serverをproxy
情報を取得するVRFを指定するuse-vrf
を
定義しています。
location
は、シスコシステムズ合同会社のオフィスがある都市を指定可能です。
シスコの日本のオフィス 一覧
さいごに
Cisco Nexus 9000シリーズにある、自作のコマンドを作成できるNX-SDKを紹介いたしました。
他にも様々なAPI/SDKや実行環境を持っているNexusを活用して、日々の運用を楽しくしてください!
参考
GitHub NX-SDK
Cisco Nexus 9000 Series NX-OS Programmability Guide, Release 9.x
DEVNET Open NX-OS
本当はrpmでパッケージして配布したかったが、なぜか今回の環境ではうまく動かず。。。。
免責事項
本サイトおよび対応するコメントにおいて表明される意見は、投稿者本人の個人的意見であり、シスコの意見ではありません。本サイトの内容は、情報の提供のみを目的として掲載されており、シスコや他の関係者による推奨や表明を目的としたものではありません。各利用者は、本Webサイトへの掲載により、投稿、リンクその他の方法でアップロードした全ての情報の内容に対して全責任を負い、本Web サイトの利用に関するあらゆる責任からシスコを免責することに同意したものとします。