pyATS/Genie - 네트워크 테스트 자동화
개요
pyATS(Python Automated Test Systems)는 Cisco가 개발한 네트워크 테스트 자동화 프레임워크이며, Genie는 그 위에서 동작하는 라이브러리입니다.
주요 특징
1. 네트워크 상태 스냅샷
- 변경 전후 상태 비교
- 자동 Diff 생성
- 회귀 테스트
2. 파서 라이브러리
- 2000+ show 명령어 파서
- 구조화된 데이터 반환
- 멀티 벤더 지원
3. 테스트 프레임워크
- 체계적인 테스트 케이스
- 상세한 로깅
- HTML 리포트 생성
설치
pip install pyats[full]
pip install genie
Testbed 파일 (장비 정의)
# testbed.yaml
testbed:
name: Lab_Network
devices:
router1:
os: iosxe
type: router
connections:
cli:
protocol: ssh
ip: 192.168.1.1
port: 22
credentials:
default:
username: admin
password: cisco
router2:
os: iosxe
type: router
connections:
cli:
protocol: ssh
ip: 192.168.1.2
credentials:
default:
username: admin
password: cisco
switch1:
os: nxos
type: switch
connections:
cli:
protocol: ssh
ip: 192.168.1.10
credentials:
default:
username: admin
password: cisco
기본 사용법
1. 장비 연결
from genie.testbed import load
# Testbed 로드
testbed = load('testbed.yaml')
# 장비 연결
device = testbed.devices['router1']
device.connect()
# 명령 실행
output = device.execute('show version')
print(output)
device.disconnect()
2. 파서 사용
from genie.testbed import load
testbed = load('testbed.yaml')
device = testbed.devices['router1']
device.connect()
# 파싱된 데이터
parsed = device.parse('show ip interface brief')
# 구조화된 데이터
for interface, data in parsed['interface'].items():
print(f"{interface}: {data['ip_address']} - {data['status']}")
Learn 기능 (상태 스냅샷)
변경 전 상태 저장
from genie.testbed import load
testbed = load('testbed.yaml')
device = testbed.devices['router1']
device.connect()
# 모든 정보 학습
device.learn('all', output_dir='./before')
# 특정 feature만 학습
device.learn('interface', output_dir='./before')
device.learn('routing', output_dir='./before')
device.learn('bgp', output_dir='./before')
변경 작업 수행
# 설정 변경
config = """
interface GigabitEthernet0/1
description Changed by pyATS
no shutdown
"""
device.configure(config)
변경 후 상태 비교
# 변경 후 상태 학습
device.learn('interface', output_dir='./after')
# Diff 비교
from genie.utils.diff import Diff
diff = Diff('./before/interface_iosxe_router1_ops.txt',
'./after/interface_iosxe_router1_ops.txt')
diff.findDiff()
print(diff)
테스트 자동화
테스트 스크립트
# test_network.py
from pyats import aetest
from genie.testbed import load
class CommonSetup(aetest.CommonSetup):
@aetest.subsection
def connect_to_devices(self, testbed):
"""모든 장비 연결"""
for device in testbed.devices.values():
device.connect()
class VerifyInterfaces(aetest.Testcase):
@aetest.setup
def setup(self, testbed):
"""테스트 준비"""
self.device = testbed.devices['router1']
@aetest.test
def check_interface_status(self):
"""인터페이스 상태 확인"""
parsed = self.device.parse('show ip interface brief')
for intf, data in parsed['interface'].items():
if data['status'] == 'administratively down':
self.passed(f"{intf} is admin down (expected)")
elif data['status'] == 'up':
self.passed(f"{intf} is up")
else:
self.failed(f"{intf} is {data['status']}")
@aetest.test
def check_bgp_neighbors(self):
"""BGP 이웃 상태 확인"""
parsed = self.device.parse('show bgp summary')
for neighbor, data in parsed['bgp']['instance']['default']['vrf']['default']['neighbor'].items():
if data['address_family']['ipv4 unicast']['state'] == 'Established':
self.passed(f"BGP neighbor {neighbor} is Established")
else:
self.failed(f"BGP neighbor {neighbor} is not Established")
class CommonCleanup(aetest.CommonCleanup):
@aetest.subsection
def disconnect_from_devices(self, testbed):
"""모든 장비 연결 해제"""
for device in testbed.devices.values():
device.disconnect()
if __name__ == '__main__':
import sys
from pyats import topology
# Testbed 로드
testbed = topology.loader.load('testbed.yaml')
# 테스트 실행
aetest.main(testbed=testbed)
테스트 실행
python test_network.py
# 결과 확인
cat TaskLog.html # HTML 리포트
네트워크 변경 검증 워크플로우
from genie.testbed import load
from genie.utils.diff import Diff
import logging
def network_change_workflow(testbed_file, config_commands):
"""네트워크 변경 검증 워크플로우"""
# 1. Testbed 로드
testbed = load(testbed_file)
device = testbed.devices['router1']
device.connect()
# 2. 변경 전 상태 저장
print("📸 변경 전 상태 수집 중...")
device.learn('all', output_dir='./snapshots/before')
# 3. 설정 변경
print("⚙️ 설정 변경 중...")
device.configure(config_commands)
# 4. 변경 후 상태 저장
print("📸 변경 후 상태 수집 중...")
device.learn('all', output_dir='./snapshots/after')
# 5. Diff 비교
print("🔍 변경사항 비교 중...")
diff = Diff('./snapshots/before', './snapshots/after')
diff.findDiff()
# 6. 결과 출력
if diff:
print("
⚠️ 변경사항 발견:")
print(diff)
else:
print("
✅ 변경사항 없음")
# 7. 연결 종료
device.disconnect()
# 사용 예
config = """
interface GigabitEthernet0/1
description Updated by pyATS
"""
network_change_workflow('testbed.yaml', config)
CLI Tool (Genie CLI)
# 장비 정보 학습
genie learn interface --testbed-file testbed.yaml --output before
# 설정 변경 후
genie learn interface --testbed-file testbed.yaml --output after
# Diff 비교
genie diff before after
# 특정 명령어 파싱
genie parse "show ip interface brief" --testbed-file testbed.yaml --devices router1
# Robot Framework 통합
robot --testbed testbed.yaml test_network.robot
지원 플랫폼
Cisco
- IOS, IOS-XE
- NX-OS
- IOS-XR
- ASA
기타
- Juniper Junos
- Arista EOS
- Linux
실전 예제
펌웨어 업그레이드 검증
def verify_upgrade(device, expected_version):
"""펌웨어 업그레이드 후 검증"""
# 버전 확인
parsed = device.parse('show version')
actual_version = parsed['version']['version']
assert actual_version == expected_version, f"Version mismatch: {actual_version} != {expected_version}"
# 인터페이스 상태 확인
interfaces_before = device.learn('interface')
# 업그레이드 수행
# ...
interfaces_after = device.learn('interface')
# Diff
diff = Diff(interfaces_before, interfaces_after)
assert not diff.findDiff(), "Interface state changed unexpectedly"
장점
✓ Cisco 공식 테스트 프레임워크 ✓ 2000+ 파서 내장 ✓ 강력한 Diff 기능 ✓ 자동화된 회귀 테스트 ✓ 상세한 로깅 및 리포팅
단점
✗ Cisco 장비에 최적화 ✗ 학습 곡선 높음 ✗ 복잡한 API ✗ 대용량 스냅샷 관리 어려움
링크
- 공식 사이트: https://developer.cisco.com/pyats/
- GitHub: https://github.com/CiscoTestAutomation/pyats
- 문서: https://pubhub.devnetcloud.com/media/pyats/docs/