Cisco DNA Centerとタクトスイッチ連携によるシンプルなネットワーク操作

# はじめに
この記事はシスコの有志による Cisco Systems Japan Advent Calendar 2019 の 8 日目として投稿しています。

- 2017年版: https://qiita.com/advent-calendar/2017/cisco
- 2018年版: https://qiita.com/advent-calendar/2018/cisco
- 2019年版: https://qiita.com/advent-calendar/2019/cisco

# 何がしたい？
- 誰でもできるネットワーク操作
- Ciscoさん一押しの企業向けコントローラーであるCisco DNA Centerの [~~ステマ~~](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%86%E3%83%AB%E3%82%B9%E3%83%9E%E3%83%BC%E3%82%B1%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0) Tipsデモ
- 業務とネタの両立
- よくあるクールなトピックス~~ポリシーとかAIとかアシュアランスとか~~ではなく、地味だけど良く聞かれる操作
- [2017年に買ったグッズの再利用](https://qiita.com/kikuta1978/items/12f9ef8c5f7a64652b12)


# これは何？
- タクトスイッチを使って、ネットワーク管理におけるルーチン作業を自動化するPythonスクリプトを発行
- スクリプトは[Cisco DNA Center](https://www.cisco.com/c/ja_jp/products/cloud-systems-management/dna-center/index.html)の[API](https://developer.cisco.com/dnacenter/)にアクセスし、ネットワーク装置はコントローラーが制御
- Cisco DNA Centerのテンプレート機能を使ったポートshut/no shut、Vlan変更、コンフィグバックアップ
- PoEポート no shutで、LEDを光らせてラボを華やかにする


## 完成図
![IMG_1322.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157325/173fca4a-9b00-c2d6-26da-d6801654ae5c.jpeg)

## 操作用のタクトスイッチ
![IMG_1323.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157325/a8b573d3-15d7-12b5-1e39-b980c52aec9c.jpeg)

## 論理構成
![SS 2019-12-08 0.19.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157325/22fc1dad-b915-0db9-bac7-da2beb136d68.png)


# デモと解説
1. スイッチポートのshut/no shut（PoE経由で給電されるイルミネーションのoff/on）
2. スイッチポートのvlan設定変更（99または1）
3. スイッチコンフィグレーションのFTPサーバーへの一斉バックアップ
4. （おまけ）いま作業中だよ〜というメッセージ通知

## デモ動画
*クリックするとYoutubeが開きます（トータル4分40秒）
[![Simple Network Operation with Cisco DNA Center and TACT switch](http://img.youtube.com/vi/BVIkaV1y46Q/hqdefault.jpg)](https://www.youtube.com/watch?v=BVIkaV1y46Q)

0:16 Demo1: Interface Shut / No shut ... PoEのshut/no shutでLED Off/On
0:51 Demo2: Vlan change ... Vlan99とVlan1の切り替え
2:00 Demo3: Configuration backup ... スイッチコンフィグレーションのFTPサーバへのバックアップ
3:27 Demo4: Notification to SNS ... 作業通知をWebEx Teamsへ投稿

## 設定, スクリプト

### Raspberry Pi

ラズパイ上にPythonスクリプトを七つ用意しています。一つ（template_switch.py）は、各ボタンの入力を受け付けて、六つのどれかのスクリプトを実行するためのものです。残りのスクリプトは、それぞれの用途に応じて、Cisco DNA CenterのAPIに向かって命令します（一つだけ直接 WebEx Teamsに投稿するものがあります）。

```bash

pi@raspberrypi:~/code $ uname -a
Linux raspberrypi 4.19.75-v7+ #1270 SMP Tue Sep 24 18:45:11 BST 2019 armv7l GNU/Linux
pi@raspberrypi:~/code $ ls -la
合計 36
drwxr-xr-x  2 root root 4096 12月  7 03:11 .
drwxr-xr-x 21 pi   pi   4096 12月  6 01:29 ..
-rwxr-xr-x  1 root root  845 12月  6 01:30 teams.py
-rwxr-xr-x  1 root root 1285 12月  5 23:08 templateConfigbackup.py
-rwxr-xr-x  1 root root 1246 12月  4 04:42 templateNoShut.py
-rwxr-xr-x  1 root root 1243 12月  4 04:41 templateShut.py
-rwxr-xr-x  1 root root 1369 12月  6 00:45 templateVlan1.py
-rwxr-xr-x  1 root root 1370 12月  6 00:46 templateVlan99.py
-rwxr-xr-x  1 root root 1894 12月  6 06:22 template_switch.py
```

六つのボタン（[タクトスイッチ](http://akizukidenshi.com/catalog/g/gP-03647/)）を、それぞれ六つのGPIO（General-purpose input/output）とGNDの間に接続し、以下のコードでInputを受け付けられるようにします。ピン配置は、適当に検索するか、ラズパイの型番に準じます。[参考：Raspberry Pi 2/3 B ピン配置 (40ピン)](https://iot.keicode.com/raspberry-pi/pinout.php)

![IMG_1266.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157325/89a682b5-c948-23bb-d66a-f1f9e21a09bb.jpeg)

※[カラー図解 最新 Raspberry Piで学ぶ電子工作 作って動かしてしくみがわかる (ブルーバックス)](https://www.amazon.co.jp/dp/4062579774/)から引用
![IMG_1342.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157325/cb04e1c5-6181-bef6-4fdb-53e6c65db621.jpeg)


```python:template_switch.py
# -*- coding: utf-8 -*-

import RPi.GPIO as GPIO
import time
import sys
import os

if __name__ == "__main__":
    pin1 = 11
    pin2 = 13
    pin3 = 15
    pin4 = 29
    pin5 = 31
    pin6 = 37

    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(pin1, GPIO.IN, pull_up_down = GPIO.PUD_UP)
    GPIO.setup(pin2, GPIO.IN, pull_up_down = GPIO.PUD_UP)
    GPIO.setup(pin3, GPIO.IN, pull_up_down = GPIO.PUD_UP)
    GPIO.setup(pin4, GPIO.IN, pull_up_down = GPIO.PUD_UP)
    GPIO.setup(pin5, GPIO.IN, pull_up_down = GPIO.PUD_UP)
    GPIO.setup(pin6, GPIO.IN, pull_up_down = GPIO.PUD_UP)

    print("ボタン待機中...")
    print("-- 6(noshut) - 5(shut) - 4(vlan1) - 3(vlan99) - 2(backup) - 1(message) --")

    while True:
        button1 = GPIO.input(pin1)
        button2 = GPIO.input(pin2)
        button3 = GPIO.input(pin3)
        button4 = GPIO.input(pin4)
        button5 = GPIO.input(pin5)
        button6 = GPIO.input(pin6)

        cmd = ""
        if button1 == False:
            print("Bottun1 - teams.py")
            cmd = "python ./teams.py '## 只今、作業中です ##'"

        elif button2 == False:
            print("Button2 - templateConfigbackup.py")
            cmd = "python ./templateConfigbackup.py"

        elif button3 == False:
            print("Button3 - templateVlan99.py")
            cmd = "python ./templateVlan99.py"

        elif button4 == False:
            print("Button4 - templateVlan1.py")
            cmd = "python ./templateVlan1.py"

        elif button5 == False:
            print("Button5 - templateShut.py")
            cmd = "python ./templateShut.py"

        elif button6 == False:
            print("Button6 - templateNoShut.py")
            cmd = "python ./templateNoShut.py"

        if cmd != "":
            ret = os.popen(cmd).readline().strip()
            print(ret)
            time.sleep(1)

        time.sleep(0.1)

    GPIO.cleanup()
```

### Cisco DNA Center
対象装置（この例だと二台のCatalyst 9300）は、Cisco DNA Centerにあらかじめ登録されている必要があります。（手順は割愛）

#### 0. Template Editor
Cisco DNA CenterのGUIからTemplate Editor機能を使って、Cisco IOSのCLIを登録しておきます。動的に補完したい変数は、ドルマーク（$）を頭に付けておくと、変数扱いされ、スクリプトまたはGUIで実行時に、補完できます。

#### 1. インターフェースのshut/no shut
![SS 2019-12-08 0.40.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157325/6eb3a398-0d53-fa5e-5cd6-29cbf3306465.png)
#### 2. Vlan変更
![SS 2019-12-08 0.39.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157325/6ea09010-61fe-7db3-e34d-7b10ca53d594.png)
#### 3. コンフィグのファイルサーバへのコピー
![SS 2019-12-08 0.41.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157325/3603345c-5cd8-9f27-fc11-4080e56267af.png)
#### 4. コンフィグアーカイブ機能を使ったアーカイブ
![SS 2019-12-08 0.43.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157325/122ea012-51c9-5989-e69f-5fa0954d4271.png)

#### ※コンフィグアーカイブに必要なIOS-XEの設定

```cisco
archive
 log config
  logging enable
  logging size 1000
  notify syslog contenttype plaintext
  hidekeys
 path ftp://<ipaddress>/ConfigBackup/Cat9300_demo1
!
ip ftp username <ftpusername>
ip ftp password <ftppassword>
```

### Cisco DNA Center の API と Pythonスクリプト
さまざまある中から、「Deploy Template」のAPIを使います。
以下のスクリプトサンプルでは、トークン取得やパスの生成など、わかりやすさのために一つに含めています。（通常は効率性やメンテナンス性を考えて、ファイルを分離してインポートすべきかと思います。）

![SS 2019-12-08 0.53.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157325/815538d5-5a65-cf5f-a42d-acc92a4a9219.png)

#### 1. スイッチポート shut/no shut

```python:templateNoShut.py 
import requests
import json

DNAC_URL = 'https://<dnac_ip_address>/api'
DNAC_USER = '<username>'
DNAC_PASSWORD = '<password>'

def get_token(url, user, password):
    api_call = '/system/v1/auth/token'
    url += api_call
    response = requests.post(url=url, auth=(user, password), verify=False).json()
    return response["Token"]

def deploy(token):
    headers = {
        'X-Auth-Token': token,
        'content-type': 'application/json'
        }
    url = "https://<dnac_ip_address>/api/v1/template-programmer/template/deploy"
    
    payload = {
        "templateId": "<template_id>",
        "forcePushTemplate": "true",
        "targetInfo": [
            {
                #LEDが繋がってるポートは一つなので、このスクリプトはスイッチを一台だけ指定しています
                "id": "<device_ip>",
                #ここのパラメータ指定で、shutまたはno shutを切り替えます
                "params": {
                    "shutnoshut": "no shut"
                },
                "type": "MANAGED_DEVICE_IP"
            }
        ],
        }

    response = requests.post(url=url, data=json.dumps(payload), headers=headers, verify=False)
    print(response)

if __name__ == '__main__':
    token = get_token(DNAC_URL, DNAC_USER, DNAC_PASSWORD)
    deploy(token)
```

#### 2. Vlan 変更

```python:templateVlan99.py
import requests
import json

DNAC_URL = 'https://<dnac_ip_address>/api'
DNAC_USER = '<username>'
DNAC_PASSWORD = '<password>'
#複数台という設定で、この例では二台に対して実行しています
ips = ["<ip_address_1>", "<ip_address_2>"]

def get_token(url, user, password):
    api_call = '/system/v1/auth/token'
    url += api_call
    response = requests.post(url=url, auth=(user, password), verify=False).json()
    return response["Token"]

def deploy(token, ipaddr):
    headers = {
        'X-Auth-Token': token,
        'content-type': 'application/json'
        }
    url = "https://<dnac_ip_address>/api/v1/template-programmer/template/deploy"
    
    payload = {
        "templateId": "<template_id>",
        "forcePushTemplate": "true",
        "targetInfo": [
            {
                "id": ipaddr, 
                #Template内に複数の変数がある場合も、ここで指定します。
                #Vlanの値や、インターフェース名を指定しています。
                "params": {
                    "vlan": "99",
                    "interface": "range GigabitEthernet 1/0/6-10"
                },
                "type": "MANAGED_DEVICE_IP"
            }
        ],
        }

    response = requests.post(url=url, data=json.dumps(payload), headers=headers, verify=False)
    print(response)

if __name__ == '__main__':
    token = get_token(DNAC_URL, DNAC_USER, DNAC_PASSWORD)
    for ip in ips:  
        deploy(token, ip)
```

#### 3. IOSコンフィグのファイルサーバへのコピー

```python:templateConfigbackup.py
import requests
import json

DNAC_URL = 'https://<dnac_ip_address>/api'
DNAC_USER = '<username>'
DNAC_PASSWORD = '<password>'
#複数台という設定で、この例では二台に対して実行しています
ips = ["<ip_address_1>", "<ip_address_2>"]

def get_token(url, user, password):
    api_call = '/system/v1/auth/token'
    url += api_call
    response = requests.post(url=url, auth=(user, password), verify=False).json()
    return response["Token"]

def deploy(token, ipaddr):
    headers = {
        'X-Auth-Token': token,
        'content-type': 'application/json'
        }
    url = "https://10.71.130.53/api/v1/template-programmer/template/deploy"
    
    payload = {
        #テンプレートの中で、変数を扱わない例で、もっとも簡単なパターンです。
        #コンフィグバックアップの方法を、二種類用意して、デモを行っています。
        #"templateId": "<template_id_1>",
        "templateId": "<template_id_2>",
        "forcePushTemplate": "true",
        "targetInfo": [
            {
                "id": ipaddr,
                "type": "MANAGED_DEVICE_IP"
            }
        ],
        }

    response = requests.post(url=url, data=json.dumps(payload), headers=headers, verify=False)
    print(response)

if __name__ == '__main__':
    token = get_token(DNAC_URL, DNAC_USER, DNAC_PASSWORD)
    for ip in ips: 
        deploy(token, ip)
```

#### 4. WebEx Teamへのメッセージ通知

力尽きたので、固定のメッセージを通知するだけになりました。ボタンを押すと、いろいろ疎通確認をして、その結果をドキュメント化してポストするなど、誰か作ってくれるでしょう...

```python:teams.py
# -*- coding: utf-8 -*-
import requests
import sys

ACCESS_TOKEN = "<access_token>"
ROOM_ID = "<room_id>"
YOUR_MESSAGE = sys.argv[1]

#ヘッダ作成
def setHeaders():
    accessToken_hdr = 'Bearer ' + ACCESS_TOKEN
    spark_header = {'Authorization': accessToken_hdr, 'Content-Type': 'application/json; charset=utf-8'}
    return spark_header

#スペースにメッセージをポスト
def postMsg(the_header,roomId,message):
    message = '{"roomId":"' + roomId + '","text":"' + message +'"}'
    uri = 'https://api.ciscospark.com/v1/messages'
    resp = requests.post(uri, data=message, headers=the_header)
    print resp

header=setHeaders()
postMsg(header,ROOM_ID,YOUR_MESSAGE)
```



# まとめ
### 誰でもできる操作
究極のシンプリシティ。よくある作業を、ボタン一発でいかがでしょう。

### コントローラー利用の向き・不向き
タクトスイッチから、例えばAnsibleに紐付けるとか、Netconf/Restconfを使って装置に直接アクセスすれば、Cisco DNA Centerみたいなものは不要です。装置の台数が少ないとか、局所的なオペレーションのためにテンプレートの中身が、ものすごいカスタムされる場合には、コントローラーがない方がいいかもしれません。

一方、スイッチの台数が多い（数百台とか）とか、スクリプトで装置ごとの動作の違いを吸収したくないとか、スクリプト実行サーバでスケーラビリティや可用性を担保したくないとか、そもそもネットワーク担当ではない人がネットワーク操作とアプリケーションを連動させたい場合とか、そういった場合には、コントローラーがあった方が良いです。

広範囲かつ平準化された箇所（LAN全体、DC全体とか）に対してはコントローラー経由で、局所的かつカスタム操作が多い箇所（HQのWAN接続とか）に対しては、コントローラーを介さずに直接、といった具合に使い分けると良いでしょう。プレイブックの維持管理、運用を考える手間から開放されます。

### シンプルで汎用化されたスクリプト
サンプルスクリプトを通して、ネットワーク装置のハンドリングが隠れていることが伝わったでしょうか。以下のスライドのP15で言ってたことが、とっても卑近な操作（shut/no shutやvlan変更など）でも、ある程度は実現されていることが確認できました。

[【Interop Tokyo 2016】 LAN/WAN向けSDNコントローラ APIC-EMのご紹介](https://www.slideshare.net/CiscoJapan/interop-tokyo-2016-lanwansdn-apicem)
※APIC-EMは、丸ごとCisco DNA Centerに取り込まれています。

もちろん、そもそもの機能自体が、もっとインテントベースに進化しており、Cisco DNA Centerでいうと、アシュアランスやSD-Access、アプリケーションポリシーといったAPIの方が未来を感じられるでしょう。

### Cisco DNA Center の Template Editor
Cisco DNA Centerは、インテントベースなポリシーコントローラー＋データプラットフォームという位置づけのネットワーク管理プラットフォームですが、Template Editorのように、脈々と使われるコンフィグレーション（CLI）をぶん回すマクロコントローラーとしても使えます。上位のスクリプトや別のアプリケーションからは、CLIのテンプレートに紐付けてAPIをたたくことで、よくある作業も自動化できそうです。

### ソフトウェア, ハードウェア, API, ネットワーク技術者
ソフトウェアで何でもできる〜とはいいつつも、ネットワーク屋さんは、ハードウェア~~から逃げられない~~大好きな方が多いでしょう。誰しもラックに配線するところから興奮した時代があったはずです。一方で、すっかりオープン化されたネットワークインフラのAPIを使って、ネットワークと上位のシステムをつないだり、別のシステムと連動した自動化をAPIを使って繋げていくことも、大事なミッションになりました。ハードウェアからソフトウェア、ビジネスアプリケーションをつないでいけるネットワーク技術者には、明るい未来があるはずです。

ということで、たまにはブレッドボードとジャンパーワイヤで、あーだこーだ遊んでみるのも、よい息抜きでした。

### 応用
- 入力に使ったタクトスイッチ -> トグルスイッチやセンサー入力など
- ネットワークへの操作 -> 設定変更に加え、疎通確認試験、経路切り替えなど
- 通知 -> メールやSNSに加え、LED点灯パターンを変えた通知、ミニ液晶への表示など
- その他 -> カメラで操作者を撮影して記録など

可能性が色々！

# 使ったもの
- [Raspberry Pi 3B](https://www.switch-science.com/catalog/3050/)、ブレッドボード、ジャンパーワイヤ（オス/オス、オス/メス）、タクトスイッチ
- [PoEスプリッタ（TP-Link TL-POE10R）](http://www.tp-link.jp/products/details/cat-43_TL-POE10R.html) 
- [LEDワイヤーライト（2.1mmジャック、12V入力）](https://www.amazon.co.jp/gp/product/B01AALVG38/)
- [Catalyst9300 24P](https://www.cisco.com/c/ja_jp/products/collateral/switches/catalyst-9300-series-switches/datasheet-c78-738977.html) 二台
- [Cisco DNA Center](https://www.cisco.com/c/ja_jp/products/cloud-systems-management/dna-center/index.html#~resources) 1.3.1.3

# 参考URL、本
- [テンプレート機能について参考になるCiscoブログ](https://blogs.cisco.com/developer/dna-center-template-programmer-api)
- [Quickly Deploy Configuration Templates with Cisco DNA Center Platform and Template Programmer](https://developer.cisco.com/learning/tracks/dnacenter-programmability/dnac-rest-apis/dnac-template-prg/step/1)
- [インテントベース ネットワーキングと DNA Center](https://gblogs.cisco.com/jp/2018/07/intent-based-networing-and-dna-center/)
- [カラー図解 最新 Raspberry Piで学ぶ電子工作 作って動かしてしくみがわかる (ブルーバックス)](https://www.amazon.co.jp/dp/4062579774/)
- [ラズパイ：タクトスイッチでPythonプログラムを動作させる - 人生は読めないブログ](https://torisky.com/%E3%83%A9%E3%82%BA%E3%83%91%E3%82%A4%EF%BC%9A%E3%82%BF%E3%82%AF%E3%83%88%E3%82%B9%E3%82%A4%E3%83%83%E3%83%81%E3%81%A7python%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%82%92%E5%8B%95%E4%BD%9C/)
- [Alexaでネットワーク制御... ついでにクリスマスを彩る](https://qiita.com/kikuta1978/items/12f9ef8c5f7a64652b12)
- [Cisco DevNet Code Exchange](https://developer.cisco.com/codeexchange/)
- [Cisco DevNet Learning Labs](https://developer.cisco.com/learning/)


# 免責事項

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