AnsibleでACL Config作成＆設定変更を自動化してみた

# はじめに

以前の記事で、Jinja2テンプレートを使ったConfig自動生成を行いました。こちらはCSV形式のパラメータ表とJinja2テンプレートを、Pythonを使ってレンダリングしてConfig生成した例です。
[ポート管理表+パラメータ表+Jinja2テンプレートから、L2SWのConfigを自動生成してみた](https://qiita.com/tech_kitara/items/b6ffd9790483b08b568b)

一方よこちさんの記事で、Pythonの代わりにAnsibleのtemplateモジュールを使った例も紹介頂いています。
[[Ansible] 「ポート管理表+パラメータ表+Jinja2テンプレートから、L2SWのConfigを自動生成してみた」を Ansible で](https://tekunabe.hatenablog.jp/entry/2019/04/02/ansible_build_template)

各方法のメリットは以下の通りかと思います。

**Python**

- AnsibleをインストールできないWindows環境でも実行できる


**Ansible**

- Pythonの知識はなくてもOK
- Ansible独自のJinja2テンプレート（フィルタープラグイン）を使って効率的にConfig生成ができる  
（例）ipaddr filterでプレフィックス`/24`をサブネットネットマスク`255.255.255.0`やワイルドカードマスク`0.0.0.255`に変換可能
- 生成したConfigをそのままAnsibleで流し込みできる

本記事では、Ansibleの例を掘り下げ、CSV形式のACL要件資料とJinja2テンプレートから「名前付き拡張ACL」のConfigファイルを生成し、後続ステップで設定変更を行ってみました（下図）。対象OSはCisco IOS、NX-OS、ASAの3種類です。

※作ったものは[GitHub/acl-jinja2-template](https://github.com/Tachashi/acl-jinja2-template)にアップロードしています。

![無題1202_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/0706de24-0396-f96c-e6ea-dfcdf8888f1b.png)

# 1. ACL要件資料
3種類の機器で6要件ずつ、計18要件分のConfigを生成してみます。

- [acl_requests1.csv](https://github.com/Tachashi/acl-jinja2-template/blob/master/add/acl_requests1.csv)
![acl_request1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/0d796941-923c-22da-cc9d-031dcc19a236.png)

各項目の内容は以下の通りです。

| 項目名       |  内容 |
|:-----------------|:------------------|
| hostname | 対象機器のホスト名 |
| change | 設定変更種別　例：add（追加）、delete（削除） |
| acl_type | ACL種別　例：name_extended（名前付き拡張）、name_standard（名前付き標準）、number_extended（番号付き拡張）、number_standard（番号付き標準）※現時点の実装はname_extendedのみ |
| acl_name | ACL名 or ACL番号　指定しない場合、ACLコンフィグレーションモード配下のコマンド（permit/deny ～）のみ生成 |
| seq_no | シーケンス番号（任意） |
| action | 通信許可 or 拒否　例：permit、deny |
| protocol | プロトコル　例：ip、tcp、udp |
| src_addr | 送信元アドレス　アドレス/プレフィックス長の形式で指定 |
| src_oper | 送信元演算子（任意）　例：eq、gt、lt、range |
| src_port | 送信元ポート番号（任意）　rangeの場合、FromとToをスペースを空けて指定 |
| dest_addr | 宛先アドレス　アドレス/プレフィックス長の形式で指定 |
| dest_oper | 宛先演算子（任意）　例：eq、gt、lt、range |
| dest_port | 宛先ポート番号（任意）　rangeの場合、FromとToをスペースを空けて指定 |
| modifier | 追加オプション（任意）　例：log、established、log-input |
| description | リマーク設定（任意）　該当ACLの前に定義 |


# 2. Jinja2テンプレート
ファイルは[acl_template.j2](https://github.com/Tachashi/acl-jinja2-template/blob/master/acl_template.j2)です。詳細は割愛しますが、意識したポイントは以下の通りです。

- Jinja2テンプレートの可読性を高めるため、インデントを付与。

- Jinja2のWhitespace Controlを利用し、実際のConfigとインデントが同じになるように調整。
具体的には、例えば`{% if ... -%}`のように、ifの後に`-`を付けることで、後ろの改行とスペースが削られるようにしています。合わせて後述のAnsible Playbook内で、templateモジュールのオプション「lstrip_blocks」を有効化することで、ブロック行`{% block %} ` の左のWhitespaceを取り除いています。

- 任意設定が空データの場合は何も追加せず、逆にデータがある場合は「スペース＋設定」を追加することで、スペースが2つ以上連ならないよう工夫。

- ipaddr filterを使い、`{{ アドレス/プレフィックス | ipaddr('network') }}`でアドレスのみ抽出。また、IOS ACLでは`{{ アドレス/プレフィックス | ipaddr('wildcard') }}`を使ってプレフィックスからワイルドカードマスクを生成し、ASA ACLでは`{{ アドレス/プレフィックス | ipaddr('netmask') }}`を使ってプレフィックスからサブネットマスクを抽出しています。
（本フィルタを使用するには、`pip install netaddr`でPythonパッケージのインストールが必要。）

- どのテンプレートを使うかは、Ansible変数`ansible_network_os`を元に判定。


# 3. Ansible Inventoryファイル

[以前の記事](https://qiita.com/tech_kitara/items/1c134e748d1db5558b67#2-ini%E5%BD%A2%E5%BC%8F)でVIRL上に構築したIOSv、NX-OSv9000、ASAvを使用しました。使用したAnsibleバージョンは2.9.0です。

# 4. ACL Config作成

本項でまず、Ansibleを使ったACL Config作成を行います。

## 4-1. Ansible Playbook
各タスクの内容は以下の通りです。

1. `read_csv`モジュールを使い、`[{ "hostname": "xx", "acl_name": "aa", ～ }, { "hostname": "yy", "acl_name": "bb", ～ }, ～]`のように、リスト内の要素として、CSVの各行のデータが辞書形式で定義された形に変換。
2. `debug`モジュールで、タスク1のリストを表示。
3. `set_fact`モジュールで、上記のリストをJinja2テンプレート内で読み込めるよう変数`acls`に格納。
4. `template`モジュールで、レンダリングした結果をテキストファイルとして出力。  
（このタイミングで、人手でレビューすることを想定。）

```yaml:playbook_aclgen.yml
---

- hosts: all
  gather_facts: no
  connection: network_cli

  vars:
    template: acl_template.j2
    acl_list: add/acl_requests1.csv
    config_filename: "add/acl_config_{{ inventory_hostname }}.txt"

  tasks:
    - name: read acl requests from CSV file and return a list   # (1)
      read_csv:
        path: "{{ acl_list }}"
      run_once: yes
      register: acl_requests

    - name: display acl requests   # (2)
      debug:
        msg: "{{ acl_requests.list }}"
      run_once: yes

    - name: set acl requests   # (3)
      set_fact:
        acls: "{{ acl_requests.list }}"
      run_once: yes

    - name: build template   # (4)
      template:
        src: "{{ template }}"
        dest: "{{ config_filename }}"
        lstrip_blocks: yes
```

## 4-2. Playbook実行ログ
タスク2の出力結果を見ると、問題なくリストが出力されている事が分かります。

```
$ ansible-playbook -i inventory_virl1.ini playbook_aclgen.yml

PLAY [all] ***************************************************************************************************

TASK [read acl requests from CSV file and return a list] *****************************************************
ok: [asav-1]

TASK [display acl requests] **********************************************************************************
ok: [asav-1] => {
    "msg": [
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "ACL_TEST1",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "ntp",
            "hostname": "iosv-1",
            "modifier": "",
            "protocol": "udp",
            "seq_no": "110",
            "src_addr": "192.168.1.1/32",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "www",
            "hostname": "iosv-1",
            "modifier": "",
            "protocol": "tcp",
            "seq_no": "120",
            "src_addr": "192.168.1.2/32",
            "src_oper": "range",
            "src_port": "1024 65535"
        },
        {
            "acl_name": "",
            "acl_type": "name_extended",
            "action": "permit",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "",
            "dest_port": "",
            "hostname": "iosv-1",
            "modifier": "log",
            "protocol": "ip",
            "seq_no": "130",
            "src_addr": "192.168.1.0/24",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST2",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "ACL_TEST2",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "ntp",
            "hostname": "iosv-1",
            "modifier": "",
            "protocol": "udp",
            "seq_no": "",
            "src_addr": "192.168.1.1/32",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "www",
            "hostname": "iosv-1",
            "modifier": "",
            "protocol": "tcp",
            "seq_no": "",
            "src_addr": "192.168.1.2/32",
            "src_oper": "range",
            "src_port": "1024 65535"
        },
        {
            "acl_name": "",
            "acl_type": "name_extended",
            "action": "permit",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "",
            "dest_port": "",
            "hostname": "iosv-1",
            "modifier": "log",
            "protocol": "ip",
            "seq_no": "",
            "src_addr": "192.168.1.0/24",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "ACL_TEST1",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "ntp",
            "hostname": "nx-osv9000-1",
            "modifier": "",
            "protocol": "udp",
            "seq_no": "110",
            "src_addr": "192.168.1.1/32",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "www",
            "hostname": "nx-osv9000-1",
            "modifier": "",
            "protocol": "tcp",
            "seq_no": "120",
            "src_addr": "192.168.1.2/32",
            "src_oper": "range",
            "src_port": "1024 65535"
        },
        {
            "acl_name": "",
            "acl_type": "name_extended",
            "action": "permit",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "",
            "dest_port": "",
            "hostname": "nx-osv9000-1",
            "modifier": "log",
            "protocol": "ip",
            "seq_no": "130",
            "src_addr": "192.168.1.0/24",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST2",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "ACL_TEST2",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "ntp",
            "hostname": "nx-osv9000-1",
            "modifier": "",
            "protocol": "udp",
            "seq_no": "",
            "src_addr": "192.168.1.1/32",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "www",
            "hostname": "nx-osv9000-1",
            "modifier": "",
            "protocol": "tcp",
            "seq_no": "",
            "src_addr": "192.168.1.2/32",
            "src_oper": "range",
            "src_port": "1024 65535"
        },
        {
            "acl_name": "",
            "acl_type": "name_extended",
            "action": "permit",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "",
            "dest_port": "",
            "hostname": "nx-osv9000-1",
            "modifier": "log",
            "protocol": "ip",
            "seq_no": "",
            "src_addr": "192.168.1.0/24",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "ACL_TEST1",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "ntp",
            "hostname": "asav-1",
            "modifier": "",
            "protocol": "udp",
            "seq_no": "1",
            "src_addr": "192.168.1.1/32",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "www",
            "hostname": "asav-1",
            "modifier": "",
            "protocol": "tcp",
            "seq_no": "2",
            "src_addr": "192.168.1.2/32",
            "src_oper": "range",
            "src_port": "1024 65535"
        },
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "permit",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "",
            "dest_port": "",
            "hostname": "asav-1",
            "modifier": "log",
            "protocol": "ip",
            "seq_no": "3",
            "src_addr": "192.168.1.0/24",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST2",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "ACL_TEST2",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "ntp",
            "hostname": "asav-1",
            "modifier": "",
            "protocol": "udp",
            "seq_no": "",
            "src_addr": "192.168.1.1/32",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST2",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "www",
            "hostname": "asav-1",
            "modifier": "",
            "protocol": "tcp",
            "seq_no": "",
            "src_addr": "192.168.1.2/32",
            "src_oper": "range",
            "src_port": "1024 65535"
        },
        {
            "acl_name": "TEST2",
            "acl_type": "name_extended",
            "action": "permit",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "",
            "dest_port": "",
            "hostname": "asav-1",
            "modifier": "log",
            "protocol": "ip",
            "seq_no": "",
            "src_addr": "192.168.1.0/24",
            "src_oper": "",
            "src_port": ""
        }
    ]
}

TASK [set acl requests] **************************************************************************************
ok: [asav-1]

TASK [build template] ****************************************************************************************
changed: [nx-osv9000-1]
changed: [iosv-1]
changed: [asav-1]

PLAY RECAP ***************************************************************************************************
asav-1                     : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
iosv-1                     : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
nx-osv9000-1               : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  
```

## 4-3. 生成されたConfigファイル
いずれも問題なく作成されました。

```:acl_config_iosv-1.txt
ip access-list extended TEST1
 remark ACL_TEST1
 110 deny udp host 192.168.1.1 host 192.168.2.1 eq ntp
 120 deny tcp host 192.168.1.2 range 1024 65535 host 192.168.2.1 eq www
 130 permit ip 192.168.1.0 0.0.0.255 192.168.2.0 0.0.0.255 log
ip access-list extended TEST2
 remark ACL_TEST2
 deny udp host 192.168.1.1 host 192.168.2.1 eq ntp
 deny tcp host 192.168.1.2 range 1024 65535 host 192.168.2.1 eq www
 permit ip 192.168.1.0 0.0.0.255 192.168.2.0 0.0.0.255 log
```

```:acl_config_nx-osv9000-1.txt
ip access-list TEST1
  105 remark ACL_TEST1
  110 deny udp 192.168.1.1/32 192.168.2.1/32 eq ntp
  120 deny tcp 192.168.1.2/32 range 1024 65535 192.168.2.1/32 eq www
  130 permit ip 192.168.1.0/24 192.168.2.0/24 log
ip access-list TEST2
  remark ACL_TEST2
  deny udp 192.168.1.1/32 192.168.2.1/32 eq ntp
  deny tcp 192.168.1.2/32 range 1024 65535 192.168.2.1/32 eq www
  permit ip 192.168.1.0/24 192.168.2.0/24 log
```

```:acl_config_asav-1.txt
access-list TEST1 remark ACL_TEST1
access-list TEST1 line 1 extended deny udp host 192.168.1.1 host 192.168.2.1 eq ntp
access-list TEST1 line 2 extended deny tcp host 192.168.1.2 range 1024 65535 host 192.168.2.1 eq www
access-list TEST1 line 3 extended permit ip 192.168.1.0 255.255.255.0 192.168.2.0 255.255.255.0 log
access-list TEST2 remark ACL_TEST2
access-list TEST2 extended deny udp host 192.168.1.1 host 192.168.2.1 eq ntp
access-list TEST2 extended deny tcp host 192.168.1.2 range 1024 65535 host 192.168.2.1 eq www
access-list TEST2 extended permit ip 192.168.1.0 255.255.255.0 192.168.2.0 255.255.255.0 log
```


# 5. ACL設定変更

本項では4.で生成したConfigファイルを、Ansibleの`xxx_config`モジュールのsrcオプションで指定して設定変更を行います。

## 5-1. Ansible Playbook
各タスクの内容は以下の通りです。

1. `ios_config`モジュールでACL設定変更＆設定保存。`when`でIOSの場合のみ実行。matchオプションを`none`とし、冪等性が機能しないようにしていますが、これは自動生成したConfigファイル内の`ip access-list ～`が冪等性によって設定対象から除外され、エラーになるのを回避するための苦肉の策です。。
2. `nxos_config`モジュールで1.と同様の処理を実行。NX-OSの場合のみ実行。冪等性×
3. `asa_config`モジュールで1.と同様の処理を実行。ASAの場合のみ実行。冪等性〇
4. ベンダ非依存モジュール`cli_command`で、設定変更後のRunning Configを取得。
5. `debug`モジュールで4.の取得結果を表示。

```yaml:playbook_aclconf.yml
---

- hosts: all
  gather_facts: no
  connection: network_cli

  vars:
    config_filename: "add/acl_config_{{ inventory_hostname }}.txt"

  tasks:
    - name: change acls on remote device (ios)   # (1)
      ios_config:
        src: "{{ config_filename }}"
        match: none
        save_when: modified
      when: ansible_network_os == 'ios'

    - name: change acls on remote device (nxos)   # (2)
      nxos_config:
        src: "{{ config_filename }}"
        match: none
        save_when: modified
      when: ansible_network_os == 'nxos'

    - name: change acls on remote device (asa)   # (3)
      asa_config:
        src: "{{ config_filename }}"
        save: yes
      when: ansible_network_os == 'asa'

    - name: run show command on remote device   # (4)
      cli_command:
        command: show run | begin TEST
      register: result

    - name: debug   # (5)
      debug:
        msg:
          - "{{ result.stdout_lines }}"
```

## 5-2. Playbook実行ログ
タスク5の出力結果を見ると、問題なくACL設定変更されている事が分かります。

```
$ ansible-playbook -i inventory_virl1.ini playbook_aclconf.yml

PLAY [all] ***************************************************************************************************

TASK [change acls on remote device (ios)] ********************************************************************
skipping: [asav-1]
skipping: [nx-osv9000-1]
changed: [iosv-1]

TASK [change acls on remote device (nxos)] *******************************************************************
skipping: [asav-1]
skipping: [iosv-1]
changed: [nx-osv9000-1]

TASK [change acls on remote device (asa)] ********************************************************************
skipping: [iosv-1]
skipping: [nx-osv9000-1]
changed: [asav-1]

TASK [run show command on remote device] *********************************************************************
ok: [asav-1]
ok: [nx-osv9000-1]
ok: [iosv-1]

TASK [debug] *************************************************************************************************
ok: [iosv-1] => {
    "msg": [
        [
            "ip access-list extended TEST1",
            " remark ACL_TEST1",
            " deny   udp host 192.168.1.1 host 192.168.2.1 eq ntp",
            " deny   tcp host 192.168.1.2 range 1024 65535 host 192.168.2.1 eq www",
            " permit ip 192.168.1.0 0.0.0.255 192.168.2.0 0.0.0.255 log",
            "ip access-list extended TEST2",
            " remark ACL_TEST2",
            " deny   udp host 192.168.1.1 host 192.168.2.1 eq ntp",
            " deny   tcp host 192.168.1.2 range 1024 65535 host 192.168.2.1 eq www",
            " permit ip 192.168.1.0 0.0.0.255 192.168.2.0 0.0.0.255 log",
            (省略)
        ]
    ]
}
ok: [asav-1] => {
    "msg": [
        [
            "access-list TEST1 extended deny udp host 192.168.1.1 host 192.168.2.1 eq ntp ",
            "access-list TEST1 extended deny tcp host 192.168.1.2 range 1024 65535 host 192.168.2.1 eq www ",
            "access-list TEST1 extended permit ip 192.168.1.0 255.255.255.0 192.168.2.0 255.255.255.0 log ",
            "access-list TEST1 remark ACL_TEST1",
            "access-list TEST2 remark ACL_TEST2",
            "access-list TEST2 extended deny udp host 192.168.1.1 host 192.168.2.1 eq ntp ",
            "access-list TEST2 extended deny tcp host 192.168.1.2 range 1024 65535 host 192.168.2.1 eq www ",
            "access-list TEST2 extended permit ip 192.168.1.0 255.255.255.0 192.168.2.0 255.255.255.0 log ",
            (省略)
        ]
    ]
}
ok: [nx-osv9000-1] => {
    "msg": [
        [
            "ip access-list TEST1",
            "  105 remark ACL_TEST1",
            "  110 deny udp 192.168.1.1/32 192.168.2.1/32 eq ntp ",
            "  120 deny tcp 192.168.1.2/32 range 1024 65535 192.168.2.1/32 eq www ",
            "  130 permit ip 192.168.1.0/24 192.168.2.0/24 log ",
            "ip access-list TEST2",
            "  10 remark ACL_TEST2",
            "  20 deny udp 192.168.1.1/32 192.168.2.1/32 eq ntp ",
            "  30 deny tcp 192.168.1.2/32 range 1024 65535 192.168.2.1/32 eq www ",
            "  40 permit ip 192.168.1.0/24 192.168.2.0/24 log ",
            (省略)
        ]
    ]
}

PLAY RECAP ***************************************************************************************************
asav-1                     : ok=3    changed=1    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
iosv-1                     : ok=3    changed=1    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
nx-osv9000-1               : ok=3    changed=1    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0 
```

# 最後に
今回は設定追加の例でしたが、ACL要件資料の項目`change`を`delete`とすることで、設定削除も問題なくできました。（結果はGitHubのdeleteフォルダにアップしています。）
今回のような例だと、どうしてもJinja2テンプレートが複雑になってしまいますが、一度作ってしまえば、パラメーター表から簡単にConfig生成ができるようになるので、作業頻度の高いものは作ってみてはいかがでしょうか。
ただし、`ios_bgp`モジュールのように、既にモジュールが存在する設定項目は、そちらを使った方が楽だと思います。