らくがきちょう

なんとなく

Cisco ACI で Contract 設定用 XML を出力する Python スクリプトサンプル

Cisco ACI 上で普段の運用は Web GUI が使えれば十分です。しかし、「大量に設定を行いたい」といった場合は CLIREST API を使いこなせると効率が上がります。今回は Python スクリプトを使って大量の Contract を設定するサンプルを示します。

Contract を表現する XML / Json

Contract を表現する XML / Json は以下のようになります。これらの XMLJson は全く同じ意味を持ちます。

XML

<?xml version="1.0" encoding="UTF-8"?>
<imdata totalCount="1">
  <vzBrCP descr="" name="ExtEpg1-1_ExtEpg2-1" nameAlias="" ownerKey="" ownerTag="" prio="unspecified" scope="context" targetDscp="unspecified">
    <vzSubj consMatchT="AtleastOne" descr="" name="Subject-1" nameAlias="" prio="unspecified" provMatchT="AtleastOne" revFltPorts="yes" targetDscp="unspecified">
      <vzRsSubjFiltAtt directives="" tnVzFilterName="PermitIP"/>
    </vzSubj>
  </vzBrCP>
</imdata>

Json

Json では値毎に改行している為、行数が長くなっています。

{
    "totalCount": "1",
    "imdata": [
        {
            "vzBrCP": {
                "attributes": {
                    "descr": "",
                    "name": "ExtEpg1-1_ExtEpg2-1",
                    "nameAlias": "",
                    "ownerKey": "",
                    "ownerTag": "",
                    "prio": "unspecified",
                    "scope": "context",
                    "targetDscp": "unspecified"
                },
                "children": [
                    {
                        "vzSubj": {
                            "attributes": {
                                "consMatchT": "AtleastOne",
                                "descr": "",
                                "name": "Subject-1",
                                "nameAlias": "",
                                "prio": "unspecified",
                                "provMatchT": "AtleastOne",
                                "revFltPorts": "yes",
                                "targetDscp": "unspecified"
                            },
                            "children": [
                                {
                                    "vzRsSubjFiltAtt": {
                                        "attributes": {
                                            "directives": "",
                                            "tnVzFilterName": "PermitIP"
                                        }
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        }
    ]
}

Contract を出力する Python スクリプト

Contract を出力する Python スクリプト例は以下の通りです。 引数の解析で docopt に依存している為、事前に docopt をインストールしておく必要があります。

pip install docopt

スクリプトは以下の通りです。名前は contract-builder.pyとしました。

contract-builder.py

#!/usr/bin/env python

"""Usage:
contract-builder.py [-f <FILTER>] [-s <SUBJECT>] [-t <TENANT>] <INPUTFILE>
contract-builder.py -h | --help

options:
    <INPUTFILE>              Specify input xml file.
    -f, --filter <FILTER>    Specify filter name [default: Filter-1].
    -h, --help               Show this help message and exit.
    -s, --subject <SUBJECT>  Specify subject name [default: Subject-1].
    -t, --tenant <TENANT>    Specify tenant name [default: Tenant-1].
"""

from docopt import docopt
import xml.dom.minidom as minidom
import xml.etree.ElementTree as ET

if __name__ == '__main__':
    args = docopt(__doc__)
    inputfile = open(args.get('<INPUTFILE>'))
    line = inputfile.readline()

    imdata = ET.Element('imdata')
    imdata.set('totalCount', '1')

    fvTenant = ET.SubElement(imdata, 'fvTenant')
    fvTenant.set('descr', '')
    fvTenant.set('dn', 'uni/tn-' + args['--tenant'])
    fvTenant.set('name', args['--tenant'])
    fvTenant.set('ownerKey', '')
    fvTenant.set('ownerTag', '')

    while line:
        contract_name = line.rstrip()

        vzBrCP = ET.SubElement(fvTenant, 'vzBrCP')
        vzBrCP.set('descr', '')
        vzBrCP.set('dn', 'uni/tn-' + args['--tenant'] + '/brc-' + contract_name)
        vzBrCP.set('name', contract_name)
        vzBrCP.set('nameAlias', '')
        vzBrCP.set('ownerKey', '')
        vzBrCP.set('ownerTag', '')
        vzBrCP.set('prio', 'unspecified')
        vzBrCP.set('scope', 'context')
        vzBrCP.set('targetDscp', 'unspecified')
        
        vzSubj = ET.SubElement(vzBrCP, 'vzSubj')
        vzSubj.set('consMatchT', 'AtleastOne')
        vzSubj.set('descr', '')
        vzSubj.set('name', args['--subject'])
        vzSubj.set('nameAlias', '')
        vzSubj.set('prio', 'unspecified')
        vzSubj.set('provMatchT', 'AtleastOne')
        vzSubj.set('revFltPorts', 'yes')
        vzSubj.set('targetDscp', 'unspecified')
        
        vzRsSubjFiltAtt = ET.SubElement(vzSubj, 'vzRsSubjFiltAtt')
        vzRsSubjFiltAtt.set('directives', '')
        vzRsSubjFiltAtt.set('tnVzFilterName', args['--filter'])

        line = inputfile.readline()
    inputfile.close()
    string = ET.tostring(imdata, 'utf-8')
    pretty_string = minidom.parseString(string).toprettyxml(indent='  ')
    print(pretty_string)

contract-builder.py 実行例

事前に作成したい Contract 名を羅列したファイルを用意しておきます。

# cat CONTRACTS
Epg1_EpgA
Epg2_EpgB
Epg3_EpgC

contract-builder.py の引数に事前に用意したファイル名を指定すると標準出力へ Contract を表現する XML を出力します。

# python contract-builder.py CONTRACTS
<?xml version="1.0" ?>
<imdata totalCount="1">
  <fvTenant descr="" dn="uni/tn-Tenant-1" name="Tenant-1" ownerKey="" ownerTag="">
    <vzBrCP descr="" dn="uni/tn-Tenant-1/brc-Epg1_EpgA" name="Epg1_EpgA" nameAlias="" ownerKey="" ownerTag="" prio="unspecified" scope="context" targetDscp="unspecified">
      <vzSubj consMatchT="AtleastOne" descr="" name="Subject-1" nameAlias="" prio="unspecified" provMatchT="AtleastOne" revFltPorts="yes" targetDscp="unspecified">
        <vzRsSubjFiltAtt directives="" tnVzFilterName="Filter-1"/>
      </vzSubj>
    </vzBrCP>
    <vzBrCP descr="" dn="uni/tn-Tenant-1/brc-Epg2_EpgB" name="Epg2_EpgB" nameAlias="" ownerKey="" ownerTag="" prio="unspecified" scope="context" targetDscp="unspecified">
      <vzSubj consMatchT="AtleastOne" descr="" name="Subject-1" nameAlias="" prio="unspecified" provMatchT="AtleastOne" revFltPorts="yes" targetDscp="unspecified">
        <vzRsSubjFiltAtt directives="" tnVzFilterName="Filter-1"/>
      </vzSubj>
    </vzBrCP>
    <vzBrCP descr="" dn="uni/tn-Tenant-1/brc-Epg3_EpgC" name="Epg3_EpgC" nameAlias="" ownerKey="" ownerTag="" prio="unspecified" scope="context" targetDscp="unspecified">
      <vzSubj consMatchT="AtleastOne" descr="" name="Subject-1" nameAlias="" prio="unspecified" provMatchT="AtleastOne" revFltPorts="yes" targetDscp="unspecified">
        <vzRsSubjFiltAtt directives="" tnVzFilterName="Filter-1"/>
      </vzSubj>
    </vzBrCP>
  </fvTenant>
</imdata>

Tenant、Subject、Filter 名を指定したい場合は以下のオプションを指定します。

オプション 意味 デフォルト値
-t または –tenant Tenant 名 Tenant-1
-s または –subject Subject 名 Subject-1
-f またh –filter Filter 名 Filter-1

各オプションを指定した場合の実行例は以下の通りです。

# python contract-builder.py CONTRACTS --tenant Z --subject Subject-Z --filter Filter-Z
<?xml version="1.0" ?>
<imdata totalCount="1">
  <fvTenant descr="" dn="uni/tn-Z" name="Z" ownerKey="" ownerTag="">
    <vzBrCP descr="" dn="uni/tn-Z/brc-Epg1_EpgA" name="Epg1_EpgA" nameAlias="" ownerKey="" ownerTag="" prio="unspecified" scope="context" targetDscp="unspecified">
      <vzSubj consMatchT="AtleastOne" descr="" name="Subject-Z" nameAlias="" prio="unspecified" provMatchT="AtleastOne" revFltPorts="yes" targetDscp="unspecified">
        <vzRsSubjFiltAtt directives="" tnVzFilterName="Filter-Z"/>
      </vzSubj>
    </vzBrCP>
    <vzBrCP descr="" dn="uni/tn-Z/brc-Epg2_EpgB" name="Epg2_EpgB" nameAlias="" ownerKey="" ownerTag="" prio="unspecified" scope="context" targetDscp="unspecified">
      <vzSubj consMatchT="AtleastOne" descr="" name="Subject-Z" nameAlias="" prio="unspecified" provMatchT="AtleastOne" revFltPorts="yes" targetDscp="unspecified">
        <vzRsSubjFiltAtt directives="" tnVzFilterName="Filter-Z"/>
      </vzSubj>
    </vzBrCP>
    <vzBrCP descr="" dn="uni/tn-Z/brc-Epg3_EpgC" name="Epg3_EpgC" nameAlias="" ownerKey="" ownerTag="" prio="unspecified" scope="context" targetDscp="unspecified">
      <vzSubj consMatchT="AtleastOne" descr="" name="Subject-Z" nameAlias="" prio="unspecified" provMatchT="AtleastOne" revFltPorts="yes" targetDscp="unspecified">
        <vzRsSubjFiltAtt directives="" tnVzFilterName="Filter-Z"/>
      </vzSubj>
    </vzBrCP>
  </fvTenant>
</imdata>

生成された XML から Contract を設定してみる

最後に、Python スクリプトから生成された XML を使って実際に Contract を設定してみます。Web UI のテナント設定画面等を右クリックし、Post をクリックします。

f:id:sig9:20170318175957p:plain

BROWSE をクリックし、Python スクリプトから生成された XML ファイルを選択します。

f:id:sig9:20170318180008p:plain

Parent DN: には Contract を設定したい Tenant の DN 名を指定します。例えば A というテナントに Contract を設定したい場合は uni/tn-A を指定します (B というテナントなら uni-/tn-B、C というテナントなら uni/tn-C になります)。Parent DN: を指定したら POST をクリックします。

f:id:sig9:20170318180016p:plain

エラーが出ずに Web UI へ戻れば、正しく設定されたはずです(上手くいかなかった場合は失敗の原因を示すエラーが表示されます)。 Web UI から Tenant の Contract 設定を表示すると、確かに XML ファイル中で定義されていた Contract が設定されていることが分かります。

f:id:sig9:20170318180029p:plain