ネットワーク構成図の自動作成を目指す！（5）

　艦これの2017年秋イベント開始しましたが、まだ様子見な丙提督です。
　今日は既存のスクリプトを改修して実用に近づけられるようなものにブラッシュアップする回にしたいと思います。
　…涼月可愛い、絶対欲しい。

#MySQLのテーブルの構成を変更

- vlanテーブル
 - pingスキャンを掛けるためのテーブル

```
+----------------------+---------------+
| VLAN                 | NW_ADDRESS    |
+----------------------+---------------+
| GigabitEthernet9.901 | 10.240.1.0/25 |
| Vlan1                | 10.1.1.0/24   |
| Vlan2                | 10.1.2.0/24   |
| Vlan3                | 10.1.3.0/24   |
| Vlan4                | 10.1.4.0/24   |
+----------------------+---------------+
```

- nw_dataテーブル
 - nwdiagのconfigファイルを作り出すためのテーブル 

```
+-----------------+-------------------------------+----------------------+
| IP_ADDRESS      | HOST                          | VLAN                 |
+-----------------+-------------------------------+----------------------+
| 10.240.1.1      | tky-rt-01                     | GigabitEthernet9.901 |
| 10.1.1.252      | tky-rt-01                     | Vlan1                |
| 10.1.2.252      | tky-rt-01                     | Vlan2                |
| 10.1.3.252      | tky-rt-01                     | Vlan3                |
| 10.1.4.252      | tky-rt-01                     | Vlan4                |
+-----------------+-------------------------------+----------------------+
```

#その2のスクリプト改修

　まずは、[その2](https://qiita.com/Fortran/items/95017dcec32a24610e5b)で紹介したスクリプトに判定条件を付けてみました。判定条件とは言っても、テーブルに存在していればinsertはせず既存の情報をupdateするsqlを投げ、なければ新規情報としてinsertするという仕組みです。

```zisaku2.py
import textfsm
import pprint
import mysql.connector
from netaddr import *

conn=mysql.connector.connect(
        host='127.0.0.1',
        port=3306,
        user='*******',
        passwd='********',
        db='nw_config')

cur=conn.cursor()

with open('sample.log','r') as f:
        config = f.read()

with open('template_tmp.txt','r') as f:
        table = textfsm.TextFSM(f)
        result = table.ParseText(config)


for row in result:
        (ipaddr,netmask) = row[3].split(" ")
        nw = IPNetwork(ipaddr)
        nw.prefixlen=IPAddress(netmask).netmask_bits()
        nw=nw.cidr
        host = row[0]
        vlan = row[1]

        a_check_sql="SELECT * FROM vlan WHERE VLAN=%s"
        cur.execute(a_check_sql, (vlan,))
        data1=cur.fetchall()
        res1=len(data1)

        b_check_sql="SELECT * FROM nw_data WHERE IP_ADDRESS =%s"
        cur.execute(b_check_sql,(ipaddr,))
        data2=cur.fetchall()
        res2=len(data2)

        if res1 > 0:
                a_update_sql="UPDATE vlan SET VLAN=%s, NW_ADDRESS=%s WHERE VLAN=%s"
                cur.execute(a_update_sql,(vlan,str(nw),vlan))
                print("Vlan alredy exist. Update now.")
        else:
                a_insert_sql="INSERT INTO vlan (VLAN, NW_ADDRESS) values (%s,%s)"
                cur.execute(a_insert_sql,(vlan, str(nw)))
                print("Finish to insert Vlan. ")

        if res2 > 0:
                b_update_sql="UPDATE nw_data SET IP_ADDRESS=%s, HOST=%s, VLAN=%s WHERE IP_ADDRESS=%s"
                cur.execute(b_update_sql,(str(ipaddr), host, vlan,ipaddr))
                print("IP Addresses already exist. Update now.")
        else:
                b_insert_sql="INSERT INTO nw_data (IP_ADDRESS, HOST, VLAN) values (%s,%s,%s)"
                cur.execute(b_insert_sql,(str(ipaddr), host, vlan))
                print("Finish to insert IP Addresses.")

        conn.commit()

cur.close()
conn.close()
```


#その3のスクリプト改修
　さてさて、今回はこのシリーズの[その3](https://qiita.com/Fortran/items/902d9e7c12a84c546419)で書いたコードにしっかりと判定条件を加えていきます。そうしないとMySQLの行が重複してしまって、次のコンフィグ作成の際に役に立たなくなってしまいますし、ゴミが膨れ上がってinnodbを肥大化させる要因になってしまいます。

##判定条件

　きちんと判別するには、下記のような条件をつければいいかな、と思って実装しました。

- すでにMySQLに登録済みのIPアドレスか？
 - 登録済みならホスト名は前回と異なるか？→異なるならupdate/同じなら何もしない
 - 未登録ならIPアドレスとホスト名を合わせて登録

　若干力業な方法で実装したPerlのソースコードが次になります。

```extract_iplist.pl
#!/usr/bin/perl

use strict;

use DBI;
use Net::Ping::External qw(ping);
use Net::Netmask;
use Net::SNMP;

my $vlan=$ARGV[0];
my $host2;

my $dsn = "DBI:mysql:database=nw_config;host=127.0.0.1;port=3306";
my $dbh = DBI->connect($dsn,"*******", "*******") || die("[ERROR] DB Connect Error");
my $a_sth = $dbh->prepare("select NW_ADDRESS from vlan where VLAN='$vlan'") || die("[ERROR] SQL statement is wrong.");
$a_sth->execute() || die("[ERROR] SQL cannot be executed.");

while( my $ary_ref = $a_sth->fetchrow_arrayref){
        my $nwaddr = $ary_ref->[0];
        my @target=&ip_list($nwaddr);
        my $mask=&netmask($nwaddr);
        foreach my $ip (@target){
                my $alive = ping(
                        host => $ip,
                        timeout => 1,
                        count => 1
                );
                my $c_sth = $dbh->prepare("select HOST from nw_data where IP_ADDRESS='$ip'") || die("[ERROR]SQL statement is wrong.");
                $c_sth->execute;
                my $rv = $c_sth->rows;
                while (my $c_ary_ref = $c_sth->fetchrow_hashref){
                        print "$c_ary_ref->{0}\n";
                        $host2 = $c_ary_ref->{0};
                        if($host2 eq ''){
                                $host2 = "NULL";
                        }
                }
                $c_sth->finish;
                if($alive){
                        my $hostname=&snmpwalk_host($ip);
                        if($hostname eq ''){
                                $hostname= "NULL";
                        }
                        if($rv >= 1){
                                if($hostname eq $host2){
                                        print "$ip: already exist: no change\n";
                                } else {
                                        my $u_sth=$dbh->prepare("update nw_data set HOST='$host2' where IP_ADDRESS='$ip'");
                                        $u_sth->execute();
                                        $u_sth->finish;
                                        print "$ip: already exist: updated\n";
                                }
                        } else {
                                my $b_sth=$dbh->prepare("insert into nw_data (IP_ADDRESS, HOST, VLAN) values (?, ?, ?)") || die("[ERROR] SQL statement is wrong.");
                                $b_sth->execute($ip,$hostname,$vlan) || die("[ERROR] SQL(insert) cannot be executed.");
                                $b_sth->finish;
                                print "$ip: regist\n";
                        }
                } else {
                        if($rv >= 1){
                                my $d_sth = $dbh->prepare("delete from nw_data where IP_ADDRESS='$ip'");
                                $d_sth->execute;
                                $d_sth->finish;
                                print "$ip: unpingable->delete\n";
                        } else {
                                print "$ip: skipped\n";
                        }
                }
        }
}

$a_sth->finish;
$dbh->disconnect;

exit 0;

sub ip_list{
        my @list=();
        my $block = new Net::Netmask ($_[0]);
        for my $IP ($block->enumerate)  {
                push(@list, $IP);
        }
        shift @list;
        pop @list;

        return @list;
}

sub netmask{
        my $block = new Net::Netmask($_[0]);
        my $netmask = $block->mask();
        return $netmask;
}

sub snmpwalk_host{
        my ($session,$error) = Net::SNMP->session(
                -hostname => $_[0],
                -community=> "public",
                -port     => "161"
        );
        my $host_oid=".1.3.6.1.2.1.1.5.0";
        my $request=$session->get_request(
                -varbindlist => [$host_oid]
        );
        my $result=$request->{$host_oid};
        return $result;

        $session->close;
}
```

　1つのIPアドレスに対して、だいたい10秒の処理時間が掛かる感じですね。
　もうちょっとサブルーチンに処理を回したり、書き方を変えたりして高速化したいところです。

　今日はここまで、次は構成図を使ってブラウザ上で編集作業ができる仕組みを考えていきたいと思います。