例:カスタム 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