例:カスタム YANG RPC を使用して Junos デバイスの運用情報を取得する
Junos デバイスでカスタム RPC を定義する YANG データ モデルを追加できます。カスタム RPC を作成すると、デバイス上の特定の運用タスクに合わせて、入力パラメーターと操作、出力フィールドと書式を正確に定義できます。この例では、デバイスから運用情報を取得し、カスタマイズされた CLI 出力を表示するカスタム RPC およびアクション スクリプトを示します。
RPC は、デバイス上の Junos OS スキーマに追加されます。RPC が CLI で実行されると、要求された物理インターフェイスの名前と運用ステータスが出力されます。
要件
この例では、以下のハードウェアとソフトウェアのコンポーネントを使用しています。
-
カスタムYANGデータモデルの読み込みをサポートするJunos OSリリース17.3R1以降を実行するデバイス。
RPC およびアクション スクリプトの概要
この例の YANG モジュールは、特定の物理インターフェイスの名前と運用ステータスを返すカスタム RPC を定義しています。YANGモジュール rpc-interface-status は、 rpc-interface-status.yang ファイルに保存されます。モジュールは、デバイス上でカスタム RPC を実行し、CLI 出力をカスタマイズするために必要な拡張機能を提供する Junos OS 拡張モジュールをインポートします。
モジュールは RPC を定義しますget-interface-status。<get-interface-status>リクエストタグは、デバイス上でRPCをリモートで実行するために使用されます。RPC 定義では、 junos:command ステートメントは、CLI で RPC を実行するために使用される コマンドを定義します。この場合は.show intf status
および junos:script ステートメントはjunos:action-execute、RPC の実行時に呼び出されるアクション・スクリプトを定義します。この例では、rpc-interface-status.py という名前の Python アクション スクリプトを使用して、RPC に必要な情報を取得し、RPC ステートメントで定義された XML 出力要素をoutput返します。
rpc get-interface-status {
description "RPC example to retrieve interface status";
junos:command "show intf status" {
junos:action-execute {
junos:script "rpc-interface-status.py";
}
}
...
Junos OS リリース 17.3 以降、 action-execute ステートメントは. command以前のリリースでは、 action-execute および command ステートメントは同じレベルに配置され command 、 ステートメントはオプションです。
RPC には 、 という名前 matchの入力パラメーターがあり、出力に含めるインターフェイスを決定します。RPC を実行する場合、ge-0*など、目的のインターフェイスに一致する文字列を含めます。空の文字列("")は、すべてのインターフェイスで一致します。アクションスクリプトは のデフォルト値 match を空の文字列として定義するため、ユーザーがこの引数を省略すると、出力にはすべてのインターフェイスの情報が含まれます。
input {
leaf match {
description "Requested interface match condition";
type string;
}
}
RPC は、対応するアクション スクリプトによって出力される必要がある出力ノードも定義します。ルート ノードは要素で<interface-status-info>、一致したインターフェイスの および <status> ノードを<interface>囲む要素が 0 以上<status-info>含まれています。ステートメントはjunos-odl:format interface-status-info-format、CLI に表示される出力の書式を定義します。このノードは、出力 XML ツリーには出力されません。
output {
container interface-status-info {
list status-info {
leaf interface {
type string;
description "Physical inteface name";
}
leaf status {
type string;
description "Operational status";
}
junos-odl:format interface-status-info-format {
...
}
}
}
}
この例では、Python アクション スクリプトの 2 つのバージョンを示します。スクリプトは、運用コマンドの出力を取得するさまざまな手段を示していますが、どちらのスクリプトも同じ RPC 出力を発行します。最初のアクションスクリプトは、Python subprocess モジュールを使用してコマンドを show interfaces match-value | display xml 実行し、文字列出力をXMLに変換します。2 番目のアクション スクリプトでは 、Junos PyEZ を使用して、RPC と同等のコマンドを show interfaces match-value 実行します。どちらのスクリプトも、コマンド出力を解析するために同一のコードを使用し、各物理インターフェイスの名前と運用ステータスを抽出します。スクリプトは RPC 出力用の XML を構築し、出力を印刷して情報をデバイスに返します。XML ツリーは、RPC で定義された階層と完全に一致する必要があります。
Junos デバイスは、 要素を含む運用出力内の多くの要素に対して、リリースに依存する名前空間を <interface-information> 定義します。RPC Junos OS のリリースを独立させるために、コードはこれらの要素に対して local-name() XPath 式の関数を使用します。名前空間マッピングを引数 xpath() として含め、要素を適切な名前空間で修飾することができます。
RPCとアクションスクリプトファイルを含むモジュールは、 という名前 intf-rpcの新しいYANGパッケージの一部としてデバイスに追加されます。
YANG モジュール
YANG モジュール
YANG モジュールの rpc-interface-status.yang は、RPC、CLI で RPC を実行するために使用されるコマンド、RPC の実行時に呼び出すアクション スクリプトの名前を定義します。ファイルの基本名は、モジュール名と一致する必要があります。
/*
* Copyright (c) 2014 Juniper Networks, Inc.
* All rights reserved.
*/
module rpc-interface-status {
namespace "http://yang.juniper.net/examples/rpc-cli";
prefix rpc-cli;
import junos-extension-odl {
prefix junos-odl;
}
import junos-extension {
prefix junos;
}
organization
"Juniper Networks, Inc.";
description
"Junos OS YANG module for RPC example";
rpc get-interface-status {
description "RPC example to retrieve interface status";
junos:command "show intf status" {
junos:action-execute {
junos:script "rpc-interface-status.py";
}
}
input {
leaf match {
description "Requested interface match condition";
type string;
}
}
output {
container interface-status-info {
list status-info {
leaf interface {
type string;
description "Physical interface name";
}
leaf status {
type string;
description "Operational status";
}
junos-odl:format interface-status-info-format {
junos-odl:header "Physical Interface - Status\n";
junos-odl:indent 5;
junos-odl:comma;
junos-odl:space;
junos-odl:line {
junos-odl:field "interface";
junos-odl:field "status";
}
}
}
}
}
}
}
アクションスクリプト
対応するアクション スクリプトが rpc-interface-status.py。この例では、データを取得するために異なる手段を使用する 2 つのアクション スクリプトを示します。一方のスクリプトでは Python subprocess モジュールを使用し、もう 1 つのスクリプトは Junos PyEZ ライブラリを使用します。どちらのスクリプトも同じ RPC XML 出力を発行します。
Junos OSリリース21.2R1およびJunos OS Evolvedリリース21.2R1以降、デバイスがコマンドライン引数をPythonアクションスクリプトに渡すと、単一のハイフン(-)のプレフィックスが単一のハイフン(-)から単一文字の引数名に、2つのハイフン(-)から複数文字の引数名にプレフィックスが付加されます。
アクションスクリプト(使用 subprocess)
以下のアクションスクリプトは、Python subprocess モジュールを使用して運用コマンドを実行し、データを取得します。この例では、異なるリリースのスクリプトのコマンドライン引数を適切に処理する、2 つのバージョンのスクリプトを提供します。
Junos OS リリース 21.1 以前
#!/usr/bin/python
# Junos OS Release 21.1 and earlier
import sys
import subprocess
from lxml import etree
def get_device_info(cmd):
"""
Execute Junos OS operational command and parse output
:param: str cmd: operational command to execute
:returns: List containing the XML data for each interface
"""
# execute Junos OS operational command and retrieve output
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
tmp = proc.stdout.read()
root = etree.fromstring(tmp.strip())
xml_items = []
# parse output for required data
for intf in root.xpath("/rpc-reply \
/*[local-name()='interface-information'] \
/*[local-name()='physical-interface']"):
# retrieve data for the interface name and operational status
name = intf.xpath("*[local-name()='name']")[0].text
oper_status = intf.xpath("*[local-name()='oper-status']")[0].text
# append the XML for each interface to a list
xml_item = etree.Element('status-info')
interface = etree.SubElement(xml_item, 'interface')
interface.text = name
status = etree.SubElement(xml_item, 'status')
status.text = oper_status
xml_items.append(xml_item)
return xml_items
def generate_xml(cmd):
"""
Generate the XML tree for the RPC output
:param: str cmd: operational command from which to retrieve data
:returns: XML tree for the RPC output
"""
xml = etree.Element('interface-status-info')
intf_list_xml = get_device_info(cmd)
for intf in intf_list_xml:
xml.append(intf)
return xml
def main():
args = {'match': ""}
for arg in args.keys():
if arg in sys.argv:
index = sys.argv.index(arg)
args[arg] = sys.argv[index+1]
# define the operational command from which to retrieve information
cli_command = 'show interfaces ' + args['match'] + ' | display xml'
cmd = ['cli', '-c', cli_command]
# generate the XML for the RPC output
rpc_output_xml = generate_xml(cmd)
# print RPC output
print (etree.tostring(rpc_output_xml, pretty_print=True, encoding='unicode'))
if __name__ == '__main__':
main()
Junos OS リリース 21.2R1 以降
#!/usr/bin/python3
# Junos OS Release 21.2R1 and later
import subprocess
import argparse
from lxml import etree
def get_device_info(cmd):
"""
Execute Junos OS operational command and parse output
:param: str cmd: operational command to execute
:returns: List containing the XML data for each interface
"""
# execute Junos OS operational command and retrieve output
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
tmp = proc.stdout.read()
root = etree.fromstring(tmp.strip())
xml_items = []
# parse output for required data
for intf in root.xpath("/rpc-reply \
/*[local-name()='interface-information'] \
/*[local-name()='physical-interface']"):
# retrieve data for the interface name and operational status
name = intf.xpath("*[local-name()='name']")[0].text
oper_status = intf.xpath("*[local-name()='oper-status']")[0].text
# append the XML for each interface to a list
xml_item = etree.Element('status-info')
interface = etree.SubElement(xml_item, 'interface')
interface.text = name
status = etree.SubElement(xml_item, 'status')
status.text = oper_status
xml_items.append(xml_item)
return xml_items
def generate_xml(cmd):
"""
Generate the XML tree for the RPC output
:param: str cmd: operational command from which to retrieve data
:returns: XML tree for the RPC output
"""
xml = etree.Element('interface-status-info')
intf_list_xml = get_device_info(cmd)
for intf in intf_list_xml:
xml.append(intf)
return xml
def main():
parser = argparse.ArgumentParser(description='This is a demo script.')
parser.add_argument('--match', required=False, default='')
parser.add_argument('--rpc_name', required=True)
args = parser.parse_args()
# define the operational command from which to retrieve information
cli_command = 'show interfaces ' + args.match + ' | display xml'
cmd = ['cli', '-c', cli_command]
# generate the XML for the RPC output
rpc_output_xml = generate_xml(cmd)
# print RPC output
print (etree.tostring(rpc_output_xml, pretty_print=True, encoding='unicode'))
if __name__ == '__main__':
main()
アクション スクリプト(Junos PyEZ を使用)
以下のアクションスクリプトは、Junos PyEZを使用して操作コマンドを実行し、データを取得します。この例では、異なるリリースのスクリプトのコマンドライン引数を適切に処理する、2 つのバージョンのスクリプトを提供します。
Junos OS リリース 21.1 以前
#!/usr/bin/python
# Junos OS Release 21.1 and earlier
import sys
from jnpr.junos import Device
from jnpr.junos.exception import *
from lxml import etree
def get_device_info(match):
"""
Execute Junos OS operational command and parse output
:param: str match: interface match condition
:returns: List containing the XML data for each interface
"""
# execute Junos OS operational command and retrieve output
try:
with Device() as dev:
if (match == ""):
root = dev.rpc.get_interface_information( )
else:
root = dev.rpc.get_interface_information(interface_name=match)
except Exception:
sys.exit()
xml_items = []
# parse output for required data
for intf in root.xpath("/rpc-reply \
/*[local-name()='interface-information'] \
/*[local-name()='physical-interface']"):
# retrieve data for the interface name and operational status
name = intf.xpath("*[local-name()='name']")[0].text
oper_status = intf.xpath("*[local-name()='oper-status']")[0].text
# append the XML for each interface to a list
xml_item = etree.Element('status-info')
interface = etree.SubElement(xml_item, 'interface')
interface.text = name
status = etree.SubElement(xml_item, 'status')
status.text = oper_status
xml_items.append(xml_item)
return xml_items
def generate_xml(match):
"""
Generate the XML tree for the RPC output
:param: str match: interface match condition
:returns: XML tree for the RPC output
"""
xml = etree.Element('interface-status-info')
intf_list_xml = get_device_info(match)
for intf in intf_list_xml:
xml.append(intf)
return xml
def main():
args = {'match': ""}
for arg in args.keys():
if arg in sys.argv:
index = sys.argv.index(arg)
args[arg] = sys.argv[index+1]
# generate the XML for the RPC output
rpc_output_xml = generate_xml(args['match'])
# print RPC output
print (etree.tostring(rpc_output_xml, pretty_print=True, encoding='unicode'))
if __name__ == '__main__':
main()
Junos OS リリース 21.2R1 以降
#!/usr/bin/python3
# Junos OS Release 21.2R1 and later
import sys
import argparse
from jnpr.junos import Device
from jnpr.junos.exception import *
from lxml import etree
def get_device_info(match):
"""
Execute Junos OS operational command and parse output
:param: str match: interface match condition
:returns: List containing the XML data for each interface
"""
# execute Junos OS operational command and retrieve output
try:
with Device() as dev:
if (match == ""):
root = dev.rpc.get_interface_information( )
else:
root = dev.rpc.get_interface_information(interface_name=match)
except Exception:
sys.exit()
xml_items = []
# parse output for required data
for intf in root.xpath("/rpc-reply \
/*[local-name()='interface-information'] \
/*[local-name()='physical-interface']"):
# retrieve data for the interface name and operational status
name = intf.xpath("*[local-name()='name']")[0].text
oper_status = intf.xpath("*[local-name()='oper-status']")[0].text
# append the XML for each interface to a list
xml_item = etree.Element('status-info')
interface = etree.SubElement(xml_item, 'interface')
interface.text = name
status = etree.SubElement(xml_item, 'status')
status.text = oper_status
xml_items.append(xml_item)
return xml_items
def generate_xml(match):
"""
Generate the XML tree for the RPC output
:param: str match: interface match condition
:returns: XML tree for the RPC output
"""
xml = etree.Element('interface-status-info')
intf_list_xml = get_device_info(match)
for intf in intf_list_xml:
xml.append(intf)
return xml
def main():
parser = argparse.ArgumentParser(description='This is a demo script.')
parser.add_argument('--match', required=False, default='')
parser.add_argument('--rpc_name', required=True)
args = parser.parse_args()
# generate the XML for the RPC output
rpc_output_xml = generate_xml(args.match)
# print RPC output
print (etree.tostring(rpc_output_xml, pretty_print=True, encoding='unicode'))
if __name__ == '__main__':
main()
Python スクリプトの実行の有効化
デバイスが符号なしPythonスクリプトを実行できるようにします。
デバイスでの RPC の読み込み
RPC およびアクション スクリプトを Junos スキーマに追加するには、以下の手順にしたがっています。
RPC の検証
目的
RPC が想定通りに動作することを確認します。
アクション
運用モードから、RPC 定義の ステートメントで定義された junos:command コマンドを発行して CLI で RPC を実行し、入力引数を match 含めます。この例では、ge-0 で始まるすべてのインターフェイスで match 引数を使用します。
user@host> show intf status match ge-0*
Physical Interface - Status
ge-0/0/0, up
ge-0/0/1, up
ge-0/0/2, up
ge-0/0/3, up
ge-0/0/4, up
ge-0/0/5, up
ge-0/0/6, up
ge-0/0/7, up
ge-0/0/8, up
ge-0/0/9, up
ge-0/1/0, up
ge-0/1/1, up
ge-0/1/2, up
ge-0/1/3, up
ge-0/1/4, up
ge-0/1/5, up
ge-0/1/6, up
ge-0/1/7, up
ge-0/1/8, up
ge-0/1/9, up
また、 一致条件を調整して、異なるインターフェイスセットを返することもできます。例えば:
user@host> show intf status match *e-0/*/0
Physical Interface - Status
ge-0/0/0, up
pfe-0/0/0, up
ge-0/1/0, up
xe-0/2/0, up
xe-0/3/0, up
XML 形式で同じ出力を返すために、 コマンドに | display xml フィルターを追加します。
user@host> show intf status match *e-0/*/0 | display xml
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/17.3R1/junos">
<interface-status-info>
<status-info>
<interface>ge-0/0/0</interface>
<status>up</status>
</status-info>
<status-info>
<interface>pfe-0/0/0</interface>
<status>up</status>
</status-info>
<status-info>
<interface>ge-0/1/0</interface>
<status>up</status>
</status-info>
<status-info>
<interface>xe-0/2/0</interface>
<status>up</status>
</status-info>
<status-info>
<interface>xe-0/3/0</interface>
<status>up</status>
</status-info>
</interface-status-info>
<cli>
<banner></banner>
</cli>
</rpc-reply>
すべてのインターフェイスで一致するには、引数を match 省略するか、引数の値を空の文字列("")に設定します。
意味
RPC を実行すると、デバイスはアクション スクリプトを呼び出します。アクションスクリプトは、操作コマンドを実行してデバイスからインターフェイス情報を取得し、目的の情報の出力を解析し、RPC output ステートメントで定義されたRPC出力のXML階層を印刷します。CLI で RPC を実行すると、デバイスは RPC で定義された CLI フォーマットを使用して XML 出力を表示される CLI 出力に変換します。元の XML 出力を返すために、 コマンドに | display xml フィルターを追加します。
RPC 要求タグを使用してリモートで RPC を実行する場合、出力のデフォルト形式は XML になります。
RPC 実行エラーのトラブルシューティング
問題
説明
RPC を実行すると、デバイスは次のエラーを生成します。
error: open failed: /var/db/scripts/action/rpc-interface-status.py: Permission denied
原因
RPC を呼び出したユーザーは、対応する Python アクション スクリプトを実行するための必要な権限を持っていません。
ソリューション
スクリプトのファイルパーミッションに、ユーザーが受ける最初のクラスの読み取り権限がユーザー、グループなどの順序で含まれている場合、ユーザーはJunosデバイスでのみ署名されていないPythonスクリプトを実行できます。
スクリプトがスクリプトを実行するためにそのユーザーに必要な権限をスクリプトに持っているかどうかを確認し、必要に応じてアクセス許可を調整します。権限を更新する場合、この変更を有効にするには、YANG パッケージも更新する必要があります。例えば:
admin@host> file list ~ detail -rw------- 1 admin wheel 2215 Apr 20 11:36 rpc-interface-status.py
admin@host> file change-permission rpc-interface-status.py permission 644 admin@host> file list ~ detail -rw-r--r-- 1 admin wheel 2215 Apr 20 11:36 rpc-interface-status.py
admin@host> request system yang update intf-rpc action-script /var/tmp/rpc-interface-status.py Scripts syntax validation : START Scripts syntax validation : SUCCESS