秒速でルータ・スイッチのコンフィグ(数千のインタフェース)をpythonで作成する

#何がやりたい？

**ネットワークエンジニア**であろう皆様にとって、日々の検証環境を構築する際に、ルータ・スイッチ・RADIUSサーバーなど、IPアドレスが必要となる**コンフィグ**及び**設定ファイル**を作成する際に、このツールを使うことで少しでも効率が上がれば良いかなと考えています。

例えば、サブインタフェースやVLAN IDなど、単純なインクリメントでコンフィグを作成する際には、エクセルを使ったり、色々あるとは思いますが、**IPアドレス**と**サブネット**の**設定が面倒**と感じることはありませんでしょうか。/24ならまだしも、特に、/30とか/29とか…

**そのアドレス部分の生成だけを関数にして、スクリプトをシンプルに書いてみませんか？**

##1. 作業環境の準備
スクリプトの内容を説明するよりも、このスクリプトを実行することで、どのような結果が得られるのかを理解していただければと思いますので、ハンズオン形式で進められるようにステップ・バイ・ステップで説明させていただきます。

###1-1. 作業用のディレクトリを作成
まず最初に、任意のディレクトリ配下に作業用のディレクトリを作成して、そのディレクトリに移動していただけますでしょうか。

ここでは、例として、ホームディレクトリ配下に作業用ディレクトリ**[work]**を作成して、さらにサブディレクトリとしてライブラリ用のディレクトリ**[work/mylib]**も作成します。作業手順として、ターミナルを使ってコマンドを実行した結果を記載しますので参考にしてください。

```shell:コマンド実行
EIUEMURA-M-W193:~ eiuemura$ echo $HOME
/Users/eiuemura
EIUEMURA-M-W193:~ eiuemura$ mkdir $HOME/work/
EIUEMURA-M-W193:~ eiuemura$ mkdir $HOME/work/mylib
```

###1-2. モジュールを作成 : ip_address_module.py
先程の手順の中で、`mylib`という自作モジュールのライブラリを置くためのディレクトリを作成しています。
その`mylib`配下に、`ip_address_module.py`というファイルを作成して、他のpythonスクリプトから参照できるように準備します。

```shell:コマンド実行
EIUEMURA-M-W193:~ eiuemura$ cd $HOME/work/mylib/
EIUEMURA-M-W193:mylib eiuemura$ touch ip_address_module.py
EIUEMURA-M-W193:mylib eiuemura$ vi ip_address_module.py
```

まず最初に、下記の内容`ip_address_module.py`を全て選択してクリップボードにコピーします。
次に、`vi`エディタを開いて、クリップボードの内容を貼り付けて保存します。

`vi`エディタを使って編集モードに移行するには、`i`か`a`を入力すると最下段に`-- INSERT --`と表示されますので、その状態から貼り付けを実施します。貼り付けが完了しましたら、`esc`キーをクリックして編集モードを終了します。

最後に、編集したファイルを保存して終了するには、`:wq`と入力して完了となります。

```python:ip_address_module.py
# -*- coding: utf-8 -*-
# 日本語のコメントも記述可能


class IPv4_ADDRESS:

    def __init__(self):
        self.oct1_carry = 0            # 第1オクテットの桁が上がる際に加算する数を指定
        self.oct2_carry = 0            # 第2オクテットの桁が上がる際に加算する数を指定
        self.oct3_carry = 0            # 第3オクテットの桁が上がる際に加算する数を指定
        self.oct4_carry = 0            # 第4オクテットの桁が上がる際に加算する数を指定
        self.oct1_num_of_repeats = 0   #
        self.oct2_num_of_repeats = 0   #
        self.oct3_num_of_repeats = 0   #
        self.oct4_num_of_repeats = 0   #
        self.oct1_initial_number = 0   #
        self.oct2_initial_number = 0   #
        self.oct3_initial_number = 0   #
        self.oct4_initial_number = 0   #
        self.mask_bit = 0              # /0 から /32 までの範囲で、ビット数を指定
        self.mask_str_address    = ""  # mask_bit = 24 の場合、"255.255.255.0"に変換して代入する

        self._NUM_WORK2 = []
        self._NUM_WORK3 = []
        self._NUM_WORK4 = []
        self.list_octet = [0,0,0,0]
        self._mask_bit_max = 32        # 32 固定値
        self.mask_bit_num = 0          # サブネットマスクを数値に変換して代入する
        self._oct1_mask_num = 0
        self._oct2_mask_num = 0
        self._oct3_mask_num = 0
        self._oct4_mask_num = 0

    def set_init_addr(self, oct1, oct2, oct3, oct4):
        self.oct1_initial_number = oct1
        self.oct2_initial_number = oct2
        self.oct3_initial_number = oct3
        self.oct4_initial_number = oct4
        return

    def set_carry(self, oct1, oct2, oct3, oct4):
        self.oct1_carry = oct1
        self.oct2_carry = oct2
        self.oct3_carry = oct3
        self.oct4_carry = oct4
        return

    def set_repeats(self, oct1, oct2, oct3, oct4):
        if( oct1 >=1 and oct2 >=1 and oct3>=1 and oct4 >=1):
            self.oct1_num_of_repeats = oct1
            self.oct2_num_of_repeats = oct2
            self.oct3_num_of_repeats = oct3
            self.oct4_num_of_repeats = oct4
            return
        else:
            print('ERROR : set_repeats should be specified more than equal to 1.')
            exit()

    def set_mask_bit(self, bit):
        self.mask_bit = bit
        return

    def __calc_address(self, num_loop):
        self._NUM_WORK2 = self.oct2_num_of_repeats * self.oct3_num_of_repeats * self.oct4_num_of_repeats
        self._NUM_WORK3 = self.oct3_num_of_repeats * self.oct4_num_of_repeats
        self._NUM_WORK4 = self.oct4_num_of_repeats
        self.list_octet = [0,0,0,0]
        self.list_octet[0], self.list_octet[1] = divmod(num_loop, self._NUM_WORK2)
        self.list_octet[1], self.list_octet[2] = divmod(self.list_octet[1], self._NUM_WORK3)
        self.list_octet[2], self.list_octet[3] = divmod(self.list_octet[2], self._NUM_WORK4)
        self.list_octet[0] = self.oct1_carry * self.list_octet[0] + self.oct1_initial_number
        self.list_octet[1] = self.oct2_carry * self.list_octet[1] + self.oct2_initial_number
        self.list_octet[2] = self.oct3_carry * self.list_octet[2] + self.oct3_initial_number
        self.list_octet[3] = self.oct4_carry * self.list_octet[3] + self.oct4_initial_number

    def get_addr(self, num_loop, num_octet):
        self.__calc_address(num_loop)
        if num_octet is 1:
            return self.list_octet[0]
        elif num_octet is 2:
            return self.list_octet[1]
        elif num_octet is 3:
            return self.list_octet[2]
        elif num_octet is 4:
            return self.list_octet[3]
        return 0

    def get_str_mask(self):
        if self.mask_bit < 0 or self.mask_bit > self._mask_bit_max:
            self.mask_bit = self._mask_bit_max
        self.mask_bit_num = ((2 ** self._mask_bit_max) - 1) ^ ((2 ** (self._mask_bit_max - self.mask_bit)) - 1)
        self._oct1_mask_num, self._oct2_mask_num = divmod(self.mask_bit_num, 0x1000000)
        self._oct2_mask_num, self._oct3_mask_num = divmod(self._oct2_mask_num, 0x10000)
        self._oct3_mask_num, self._oct4_mask_num = divmod(self._oct3_mask_num, 0x100)
        str_mask = format(self.mask_bit_num, 'b')
        self.mask_str_address = '{0}.{1}.{2}.{3}'.format(self._oct1_mask_num, self._oct2_mask_num, self._oct3_mask_num, self._oct4_mask_num)
        return self.mask_str_address
```

###1-3. モジュールを作成 : read_text_file.py

同じ手順で、下記のファイルも作成します。

```shell:コマンド実行
EIUEMURA-M-W193:mylib eiuemura$ touch read_text_file.py
EIUEMURA-M-W193:mylib eiuemura$ vi read_text_file.py
```

```python:read_text_file.py
# -*- coding: utf-8 -*-
# 日本語のコメントも記述可能

import sys,re

class ReadTextFile:

    def __init__(self, arg_filename):
        self.all_lines = []
        self.line = ''
        self.all_lines_length = 0
        self.p = re.compile('')
        self.temp = []
        self.str_line = ""

        try:
            self.fp = open(arg_filename, 'r')
            for self.str_line in self.fp.readlines():
                self.all_lines.append(self.str_line.rstrip())
            self.all_lines_length = len(self.all_lines)
        except IOError:
            print('File open error: "{0}}" cannot be opened.').format(arg_filename)
        else:
            self.fp.close()

    def __check_start_num(self, num):
        if num < 1:
            print('ERROR: incorrect input value.( < 0)')
            return False
        elif num > self.all_lines_length:
            print('ERROR: incorrect input value.( > max)')
            return False
        return True

    def __check_start_end_line_num(self, s, e):
        if self.__check_start_num(s) is False:
            return False
        if self.__check_start_num(e) is False:
            return False
        if s > e:
            print('ERROR: incorrect input value.(start > end)')
            return False
        return True

    def print_all(self):
        for self.str in self.all_lines:
            print(self.str)

    def print_range(self, s, e):
        if self.__check_start_end_line_num(s, e) is False:
            return
        for self.str in self.all_lines[(s - 1):e]:
            print(self.str)

    def get_line(self, s):
        if self.__check_start_num(s) is True:
            return self.all_lines[s - 1]

    def del_line(self, s):
        if self.__check_start_num(s) is True:
            del self.all_lines[s - 1]
            self.all_lines_length = len(self.all_lines)

    def get_line_length(self):
        if self.all_lines_length is 0:
            return 0
        else:
            return (self.all_lines_length + 1)

    def get_line_split(self, s, st, i):
        if self.__check_start_num(s) is True:
            self.temp = re.split(st, self.all_lines[s - 1])
            if len(self.temp) is 1:
                return False
            if len(self.temp) < i:
                return False
            return self.temp[i - 1]

    def regex_search(self, s, st):
        if s < 1:
            print('ERROR: incorrect input value.')
            return False
        self.p = re.compile(st)
        if self.p.search(self.all_lines[s - 1]):
            return True
        else:
            return False
```
##2. インタフェースコンフィグの作成
早速ですが、サンプルとして`IOS-XR`を想定したコンフィグを作成することに挑戦してみたいと思います。

まず、スクリプトを作成する前に、作業用のディレクトリに移動します。

```shell:コマンド実行
EIUEMURA-M-W193:mylib eiuemura$ cd $HOME/work/
EIUEMURA-M-W193:work eiuemura$ 
```

###2-1. サブインタフェース(1-1000) : 192.168.1.1/24

実行するためのスクリプト本体を作成します。

```shell:コマンド実行
EIUEMURA-M-W193:work eiuemura$ touch subif_01_192.168.1.1_24.py
EIUEMURA-M-W193:work eiuemura$ vi subif_01_192.168.1.1_24.py
```
`vi`エディタが開いたら、`i`か`a`、下記の内容をコピー&ペーストしてから、`ESC`、`:wq`で保存します。

```python:subif_01_192.168.1.1_24.py
import os
import sys

sys.path.append(os.path.join(os.path.dirname(__file__), './mylib'))

import ip_address_module

ip = ip_address_module.IPv4_ADDRESS()
ip.set_init_addr(192, 168, 1, 1)
ip.set_carry(1, 1, 1, 0)
ip.set_repeats(255, 255, 255, 1)
ip.set_mask_bit(24)

for i in range(0,1000):
    print("interface Bundle-Ether1.{0:d}".format(i + 1))
    print(" ipv4 address {0:d}.{1:d}.{2:d}.{3:d} {4}".format(ip.get_addr(i, 1), ip.get_addr(i, 2), ip.get_addr(i, 3), ip.get_addr(i, 4), ip.get_str_mask()))
    print(" ipv6 address 2001:{0:d}:{1:d}:{2:d}::{3:d}/64".format(ip.get_addr(i, 1), ip.get_addr(i, 2), ip.get_addr(i, 3), ip.get_addr(i, 4)))
    print(" encapsulation dot1q {0:d}".format(i + 1))
    print("!")
```

上記のスクリプトを実行した結果として、全てのコンフィグは記載できませんので、一部の繰り返し部分は`(snip)`として省略しています。

この結果からは、192.168.1.1/24から始まるアドレスで、第三オクテットが**[1]**だけインクリメントされて、**[1]**から**[255]**まで繰り返すと、次に、第二オクテットが**[1]**だけインクリメントされて、**[168]**から**[169]**に増えていることが確認できます。

あとは、1000までの繰り返しで、192.171.235.1/24のところでスクリプトが終了しています。

```shell:コマンド実行
EIUEMURA-M-W193:work eiuemura$ python3 subif_01_192.168.1.1_24.py
```

```shell:実行結果
interface Bundle-Ether1.1
 ipv4 address 192.168.1.1 255.255.255.0
 ipv6 address 2001:192:168:1::1/64
 encapsulation dot1q 1
!
interface Bundle-Ether1.2
 ipv4 address 192.168.2.1 255.255.255.0
 ipv6 address 2001:192:168:2::1/64
 encapsulation dot1q 2
!
(snip)
!
interface Bundle-Ether1.255
 ipv4 address 192.168.255.1 255.255.255.0
 ipv6 address 2001:192:168:255::1/64
 encapsulation dot1q 255
!
interface Bundle-Ether1.256
 ipv4 address 192.169.1.1 255.255.255.0
 ipv6 address 2001:192:169:1::1/64
 encapsulation dot1q 256
!
(snip)
!
interface Bundle-Ether1.1000
 ipv4 address 192.171.235.1 255.255.255.0
 ipv6 address 2001:192:171:235::1/64
 encapsulation dot1q 1000
!
```

####2-1-1. ベースとなるアドレスについて

スクリプト本体の中で、下記の抜粋した箇所がIPアドレスを作成する際のベースとなります。

```python:subif_01_192.168.1.1_24.py
ip.set_init_addr(192, 168, 1, 1)
```

####2-1-2. インクリメント(加算)する値について

次に、下記の抜粋した箇所でインクリメントする値を指定します。これは、第一から第四までの各オクテットに加算する際の値となります。

```python:subif_01_192.168.1.1_24.py
ip.set_carry(1, 1, 1, 0)
```
この内容から、第一オクテットから、第四オクテットまでの値は下記の通りであることがご理解いただけるかと思います。

第一オクテット = 1
第二オクテット = 1
第三オクテット = 1
第四オクテット = 0

これらの値は、**[1]**から**[1000]**までインクリメントが繰り返される中で、それぞれのオクテット部分に加算する値を指定します。

例えば、第四オクテットは、**[0]**が指定されていますので、第四オクテットの値が増加することはなく、ベースとなるアドレスで指定した第四オクテットの値、この例では、192.168.1.1の**[1]**から変わることはない。という意味になります。

次に、第三オクテットは、**[1]**が指定されています。これは、2-1-3の**繰り返しを実行する値について**に関連しますが、
**桁が上がる**ときに加算する値になりますので、この例では、**192.168.1.1**から**192.1682.1**に変化する際に加算される値となります。

第二オクテットも第三オクテットと同様に、**[1]**が指定されていますので、この例では、**192.168.255.1**から**192.169.1.1**に変化する際に加算される値となります。

####2-1-3. 繰り返しを実行する値について

ここでは、下記の抜粋した箇所で繰り返しを実行する値を指定します。前述の通り、インクリメントする値にも関連します。

```python:subif_01_192.168.1.1_24.py
ip.set_repeats(255, 255, 255, 1)
```

この内容から、第一オクテットから、第四オクテットまでの各オクテットに対して繰り返しを実行するための値は下記の通りであることがご理解いただけるかと思います。

ここで一点、注意事項を説明しますが、ここで指定する値の**最小値**は、**[1]**となります。
もし、**[0]**を指定しますと、正常に動作しなくなりますのでご注意ください。

第一オクテット = 255
第二オクテット = 255
第三オクテット = 255
第四オクテット = 1

例えば、第四オクテットは、**[1]**が指定されていますので、第四オクテットの値がインクリメントされることはなく、ベースとなるアドレスで指定した第四オクテットの値、この例では、192.168.1.1の**[1]**から変わることはない。という意味になります。

ここで少し**クイズ**のようになりますが、もし、第四オクテットに、**[2]**を指定した場合、**ip.set_repeats(255, 255, 255, 2)**のように変更すると、第四オクテットの値がインクリメントされて、**192.168.1.1**から**192.168.1.2**に変わるのかな、と思われるかも知れません。しかし、**ip.set_carry(1, 1, 1, 0)**で指定しているように、第四オクテットでインクリメントする値として**[0]**が指定されていますので、結果としては、**192.168.1.1**が表示された後に、もう一度、**192.168.1.1**が表示される結果となります。

もちろん、インタフェースのコンフィグを作成する際に同じアドレスが繰り返してコンフィグするとエラーとなりますが、インタフェース毎に`vrf`が指定されている場合であればエラーは表示されません。

必要に応じて、このような使い方もあるということでご理解いただければと思います。

第三オクテットの例に戻りまして、ここでは**[255]**が指定されていますので、第四オクテットの繰り返しが終了すると、第三オクテットの値がインクリメントされます。第三オクテットで**[255]**回の繰り返しが終了すると、次に、第二オクテットの値がインクリメントされます。

####2-1-4. サブネットマスクについて

サブネットマスクですが、IPv4のサブネットマスクに対応するビット数を指定します。他の設定と異なり、必須の設定ではなく完全に独立した機能です。

使用方法としては、**get_str_mask()**を呼び出すと、指定されたビット数に対するサブネットマスクを**文字列**で返します。

```python:subif_01_192.168.1.1_24.py
ip.set_mask_bit(24)
```

この例では、**ip.get_str_mask()**を実行すると、**255.255.255.0**を返します。
サブネットマスクを`print()`に直に指定するのであれば、**ip.get_str_mask()**を使う必要もありません。


###2-2. サブインタフェース(1-2000) : 10.1.1.1/24

実行するためのスクリプト本体を作成します。

```shell:コマンド実行
EIUEMURA-M-W193:work eiuemura$ touch subif_02_10.1.1.1_24.py
EIUEMURA-M-W193:work eiuemura$ vi subif_02_10.1.1.1_24.py
```
`vi`エディタが開いたら、`i`か`a`、下記の内容をコピー&ペーストしてから、`ESC`、`:wq`で保存します。

```python:subif_02_10.1.1.1_24.py
import os
import sys

sys.path.append(os.path.join(os.path.dirname(__file__), './mylib'))

import ip_address_module

ip = ip_address_module.IPv4_ADDRESS()
ip.set_init_addr(10, 0, 1, 1)
ip.set_carry(1, 1, 1, 0)
ip.set_repeats(255, 100, 100, 1)
ip.set_mask_bit(24)

for i in range(0,2000):
    print("interface Bundle-Ether1.{0:d}".format(i + 1))
    print(" ipv4 address {0:d}.{1:d}.{2:d}.{3:d} {4}".format(ip.get_addr(i, 1), ip.get_addr(i, 2), ip.get_addr(i, 3), ip.get_addr(i, 4), ip.get_str_mask()))
    print(" ipv6 address 2001:{0:d}:{1:d}:{2:d}::{3:d}/64".format(ip.get_addr(i, 1), ip.get_addr(i, 2), ip.get_addr(i, 3), ip.get_addr(i, 4)))
    print(" encapsulation dot1q {0:d}".format(i + 1))
    print("!")
```

```shell:コマンド実行
EIUEMURA-M-W193:work eiuemura$ python3 subif_02_10.1.1.1_24.py
```

```shell:実行結果
interface Bundle-Ether1.1
 ipv4 address 10.0.1.1 255.255.255.0
 ipv6 address 2001:10:0:1::1/64
 encapsulation dot1q 1
!
interface Bundle-Ether1.2
 ipv4 address 10.0.2.1 255.255.255.0
 ipv6 address 2001:10:0:2::1/64
 encapsulation dot1q 2
!
(snip)
!
interface Bundle-Ether1.100
 ipv4 address 10.0.100.1 255.255.255.0
 ipv6 address 2001:10:0:100::1/64
 encapsulation dot1q 100
!
(snip)
!
interface Bundle-Ether1.2000
 ipv4 address 10.19.100.1 255.255.255.0
 ipv6 address 2001:10:19:100::1/64
 encapsulation dot1q 2000
!
```

####2-2-1. 繰り返しを実行する値について

この例では、ベースとなるアドレスをインクリメントされる状況を分かりやすくするために、**192.168.1.1**から**10.0.1.1**に変更しています。

```python:subif_01_192.168.1.1_24.py
ip.set_init_addr(192, 168, 1, 1)
ip.set_carry(1, 1, 1, 0)
ip.set_repeats(255, 255, 255, 1)
```

```python:subif_02_10.1.1.1_24.py
ip.set_init_addr(10, 0, 1, 1)
ip.set_carry(1, 1, 1, 0)
ip.set_repeats(255, 100, 100, 1)
```

また、**set_repeats(255, 100, 100, 1)**の値も下記のように変更しています。

第一オクテット = 255
第二オクテット = 100
第三オクテット = 100
第四オクテット = 1

これで、第三オクテットのインクリメントを100回繰り返してから、第二オクテットを1回だけインクリメントする。
第二オクテットのインクリメントが100回繰り返されたら、第一オクテットを1回だけインクリメントする。
という動作になることがご理解いただけるかと思います。


###2-3. サブインタフェース(1-4000) : 172.16.1.1/29

実行するためのスクリプト本体を作成します。

```shell:コマンド実行
EIUEMURA-M-W193:work eiuemura$ touch subif_03_172.16.1.1_29.py
EIUEMURA-M-W193:work eiuemura$ vi subif_03_172.16.1.1_29.py
```
`vi`エディタが開いたら、`i`か`a`、下記の内容をコピー&ペーストしてから、`ESC`、`:wq`で保存します。

```python:subif_03_172.16.1.1_29.py
import os
import sys

sys.path.append(os.path.join(os.path.dirname(__file__), './mylib'))

import ip_address_module

ip = ip_address_module.IPv4_ADDRESS()
ip.set_init_addr(172, 16, 1, 1)
ip.set_carry(1, 1, 1, 8)
ip.set_repeats(255, 255, 255, 32)
ip.set_mask_bit(29)

for i in range(0,4000):
    print("interface Bundle-Ether1.{0:d}".format(i + 1))
    print(" ipv4 address {0:d}.{1:d}.{2:d}.{3:d} {4}".format(ip.get_addr(i, 1), ip.get_addr(i, 2), ip.get_addr(i, 3), ip.get_addr(i, 4), ip.get_str_mask()))
    print(" ipv6 address 2001:{0:d}:{1:d}:{2:d}::{3:d}/64".format(ip.get_addr(i, 1), ip.get_addr(i, 2), ip.get_addr(i, 3), ip.get_addr(i, 4)))
    print(" encapsulation dot1q {0:d}".format(i + 1))
    print("!")
```

```shell:コマンド実行
EIUEMURA-M-W193:work eiuemura$ python3 subif_03_172.16.1.1_29.py
```

```shell:実行結果
interface Bundle-Ether1.1
 ipv4 address 172.16.1.1 255.255.255.248
 ipv6 address 2001:172:16:1::1/64
 encapsulation dot1q 1
!
interface Bundle-Ether1.2
 ipv4 address 172.16.1.9 255.255.255.248
 ipv6 address 2001:172:16:1::9/64
 encapsulation dot1q 2
!
(snip)
!
interface Bundle-Ether1.32
 ipv4 address 172.16.1.249 255.255.255.248
 ipv6 address 2001:172:16:1::249/64
 encapsulation dot1q 32
!
interface Bundle-Ether1.33
 ipv4 address 172.16.2.1 255.255.255.248
 ipv6 address 2001:172:16:2::1/64
 encapsulation dot1q 33
!
interface Bundle-Ether1.34
 ipv4 address 172.16.2.9 255.255.255.248
 ipv6 address 2001:172:16:2::9/64
 encapsulation dot1q 34
!
(snip)
!
interface Bundle-Ether1.3998
 ipv4 address 172.16.125.233 255.255.255.248
 ipv6 address 2001:172:16:125::233/64
 encapsulation dot1q 3998
!
interface Bundle-Ether1.3999
 ipv4 address 172.16.125.241 255.255.255.248
 ipv6 address 2001:172:16:125::241/64
 encapsulation dot1q 3999
!
interface Bundle-Ether1.4000
 ipv4 address 172.16.125.249 255.255.255.248
 ipv6 address 2001:172:16:125::249/64
 encapsulation dot1q 4000
!
```

####2-3-1. インクリメント(加算)する値について

これまでの例では、サブネットマスクが**/24**でしたので、**set_carry(1, 1, 1, 0)**の第四オクテットに**[0]**を指定しました。

今回は、**/29**となりますので、各サブネットに8つのアドレスが割り当てられますので、**set_carry(1, 1, 1, 8)**の第四オクテットにも**[8]**を指定します。これは、第四オクテットのアドレスだけは、**[8]**が加算されることを示します。

```python:subif_01_192.168.1.1_24.py
ip.set_init_addr(192, 168, 1, 1)
ip.set_carry(1, 1, 1, 0)
ip.set_repeats(255, 255, 255, 1)
```

```python:subif_02_10.1.1.1_24.py
ip.set_init_addr(10, 0, 1, 1)
ip.set_carry(1, 1, 1, 0)
ip.set_repeats(255, 100, 100, 1)
```

```python:subif_03_172.16.1.1_29.py
ip.set_init_addr(172, 16, 1, 1)
ip.set_carry(1, 1, 1, 8)
ip.set_repeats(255, 255, 255, 32)
ip.set_mask_bit(29)
```

####2-3-2. 繰り返しを実行する値について

前述の通り、第四オクテットのアドレスだけは、**[8]**が加算されますので、第四オクテットのアドレスレンジを全て使うのであれば、256/8=32の**[32]**を**set_repeats(255, 255, 255, 32)**に指定します。

**このスクリプトは、下記のという関係で成り立っていることに注意して値を指定してください。**

**set_carry**は、加算する値を指定(0の指定も可能)
**set_repeats**は、繰り返す値を指定(1以上の値)

```python:subif_01_192.168.1.1_24.py
ip.set_init_addr(192, 168, 1, 1)
ip.set_carry(1, 1, 1, 0)
ip.set_repeats(255, 255, 255, 1)
```

```python:subif_02_10.1.1.1_24.py
ip.set_init_addr(10, 0, 1, 1)
ip.set_carry(1, 1, 1, 0)
ip.set_repeats(255, 100, 100, 1)
```

```python:subif_03_172.16.1.1_29.py
ip.set_init_addr(172, 16, 1, 1)
ip.set_carry(1, 1, 1, 8)
ip.set_repeats(255, 255, 255, 32)
ip.set_mask_bit(29)
```

###2-4. IPv4_ADDRESS : 機能のまとめ

前述のサンプルスクリプトで使用されている各機能について注意事項も合わせて説明します。

####2-4-1. set_init_addr : 開始するアドレスを設定

生成するIPv4アドレスのベースとなるアドレスを指定します。

**set_init_addr**(引数1,引数2,引数3,引数4)
引数1 : IPアドレスの第一オクテット
引数2 : IPアドレスの第二オクテット
引数3 : IPアドレスの第三オクテット
引数4 : IPアドレスの第四オクテット


####2-4-2. set_carry : 加算する値を指定

各オクテットに加算する値を指定します。
※ `set_repeats`で指定された回数が"1"の場合は、`set_carry`に指定する値が"0"以外でも結果には影響しません。

**set_carry**(引数1,引数2,引数3,引数4)
引数1 : 第一オクテットに加算される値
引数2 : 第二オクテットに加算される値
引数3 : 第三オクテットに加算される値
引数4 : 第四オクテットに加算される値


####2-4-3. set_repeats : 繰り返す回数を指定

各オクテットで加算(`set_carry`で指定された値)を繰り返す回数を指定します。
※ 1以上の値を指定する必要があります。

**set_repeats**(引数1,引数2,引数3,引数4)
引数1 : 第一オクテットで繰り返す回数
引数2 : 第二オクテットで繰り返す回数
引数3 : 第三オクテットで繰り返す回数
引数4 : 第四オクテットで繰り返す回数


####2-4-4. set_mask_bit : 指定した範囲を出力

`get_str_mask`を実行した際に得られるサブネットマスクのビット数を指定します。

**set_mask_bit**(引数1)
引数1 : 出力を開始する行番号
引数2 : 出力を終了する行番号


####2-4-5. get_addr : IPアドレスを取得

`set_init_addr` , `set_carry`, `set_repeats` で指定した値からIPアドレスの値を算出して数値で取得します。

**get_addr**(引数1,引数2)
引数1 : ループの回数(`set_repeats`を繰り返す回数)
引数2 : 取得したいオクテットの番号(1〜4)


####2-4-6. get_str_mask : サブネットマスクを取得
`set_mask_bit`で指定したビット数に対応するサブネットマスクを文字列で取得します。

**get_str_mask**()
引数なし


##3. テキストファイルの読み込みと出力

ここから内容を変えまして、テキストファイルを読み込んで、コマンドを実行したターミナル上で単なる標準出力として出力します。

なぜ、このような内容を試すのかと言うと、これまでIPアドレスのような可変部分をスクリプトで実現していましたが、どの装置でも共通で使うテンプレートのようなコンフィグもあるかと思います。

そこで、共通部分は予め**テキストファイル**として用意しておき、スクリプト本体ではコンフィグの出力に`print()`を使用せずに、その**テキストファイル**を読み込んで出力する。ということで、スクリプト本体をシンプルにしたいと思います。

###3-1. テキスト形式でコンフィグのファイルを作成

テキストファイルを作成します。

```shell:コマンド実行
EIUEMURA-M-W193:work eiuemura$ touch config_base.txt
EIUEMURA-M-W193:work eiuemura$ vi config_base.txt
```
`vi`エディタが開いたら、`i`か`a`、下記の内容をコピー&ペーストしてから、`ESC`、`:wq`で保存します。

```text:config_base.txt
!
hostname R1
clock timezone JST +9
logging events level informational
logging console informational
logging buffered informational
service timestamps log datetime localtime msec show-timezone
service timestamps debug datetime localtime msec show-timezone
!
```

###3-1. テキストファイルを読み込んで出力する

実行するためのスクリプト本体を作成します。

```shell:コマンド実行
EIUEMURA-M-W193:work eiuemura$ touch read_text_01.py
EIUEMURA-M-W193:work eiuemura$ vi read_text_01.py
```
`vi`エディタが開いたら、`i`か`a`、下記の内容をコピー&ペーストしてから、`ESC`、`:wq`で保存します。

```python:read_text_01.py
import os
import sys

sys.path.append(os.path.join(os.path.dirname(__file__), './mylib'))

import read_text_file

f_base_01 = read_text_file.ReadTextFile("config_base.txt")
f_base_01.print_all()
```
実行結果としては、**config_base.txt**の内容がそのまま表示されるだけです。

```shell:コマンド実行
EIUEMURA-M-W193:work eiuemura$ python3 read_text_01.py
```

```shell:実行結果
!
hostname R1
clock timezone JST +9
logging events level informational
logging console informational
logging buffered informational
service timestamps log datetime localtime msec show-timezone
service timestamps debug datetime localtime msec show-timezone
!
```

###3-2. ReadTextFile : その他の機能について

前述で試した**ReadTextFile**には、ファイルの内容を出力するだけでなく、その他にも幾つかの便利な機能がありますので、それぞれの機能について説明します。

ここでは、全ての機能について個別のサンプルスクリプトで説明するのではなく、一つのスクリプトで全ての機能を実行する内容となります。

読み込んだファイルは一時的なバッファのような領域に保持しますので、オリジナルのファイルが変更されることはありません。
そのバッファに保持した内容に対して、ここで説明している各関数を使ってデータにアクセスするように、データと操作する関数が一つになったオブジェクトが**f_base_01**だと考えてください。


実行するためのスクリプト本体を作成します。

```shell:コマンド実行
EIUEMURA-M-W193:work eiuemura$ touch read_text_02.py
EIUEMURA-M-W193:work eiuemura$ vi read_text_02.py
```
`vi`エディタが開いたら、`i`か`a`、下記の内容をコピー&ペーストしてから、`ESC`、`:wq`で保存します。

```python:read_text_02.py
import os
import sys

sys.path.append(os.path.join(os.path.dirname(__file__), './mylib'))

import read_text_file

f_base_01 = read_text_file.ReadTextFile("config_base.txt")

print("-------------------------------------------------- print_all")
f_base_01.print_all()
print("-------------------------------------------------- print_range")
f_base_01.print_range(2,3)
print("-------------------------------------------------- get_line ")
print(f_base_01.get_line(2))
print(f_base_01.get_line(3))
print("-------------------------------------------------- get_line_length")
print(f_base_01.get_line_length())
print("-------------------------------------------------- get_line_split")
print(f_base_01.get_line_split(2," ", 1))
print(f_base_01.get_line_split(2," ", 2))
print("-------------------------------------------------- regex_search")
print(f_base_01.regex_search(2,"hostname"))
print(f_base_01.regex_search(2,"R1"))
print(f_base_01.regex_search(2,"R2"))
print("-------------------------------------------------- del_line")
f_base_01.del_line(2)
f_base_01.del_line(3)
f_base_01.print_all()
print("--------------------------------------------------")
```

```shell:コマンド実行
EIUEMURA-M-W193:work eiuemura$ python3 read_text_02.py
```

```shell:実行結果
-------------------------------------------------- print_all
!
hostname R1
clock timezone JST +9
logging events level informational
logging console informational
logging buffered informational
service timestamps log datetime localtime msec show-timezone
service timestamps debug datetime localtime msec show-timezone
!
-------------------------------------------------- print_range
hostname R1
clock timezone JST +9
-------------------------------------------------- get_line 
hostname R1
clock timezone JST +9
-------------------------------------------------- get_line_length
10
-------------------------------------------------- get_line_split
hostname
R1
-------------------------------------------------- regex_search
True
True
False
-------------------------------------------------- del_line
!
logging events level informational
logging console informational
logging buffered informational
service timestamps log datetime localtime msec show-timezone
service timestamps debug datetime localtime msec show-timezone
!
--------------------------------------------------
```

####3-2-1. print_all : 全ての内容を出力

既に実行していますが、もう一度、おさらいです。

**print_all**()
引数なし

```python:read_text_02.py
print("-------------------------------------------------- print_all")
f_base_01.print_all()
```

```shell:実行結果
-------------------------------------------------- print_all
!
hostname R1
clock timezone JST +9
logging events level informational
logging console informational
logging buffered informational
service timestamps log datetime localtime msec show-timezone
service timestamps debug datetime localtime msec show-timezone
!
```

####3-2-2. print_range : 指定した範囲を出力

ここでは、ファイルを読み込んでから、2行目と3行目を出力しています。

**print_range**(引数1,引数2)
引数1 : 出力を開始する行番号
引数2 : 出力を終了する行番号

```python:read_text_02.py
print("-------------------------------------------------- print_range")
f_base_01.print_range(2,3)
```

```shell:実行結果
-------------------------------------------------- print_range
hostname R1
clock timezone JST +9
```

####3-2-3. get_line : 指定した行番号の内容を取得

ここでは、2行目を取得して、そのまま`print()`で出力しています。
また、2行目と同様に3行目も取得してから`print()`で出力しています。

こうすると、スクリプトを実行した結果は、**f_base_01.print_range(2,3)**を実行した結果と同じ出力結果になります。

**get_line**(引数1)
引数1 : 取得したい行番号

```python:read_text_02.py
print("-------------------------------------------------- print_range")
print(f_base_01.get_line(2))
print(f_base_01.get_line(3))
```

```shell:実行結果
-------------------------------------------------- print_range
hostname R1
clock timezone JST +9
```

####3-2-4. get_line_length : 読み込んだファイルの行数を取得

ここでは、読み込んだファイルを保持するバッファの行数を返します。
オリジナルファイルの行数ではありません。

**get_line_length**()
引数なし

```python:read_text_02.py
print("-------------------------------------------------- get_line_length")
print(f_base_01.get_line_length())
```

```shell:実行結果
-------------------------------------------------- get_line_length
10
```

####3-2-5. get_line_split : 指定した行に対して"区切り文字列"で分割して値を取得

ここでは、指定した行の内容に対して、空白文字を使って行の内容を分割して、分割された値を順番に保持し指定された番号の値を返す。

この例では、2行目を空白で分割すると、一つ目が**hostname**となり、二つ目が**R1**となります。

**get_line_split**(引数1,引数2,引数3)
引数1 : 取得したい行番号
引数2 : 行の内容を区切るための文字を指定(ここでは"空白"を区切りとして使用)
引数3 : 区切り文字で分割された値を取得する番号

```python:read_text_02.py
print("-------------------------------------------------- get_line_split")
print(f_base_01.get_line_split(2," ", 1))
print(f_base_01.get_line_split(2," ", 2))
```

```shell:実行結果
-------------------------------------------------- get_line_split
hostname
R1
```

####3-2-6. regex_search : 指定した行の内容に指定した文字列が含まれるかの判定

ここでは、指定した行の内容に対して、検索したい文字列が含まれるかどうかを返す。

**regex_search**(引数1,引数2)
引数1 : 検索したい行番号
引数2 : 検索したい文字列

戻り値 : 検索したい文字列が見つかった場合は**True**を返して、見つからなかった場合は**False**を返す

```python:read_text_02.py
print("-------------------------------------------------- regex_search")
print(f_base_01.regex_search(2,"hostname"))
print(f_base_01.regex_search(2,"R1"))
print(f_base_01.regex_search(2,"R2"))
```

```shell:実行結果
-------------------------------------------------- regex_search
True
True
False
```

####3-2-7. del_line : 指定した行を削除

ここでは、指定した行を削除しますが、オリジナルのファイルは変更されません。

この例では、3行目を削除してから、2行目を削除しているところを注意してください。
先に2行目を削除してから、もう一度、2行目を削除する場合であれば、この例と同じ結果になります。
もし、先に2行目を削除してから、3行目を削除すると異なる結果となり、`logging events level informational`の行が削除され、`clock timezone JST +9`の行が表示されます。

**regex_search**(引数1,引数2)
引数1 : 検索したい行番号
引数2 : 検索したい文字列

戻り値 : 検索したい文字列が見つかった場合は**True**を返して、見つからなかった場合は**False**を返す

```python:read_text_02.py
print("-------------------------------------------------- del_line")
f_base_01.del_line(3)
f_base_01.del_line(2)
f_base_01.print_all()
```

```shell:実行結果
-------------------------------------------------- del_line
!
logging events level informational
logging console informational
logging buffered informational
service timestamps log datetime localtime msec show-timezone
service timestamps debug datetime localtime msec show-timezone
!
```

#まとめ
ここでは、例としてインタフェースの設定を取り上げていますが、`BGP`や`HSRP`など、その他にもIPアドレスが関連する部分であれば利用できますので、色々な機器の設定を作成する際にこのツールを試してみてください。

検証が続くとコンフィグも次第に変化しますし、その都度、機器にログインして手動で変更すると、一体、どこを変更したのか分からなくなってきます。

コンフィグ全体をこのようなスクリプトで作成して管理(Gitを使って世代管理するのもありかな)というのは、非常に大変で面倒な事ですが、コンフィグ間違い(Active/Standby間の不整合など)によるトラブルシューティングでロスする時間の方が大きい場合もあると思います。

まずは、簡単なところからスクリプトを作成して、少しでも負荷を減らすことができれば幸いです。

##最後に

ここまでお付き合いくださり、ありがとうございました。

間違っているところ、「そもそも、動かへんやん…」など、お気づきの点がありましたら対応いたしますので、お気軽にコメントいただければと存じます。





## おまけ : NETCONF/RESTCONF
プログラミングという観点では、`NETCONF`や`RESTCONF`を利用する機会も増えてきておりますので、ターミナルからのコンフィグ流し込みでの設定だけでなく、下記のサイトもご参照いただければ幸いです。

[RESTCONF を使って、Ciscoルータのコンフィグを設定する(IOS-XE)](https://qiita.com/eiuemura/items/56ff49a5a59176df5ece "Qiita")
[NETCONF/YANG を使って、Ciscoルータのコンフィグを設定する(IOS-XR)](https://qiita.com/eiuemura/items/fd2c4124b309814a3f6a "Qiita")


#免責事項
本サイトおよび対応するコメントにおいて表明される意見は、投稿者本人の個人的意見であり、シスコの意見ではありません。本サイトの内容は、情報の提供のみを目的として掲載されており、シスコや他の関係者による推奨や表明を目的としたものではありません。各利用者は、本Webサイトへの掲載により、投稿、リンクその他の方法でアップロードした全ての情報の内容に対して全責任を負い、本Web サイトの利用に関するあらゆる責任からシスコを免責することに同意したものとします。