SiamCafe.net Blog
Technology

Segment Routing Testing Strategy QA — ระบบทดสอบ SR-MPLS และ SRv6 อัตโนมัติ

segment routing testing strategy qa
Segment Routing Testing Strategy QA | SiamCafe Blog
2026-03-18· อ. บอม — SiamCafe.net· 10,288 คำ

Segment Routing คืออะไรและทำไมต้องมี Testing Strategy

Segment Routing (SR) เป็น Source Routing Architecture ที่ออกแบบมาเพื่อลดความซับซ้อนของ Network โดยกำจัด Protocol หลายตัวที่เคยใช้ใน MPLS Network แบบเดิมเช่น LDP และ RSVP-TE แทนที่ด้วยการใช้ Segment Identifier (SID) ที่แนบไปกับ packet header เพื่อบอกเส้นทางที่ packet ต้องผ่าน

Segment Routing มี 2 Data Plane หลักคือ SR-MPLS ที่ใช้ MPLS Label Stack เป็น Segment Identifier เหมาะสำหรับ network ที่มี MPLS อยู่แล้ว และ SRv6 ที่ใช้ IPv6 Extension Header เป็น Segment Identifier เหมาะสำหรับ network ที่ต้องการ programmability สูงและรองรับ Network Function Chaining

การทดสอบ Segment Routing ต้องครอบคลุมทั้ง Control Plane (IS-IS/OSPF SR Extensions, BGP-LS) และ Data Plane (MPLS forwarding, SRv6 encapsulation) รวมถึง Failure Scenarios เช่น Link Failure, Node Failure และ TI-LFA (Topology-Independent Loop-Free Alternate) convergence time

Testing Strategy ที่ดีสำหรับ Segment Routing ต้องแบ่งเป็น 4 ระดับคือ Unit Test สำหรับ configuration syntax, Functional Test สำหรับ protocol behavior, Integration Test สำหรับ end-to-end path validation และ Performance Test สำหรับ convergence time และ throughput

ออกแบบ Test Plan สำหรับ Segment Routing

Test Plan สำหรับ Segment Routing ควรครอบคลุม test cases หลักดังนี้

# test_plan_segment_routing.yaml
test_plan:
  name: "Segment Routing Validation"
  version: "2.0"
  topology: "spine-leaf-sr"
  
  test_suites:
    - name: "Control Plane Tests"
      tests:
        - id: CP-001
          name: "IS-IS SR adjacency formation"
          description: "ตรวจสอบว่า IS-IS adjacency ขึ้นครบทุก link"
          expected: "All adjacencies in UP state"
          priority: critical
          
        - id: CP-002
          name: "Prefix SID allocation"
          description: "ตรวจสอบว่า Prefix SID ถูกต้องและไม่ซ้ำกัน"
          expected: "Unique SID per prefix in SRGB range"
          priority: critical
          
        - id: CP-003
          name: "Adjacency SID assignment"
          description: "ตรวจสอบ Adj-SID บนทุก interface"
          expected: "Adj-SID assigned per adjacency"
          priority: high
          
        - id: CP-004
          name: "BGP-LS SR information"
          description: "ตรวจสอบว่า BGP-LS export SR TLVs ถูกต้อง"
          expected: "SR TLVs visible in SDN controller"
          priority: high

    - name: "Data Plane Tests"
      tests:
        - id: DP-001
          name: "SR-MPLS label forwarding"
          description: "ส่ง traffic ผ่าน SR-MPLS label stack"
          expected: "Traffic forwarded correctly via label stack"
          priority: critical
          
        - id: DP-002
          name: "SRv6 encapsulation"
          description: "ตรวจสอบ SRv6 SRH encap/decap"
          expected: "Correct SRH with segment list"
          priority: critical
          
        - id: DP-003
          name: "ECMP load balancing"
          description: "ตรวจสอบ ECMP across equal-cost SR paths"
          expected: "Traffic distributed evenly"
          priority: high

    - name: "Failure Recovery Tests"
      tests:
        - id: FR-001
          name: "TI-LFA link failure"
          description: "จำลอง link failure และวัด convergence time"
          expected: "Convergence < 50ms with TI-LFA"
          priority: critical
          
        - id: FR-002
          name: "Node failure recovery"
          description: "จำลอง node failure และตรวจสอบ traffic reroute"
          expected: "Traffic rerouted via backup path"
          priority: critical

ตั้งค่า Lab Environment ด้วย Containerlab

Containerlab เป็นเครื่องมือสร้าง Network Lab ด้วย Container ที่รองรับ Network OS หลายยี่ห้อเช่น Nokia SR Linux, Arista cEOS, Cisco XRd และ FRRouting

# ติดตั้ง Containerlab
sudo bash -c "$(curl -sL https://get.containerlab.dev)"

# ตรวจสอบเวอร์ชัน
containerlab version
# containerlab version 0.54.2

# sr-lab.clab.yml — Topology สำหรับทดสอบ Segment Routing
name: sr-testing-lab
topology:
  nodes:
    spine1:
      kind: nokia_srlinux
      image: ghcr.io/nokia/srlinux:24.3.2
      startup-config: configs/spine1.cfg
    spine2:
      kind: nokia_srlinux
      image: ghcr.io/nokia/srlinux:24.3.2
      startup-config: configs/spine2.cfg
    leaf1:
      kind: nokia_srlinux
      image: ghcr.io/nokia/srlinux:24.3.2
      startup-config: configs/leaf1.cfg
    leaf2:
      kind: nokia_srlinux
      image: ghcr.io/nokia/srlinux:24.3.2
      startup-config: configs/leaf2.cfg
    leaf3:
      kind: nokia_srlinux
      image: ghcr.io/nokia/srlinux:24.3.2
      startup-config: configs/leaf3.cfg
    client1:
      kind: linux
      image: alpine:latest
    client2:
      kind: linux
      image: alpine:latest

  links:
    - endpoints: ["spine1:e1-1", "leaf1:e1-49"]
    - endpoints: ["spine1:e1-2", "leaf2:e1-49"]
    - endpoints: ["spine1:e1-3", "leaf3:e1-49"]
    - endpoints: ["spine2:e1-1", "leaf1:e1-50"]
    - endpoints: ["spine2:e1-2", "leaf2:e1-50"]
    - endpoints: ["spine2:e1-3", "leaf3:e1-50"]
    - endpoints: ["leaf1:e1-1", "client1:eth1"]
    - endpoints: ["leaf3:e1-1", "client2:eth1"]

# สร้าง Lab
sudo containerlab deploy --topo sr-lab.clab.yml

# ดูสถานะ
sudo containerlab inspect --topo sr-lab.clab.yml
# +---+------------------+---------+------+-------+
# | # | Name             | Kind    | IPv4 | IPv6  |
# +---+------------------+---------+------+-------+
# | 1 | clab-sr-spine1   | srlinux | ...  | ...   |
# | 2 | clab-sr-spine2   | srlinux | ...  | ...   |
# | 3 | clab-sr-leaf1    | srlinux | ...  | ...   |
# +---+------------------+---------+------+-------+

# เข้าถึง node
ssh admin@clab-sr-testing-lab-spine1

ตัวอย่าง SR Configuration สำหรับ Nokia SR Linux

# configs/spine1.cfg — Nokia SR Linux SR-MPLS Configuration
set / network-instance default
set / network-instance default router-id 10.0.0.1
set / network-instance default protocols isis instance main
set / network-instance default protocols isis instance main net [ 49.0001.0100.0000.0001.00 ]
set / network-instance default protocols isis instance main level-capability L2
set / network-instance default protocols isis instance main interface ethernet-1/1.0
set / network-instance default protocols isis instance main interface ethernet-1/2.0
set / network-instance default protocols isis instance main interface ethernet-1/3.0
set / network-instance default protocols isis instance main interface system0.0 passive true

# Segment Routing Configuration
set / network-instance default segment-routing
set / network-instance default segment-routing mpls
set / network-instance default segment-routing mpls global-block label-range start 16000 end 23999
set / network-instance default protocols isis instance main segment-routing mpls
set / network-instance default protocols isis instance main segment-routing prefix-sid-map
set / network-instance default protocols isis instance main interface system0.0 segment-routing mpls ipv4-node-sid index 1

# TI-LFA Configuration
set / network-instance default protocols isis instance main interface ethernet-1/1.0 ti-lfa enable true
set / network-instance default protocols isis instance main interface ethernet-1/2.0 ti-lfa enable true
set / network-instance default protocols isis instance main interface ethernet-1/3.0 ti-lfa enable true

เขียน Automated Test ด้วย PyATS และ Robot Framework

PyATS เป็น Network Testing Framework จาก Cisco ที่รองรับ multi-vendor ใช้สำหรับเขียน automated test สำหรับ network devices

# ติดตั้ง PyATS
pip install pyats[full] genie

# testbed.yaml — PyATS Testbed Definition
testbed:
  name: sr-testing-lab
  credentials:
    default:
      username: admin
      password: NokiaSrl1!

devices:
  spine1:
    os: linux
    type: srlinux
    connections:
      defaults:
        class: unicon.Unicon
      cli:
        protocol: ssh
        ip: clab-sr-testing-lab-spine1
        port: 22
  leaf1:
    os: linux
    type: srlinux
    connections:
      defaults:
        class: unicon.Unicon
      cli:
        protocol: ssh
        ip: clab-sr-testing-lab-leaf1
        port: 22
  leaf3:
    os: linux
    type: srlinux
    connections:
      defaults:
        class: unicon.Unicon
      cli:
        protocol: ssh
        ip: clab-sr-testing-lab-leaf3
        port: 22

---
# test_sr_control_plane.py — PyATS Test Script
from pyats import aetest
from pyats.topology import loader
import re
import json

class CommonSetup(aetest.CommonSetup):
    @aetest.subsection
    def connect_devices(self, testbed):
        for device in testbed.devices.values():
            device.connect(log_stdout=False)

class TestISISAdjacency(aetest.Testcase):
    @aetest.test
    def verify_isis_adjacencies(self, testbed):
        for name, device in testbed.devices.items():
            output = device.execute("info from state /network-instance default protocols isis adjacency")
            adj_count = output.count("state up")
            self.passed(f"{name}: {adj_count} adjacencies UP") if adj_count > 0 \
                else self.failed(f"{name}: No ISIS adjacencies UP")

class TestPrefixSID(aetest.Testcase):
    @aetest.test
    def verify_prefix_sids(self, testbed):
        sids_seen = set()
        for name, device in testbed.devices.items():
            output = device.execute("info from state /network-instance default segment-routing mpls")
            sid_matches = re.findall(r'ipv4-node-sid\s+index\s+(\d+)', output)
            for sid in sid_matches:
                if sid in sids_seen:
                    self.failed(f"Duplicate SID {sid} on {name}")
                sids_seen.add(sid)
        self.passed(f"All SIDs unique: {sids_seen}")

class TestTILFA(aetest.Testcase):
    @aetest.test
    def verify_tilfa_backup_paths(self, testbed):
        spine1 = testbed.devices['spine1']
        output = spine1.execute("info from state /network-instance default protocols isis interface * ti-lfa")
        if "backup-available" not in output and "protect" not in output:
            self.failed("TI-LFA backup paths not available")
        self.passed("TI-LFA backup paths configured")

class CommonCleanup(aetest.CommonCleanup):
    @aetest.subsection
    def disconnect_devices(self, testbed):
        for device in testbed.devices.values():
            device.disconnect()

# รัน test
# pyats run job test_sr_control_plane.py --testbed-file testbed.yaml

ทดสอบ SR-MPLS และ SRv6 Data Plane

ใช้ scapy และ iperf3 สำหรับทดสอบ data plane forwarding ของ Segment Routing

#!/usr/bin/env python3
# test_sr_dataplane.py — ทดสอบ SR-MPLS Data Plane ด้วย Scapy
from scapy.all import *
from scapy.contrib.mpls import MPLS
import subprocess
import time
import sys

def test_sr_mpls_forwarding(src_ip, dst_ip, label_stack):
    """ทดสอบ SR-MPLS forwarding ด้วย label stack"""
    # สร้าง packet พร้อม MPLS label stack
    pkt = Ether(dst="ff:ff:ff:ff:ff:ff")
    for i, label in enumerate(label_stack):
        s_bit = 1 if i == len(label_stack) - 1 else 0
        pkt = pkt / MPLS(label=label, s=s_bit, ttl=64)
    pkt = pkt / IP(src=src_ip, dst=dst_ip) / ICMP()

    print(f"Sending SR-MPLS packet with label stack: {label_stack}")
    print(f"  Source: {src_ip} -> Destination: {dst_ip}")

    # ส่ง packet และรอ response
    ans, unans = srp(pkt, iface="eth1", timeout=5, verbose=0)

    if ans:
        print(f"  Result: PASS - Received {len(ans)} responses")
        return True
    else:
        print(f"  Result: FAIL - No response received")
        return False

def test_srv6_encapsulation(src_ip, dst_ip, segment_list):
    """ทดสอบ SRv6 encapsulation"""
    pkt = (
        Ether(dst="ff:ff:ff:ff:ff:ff") /
        IPv6(src=src_ip, dst=segment_list[0]) /
        IPv6ExtHdrSegmentRouting(
            segleft=len(segment_list) - 1,
            addresses=segment_list[::-1]
        ) /
        IP(src="10.1.1.1", dst="10.3.1.1") /
        ICMP()
    )
    print(f"Sending SRv6 packet with segment list: {segment_list}")
    ans, unans = srp(pkt, iface="eth1", timeout=5, verbose=0)
    return len(ans) > 0

def test_ecmp_distribution(dst_ip, num_flows=100):
    """ทดสอบ ECMP distribution ด้วย different source ports"""
    results = {}
    for i in range(num_flows):
        src_port = 10000 + i
        pkt = IP(dst=dst_ip) / UDP(sport=src_port, dport=5001)
        # ใช้ traceroute เพื่อดู path
        ans, _ = traceroute(dst_ip, maxttl=5, verbose=0)
        # เก็บ next-hop
        if ans:
            next_hop = ans[0][1].src
            results[next_hop] = results.get(next_hop, 0) + 1

    print(f"ECMP Distribution ({num_flows} flows):")
    for hop, count in sorted(results.items()):
        pct = count / num_flows * 100
        print(f"  {hop}: {count} flows ({pct:.1f}%)")

def test_convergence_time(interface_to_fail):
    """วัด convergence time เมื่อ link failure"""
    print(f"Testing convergence time for {interface_to_fail} failure")

    # เริ่ม continuous ping
    ping_proc = subprocess.Popen(
        ["ping", "-i", "0.01", "-c", "1000", "10.3.1.1"],
        stdout=subprocess.PIPE, stderr=subprocess.PIPE
    )

    # รอ 2 วินาทีแล้ว shut interface
    time.sleep(2)
    subprocess.run(["ip", "link", "set", interface_to_fail, "down"])

    # รอ ping เสร็จ
    stdout, _ = ping_proc.communicate()
    output = stdout.decode()

    # วิเคราะห์ผล
    lost_match = re.search(r'(\d+)% packet loss', output)
    if lost_match:
        loss_pct = float(lost_match.group(1))
        # convergence time = จำนวน lost packets * interval (10ms)
        lost_packets = int(re.search(r'(\d+) received', output).group(1))
        convergence_ms = (1000 - lost_packets) * 10
        print(f"  Packet loss: {loss_pct}%")
        print(f"  Estimated convergence: {convergence_ms}ms")
        return convergence_ms < 50  # TI-LFA target

    # คืนค่า interface
    subprocess.run(["ip", "link", "set", interface_to_fail, "up"])

if __name__ == "__main__":
    # SR-MPLS Test: label stack [16001, 16003] = spine1 -> leaf3
    test_sr_mpls_forwarding("10.1.1.1", "10.3.1.1", [16001, 16003])

    # ECMP Test
    test_ecmp_distribution("10.3.1.1", num_flows=50)

Integration Testing กับ SDN Controller

ทดสอบการทำงานร่วมกันระหว่าง Segment Routing กับ SDN Controller ผ่าน BGP-LS และ PCEP

#!/usr/bin/env python3
# test_sdn_integration.py — ทดสอบ SDN Controller Integration
import requests
import json
import time

SDN_URL = "http://sdn-controller:8181"
AUTH = ("admin", "admin")

def test_bgp_ls_topology():
    """ตรวจสอบว่า SDN Controller ได้รับ topology จาก BGP-LS"""
    r = requests.get(
        f"{SDN_URL}/restconf/operational/network-topology:network-topology",
        auth=AUTH,
        headers={"Accept": "application/json"}
    )
    topology = r.json()
    nodes = topology.get("network-topology", {}).get("topology", [{}])[0].get("node", [])
    links = topology.get("network-topology", {}).get("topology", [{}])[0].get("link", [])

    print(f"Topology received via BGP-LS:")
    print(f"  Nodes: {len(nodes)}")
    print(f"  Links: {len(links)}")

    # ตรวจสอบว่ามี SR SID information
    for node in nodes:
        sr_info = node.get("sr-node-attributes", {})
        srgb = sr_info.get("srgb", {})
        if srgb:
            print(f"  {node['node-id']}: SRGB={srgb}")

    assert len(nodes) >= 5, f"Expected >= 5 nodes, got {len(nodes)}"
    assert len(links) >= 12, f"Expected >= 12 links, got {len(links)}"
    return True

def test_sr_policy_provisioning():
    """ทดสอบการ provision SR Policy ผ่าน SDN Controller"""
    policy = {
        "sr-policy": {
            "name": "test-policy-1",
            "headend": "10.0.0.1",
            "color": 100,
            "endpoint": "10.0.0.5",
            "candidate-paths": {
                "preference": 100,
                "segment-lists": [
                    {
                        "weight": 1,
                        "segments": [
                            {"sid": 16002, "type": "node-sid"},
                            {"sid": 16005, "type": "node-sid"}
                        ]
                    }
                ]
            }
        }
    }

    r = requests.post(
        f"{SDN_URL}/restconf/config/sr-policy:sr-policies",
        auth=AUTH,
        headers={"Content-Type": "application/json"},
        json=policy
    )
    print(f"SR Policy provisioning: {r.status_code}")
    assert r.status_code in [200, 201, 204], f"Failed: {r.text}"

    # ตรวจสอบว่า policy ถูกติดตั้งบน headend
    time.sleep(5)
    r = requests.get(
        f"{SDN_URL}/restconf/operational/sr-policy:sr-policies",
        auth=AUTH,
        headers={"Accept": "application/json"}
    )
    policies = r.json().get("sr-policies", {}).get("sr-policy", [])
    active = [p for p in policies if p.get("state") == "active"]
    print(f"Active SR Policies: {len(active)}")
    return len(active) > 0

def test_sr_te_path_computation():
    """ทดสอบ Path Computation สำหรับ SR-TE"""
    request_body = {
        "path-computation-request": {
            "source": "10.0.0.1",
            "destination": "10.0.0.5",
            "constraints": {
                "bandwidth": "1000000000",
                "latency-max": "10",
                "exclude-nodes": ["10.0.0.3"],
                "metric-type": "te-metric"
            }
        }
    }

    r = requests.post(
        f"{SDN_URL}/restconf/operations/path-computation:compute-path",
        auth=AUTH,
        headers={"Content-Type": "application/json"},
        json=request_body
    )
    result = r.json()
    path = result.get("path-computation-response", {}).get("computed-path", {})
    sid_list = path.get("segment-list", [])

    print(f"Computed SR-TE path:")
    print(f"  SID list: {[s['sid'] for s in sid_list]}")
    print(f"  Metric: {path.get('metric', 'N/A')}")
    print(f"  Latency: {path.get('latency', 'N/A')}ms")

    return len(sid_list) > 0

if __name__ == "__main__":
    results = {}
    results["BGP-LS Topology"] = test_bgp_ls_topology()
    results["SR Policy Provisioning"] = test_sr_policy_provisioning()
    results["SR-TE Path Computation"] = test_sr_te_path_computation()

    print("\n=== Test Summary ===")
    for test, passed in results.items():
        status = "PASS" if passed else "FAIL"
        print(f"  [{status}] {test}")

FAQ คำถามที่พบบ่อย

Q: SR-MPLS กับ SRv6 ควรเลือกใช้ตัวไหน?

A: SR-MPLS เหมาะสำหรับ network ที่มี MPLS infrastructure อยู่แล้วและต้องการ migration ที่ง่าย ส่วน SRv6 เหมาะกับ network ใหม่ที่ต้องการ programmability สูงและรองรับ Network Function Chaining แต่ SRv6 มี overhead สูงกว่าเนื่องจากใช้ IPv6 header ที่ใหญ่กว่า MPLS label

Q: TI-LFA convergence time ควรเป็นเท่าไหร่?

A: Target ที่แนะนำคือต่ำกว่า 50ms สำหรับ link failure ซึ่ง TI-LFA สามารถทำได้เพราะ backup path ถูก pre-computed ไว้แล้วใน FIB เมื่อ failure เกิดขึ้น router เพียงแค่สลับไปใช้ backup path โดยไม่ต้องรอ IGP convergence

Q: Containerlab กับ GNS3/EVE-NG ต่างกันอย่างไร?

A: Containerlab ใช้ container-based approach ทำให้เบาและเร็วกว่ามาก สามารถ define topology เป็น YAML file ที่ version control ได้ เหมาะสำหรับ CI/CD pipeline ส่วน GNS3/EVE-NG เหมาะกับการสร้าง lab ที่ต้องการ GUI และรองรับ hardware-based images

Q: จะทดสอบ Segment Routing บน Production Network ได้อย่างไร?

A: ใช้ OAM (Operations Administration and Maintenance) tools ที่ built-in มากับ SR เช่น SR-MPLS OAM ที่รองรับ ping และ traceroute ผ่าน label stack, SRv6 OAM ที่ใช้ ICMPv6 และ Performance Measurement ที่วัด delay/loss ได้โดยไม่กระทบ traffic จริง ไม่ควรรัน active scan บน production โดยตรง

Q: PyATS รองรับ vendor ไหนบ้าง?

A: PyATS รองรับ Cisco IOS/IOS-XE/IOS-XR/NX-OS เป็นหลัก แต่ผ่าน Unicon library สามารถเชื่อมต่อกับ device อื่นๆได้เช่น Nokia SR Linux, Arista EOS, Juniper Junos และ Linux-based devices สำหรับ vendor ที่ไม่รองรับ native สามารถใช้ NETCONF/RESTCONF หรือ SSH command parsing แทน

📖 บทความที่เกี่ยวข้อง

Segment Routing Data Pipeline ETLอ่านบทความ → Segment Routing 12 Factor Appอ่านบทความ → Segment Routing อ่านบทความ → Segment Routing CDN Configurationอ่านบทความ → Segment Routing Consensus Algorithmอ่านบทความ →

📚 ดูบทความทั้งหมด →