├── README.md ├── drone.yml ├── hq-topology.yaml ├── hq-vlan-service.yaml ├── pyproject.toml └── tests ├── Dockerfile ├── configure_vlan_service.py ├── gen_cml_topology.py ├── populate_nso.py ├── requirements.txt ├── setup_ncs.sh ├── templates └── topology.j2 ├── test_vlan_service.py └── vlan-service ├── README ├── load-dir ├── switch-topology.fxs └── vlan-service.fxs ├── package-meta-data.xml ├── python └── vlan_service │ ├── __init__.py │ └── main.py ├── src ├── Makefile └── yang │ ├── switch-topology.yang │ └── vlan-service.yang ├── templates ├── trunk-port-template.xml └── vlan-service-template.xml └── test ├── Makefile └── internal ├── Makefile └── lux ├── Makefile └── service ├── Makefile ├── dummy-device.xml ├── dummy-service.xml ├── pyvm.xml └── run.lux /README.md: -------------------------------------------------------------------------------- 1 | # CML CI/CD Pipeline 2 | 3 | ## Introduction 4 | 5 | NetDevOps seems to be all the rage these days. NetDevOps is the application of DevOps principles and tools to networking -- specifically configuration and operations. Part of this approach involves a practice known as Infrastructure as Code (IaC) whereby network configuration is done in a similar manner to writing code. That is, you commit your configuration changes (or abstracted instances thereof) to a version control system, which then triggers some automation to [hopefully] test, and then deploy the configuration into production. This whole system forms a Continuous Integration / Continuous Development (CI/CD) pipeline. 6 | 7 | This repo contains an example of this testing portion of the IaC pipeline. It is built using [Drone](https://drone.io) with Drone's [Docker](https://docs.drone.io/runner/docker/overview/) runner. The network configuration deployment is done using [Network Services Orchestrator](https://developer.cisco.com/site/nso/) (NSO), and the network itself is hosted as a virtual lab within [Cisco Modeling Labs](https://developer.cisco.com/modeling-labs) (CML). The testing part is done using [pyATS](https://developer.cisco.com/pyats/). 8 | 9 | ## How It Works 10 | 11 | In this repo, there are three YAML files: `hq-topology.yaml`, `hq-vlan-service.yaml`, and `drone.yml`. The `hq-topology.yaml` file defines a simple three-tiered switch topology, and the `hq-vlan-service.yaml` defines a set of VLANs to be deployed into this topology. The idea is that when you change either of these files, that will kick off the Drone runner to execute a test pipeline that is defined in the `drone.yaml` file. The Drone workflow will do the following: 12 | 13 | 1. Start NSO in the runner's Docker container 14 | 2. Fetch the new version of the [virlutils](https://github.com/CiscoDevNet/virlutils) utility, which allows CLI access to Cisco Modeling Labs via its powerful REST API 15 | 3. Spins up a CML topology based on `hq-topology.yaml` 16 | 4. Populates NSO with the virtual test devices 17 | 5. Configures the new VLAN service into NSO, which pushes the device configuration to the virtual devices 18 | 6. Tests that the VLAN service was properly deployed using pyATS 19 | 7. Cleans up CML when it's all done 20 | 21 | All of these steps are controlled by the various scripts and files found in the `tests` subdirectory. 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /drone.yml: -------------------------------------------------------------------------------- 1 | name: cml-cicd 2 | kind: pipeline 3 | type: docker 4 | 5 | steps: 6 | - name: cml-test 7 | image: nso:cicd 8 | volumes: 9 | - name: cml-dir 10 | path: /cml 11 | environment: 12 | ACCESS_IP_ADDRESS: 13 | from_secret: access_ip_address 14 | DIST_IP_ADDRESS: 15 | from_secret: dist_ip_address 16 | CORE_IP_ADDRESS: 17 | from_secret: core_ip_address 18 | SUBNET_MASK: 19 | from_secret: subnet_mask 20 | HQ_USERNAME: 21 | from_secret: hq_username 22 | HQ_PASSWORD: 23 | from_secret: hq_password 24 | NSO_USERNAME: 25 | from_secret: nso_username 26 | NSO_PASSWORD: 27 | from_secret: nso_password 28 | VIRL_HOST: 29 | from_secret: virl_host 30 | CML2_PLUS: yes 31 | CML_VERIFY_CERT: "False" 32 | VIRL_USERNAME: 33 | from_secret: virl_username 34 | VIRL_PASSWORD: 35 | from_secret: virl_password 36 | commands: 37 | - if (git diff --exit-code --quiet HEAD^ HEAD -- tests "hq*.yaml" .drone.yml); then exit 78; fi 38 | - . /opt/ncs/ncsrc 39 | - cd tests && pip install -Ur requirements.txt 40 | - cd /nso/packages && ln -sf /drone/src/tests/vlan-service . 41 | - cd .. && ncs --with-package-reload >/dev/null 2>&1 42 | - cd /tmp && git clone https://github.com/CiscoDevNet/virlutils.git 43 | - cd virlutils && git checkout cml2-dev && python setup.py install 44 | - cd /drone/src/tests 45 | - python gen_cml_topology.py 46 | - cd /cml 47 | - cml up -f /drone/src/tests/topology.yaml --provision 48 | - cd /drone/src && python tests/populate_nso.py 49 | - python tests/configure_vlan_service.py 50 | - python tests/test_vlan_service.py 51 | 52 | - name: cleanup 53 | image: nso:cicd 54 | when: 55 | status: 56 | - success 57 | - failure 58 | volumes: 59 | - name: cml-dir 60 | path: /cml 61 | failure: ignore 62 | environment: 63 | VIRL_HOST: 64 | from_secret: virl_host 65 | CML2_PLUS: yes 66 | CML_VERIFY_CERT: "False" 67 | VIRL_USERNAME: 68 | from_secret: virl_username 69 | VIRL_PASSWORD: 70 | from_secret: virl_password 71 | commands: 72 | - cd /tmp && git clone https://github.com/CiscoDevNet/virlutils.git 73 | - cd virlutils && git checkout cml2-dev && python setup.py install 74 | - cd /cml 75 | - cml rm --force --no-confirm 76 | 77 | volumes: 78 | - name: cml-dir 79 | temp: {} 80 | -------------------------------------------------------------------------------- /hq-topology.yaml: -------------------------------------------------------------------------------- 1 | topology: headquarters 2 | 3 | devices: 4 | - name: sw-core 5 | address: 192.168.10.207 6 | ned_id: cisco-ios-cli-3.8:cisco-ios-cli-3.8 7 | os: ios 8 | downlink_trunks: 9 | - "0/1" 10 | - name: sw-dist 11 | address: 192.168.10.208 12 | ned_id: cisco-ios-cli-3.8:cisco-ios-cli-3.8 13 | os: ios 14 | uplink_trunk: "0/1" 15 | downlink_trunks: 16 | - "0/2" 17 | - name: sw-access 18 | address: 192.168.10.191 19 | ned_id: cisco-ios-cli-3.8:cisco-ios-cli-3.8 20 | os: ios 21 | uplink_trunk: "0/1" 22 | -------------------------------------------------------------------------------- /hq-vlan-service.yaml: -------------------------------------------------------------------------------- 1 | name: headquarters 2 | topology: headquarters 3 | vlan: 4 | - vlanid: 11 5 | name: USERS 6 | - vlanid: 12 7 | name: PRINTERS 8 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 140 3 | -------------------------------------------------------------------------------- /tests/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:buster 2 | RUN apt-get update \ 3 | && apt-get install -qy \ 4 | default-jre-headless \ 5 | iputils-ping \ 6 | libexpat1 \ 7 | openssh-client \ 8 | procps \ 9 | python3 \ 10 | tcpdump \ 11 | telnet \ 12 | git \ 13 | vim \ 14 | python3-pip \ 15 | xmlstarlet \ 16 | && apt-get -qy autoremove \ 17 | && apt-get clean \ 18 | && rm -rf /var/lib/apt/lists/* /root/.cache \ 19 | && update-alternatives --install /usr/bin/python python /usr/bin/python3 \ 20 | && update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 21 | 22 | # This needs to be set based on what you download and where you put it. 23 | # Then uncomment these next four lines. 24 | #COPY $NSO_INSTALL_FILE /tmp/nso_installer.bin 25 | #COPY setup_ncs.sh /tmp/setup_ncs.sh 26 | #RUN sh /tmp/nso_installer.bin /opt/ncs 27 | #RUN sh /tmp/setup_ncs.sh 28 | 29 | CMD ["/bin/bash"] 30 | -------------------------------------------------------------------------------- /tests/configure_vlan_service.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import requests 4 | import os 5 | from yaml import load, dump 6 | 7 | try: 8 | from yaml import CLoader as Loader, CDumper as Dumper 9 | except ImportError: 10 | from yaml import Loader, Dumper 11 | 12 | 13 | def main(): 14 | headers = { 15 | "Accept": "application/yang-data+json", 16 | "Content-Type": "application/yang-data+json", 17 | } 18 | service = None 19 | 20 | with open("hq-vlan-service.yaml") as fd: 21 | service = load(fd, Loader=Loader) 22 | 23 | # Wrap the service in the YAML file with namespaces. 24 | vlan_service = {"vlan-service:vlan-service": [service]} 25 | 26 | try: 27 | # Use RESTCONF to deploy the VLAN service to NSO 28 | r = requests.request( 29 | "POST", 30 | "http://127.0.0.1:8080/restconf/data", 31 | headers=headers, 32 | json=vlan_service, 33 | auth=(os.environ["NSO_USERNAME"], os.environ["NSO_PASSWORD"]), 34 | ) 35 | r.raise_for_status() 36 | except Exception as e: 37 | print(f"ERROR: Failed to create VLAN service: '{getattr(e, 'message', repr(e))}'") 38 | print(r.text) 39 | exit(1) 40 | 41 | print(f"VLAN Service {service['name']} created successfully.") 42 | 43 | 44 | if __name__ == "__main__": 45 | main() 46 | -------------------------------------------------------------------------------- /tests/gen_cml_topology.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from jinja2 import Environment, PackageLoader 4 | import os 5 | import time 6 | 7 | 8 | def main(): 9 | j2_env = Environment(loader=PackageLoader(__name__), trim_blocks=False) 10 | template = j2_env.get_template("topology.j2") 11 | 12 | with open("topology.yaml", "w") as fd: 13 | fd.write( 14 | template.render( 15 | CORE_IP_ADDRESS=os.environ["CORE_IP_ADDRESS"], 16 | DIST_IP_ADDRESS=os.environ["DIST_IP_ADDRESS"], 17 | ACCESS_IP_ADDRESS=os.environ["ACCESS_IP_ADDRESS"], 18 | SUBNET_MASK=os.environ["SUBNET_MASK"], 19 | TOPOLOGY_NAME=f"HQ Network (CI/CD Start: {time.ctime()})", 20 | ) 21 | ) 22 | 23 | 24 | if __name__ == "__main__": 25 | main() 26 | -------------------------------------------------------------------------------- /tests/populate_nso.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import ncs 3 | import os 4 | from yaml import load, dump 5 | 6 | try: 7 | from yaml import CLoader as Loader, CDumper as Dumper 8 | except ImportError: 9 | from yaml import Loader, Dumper 10 | 11 | 12 | def main(): 13 | topology = None 14 | devices = [] 15 | 16 | with open("hq-topology.yaml") as fd: 17 | topology = load(fd, Loader=Loader) 18 | 19 | topo_name = topology["topology"] 20 | 21 | # Create the authgroup 22 | with ncs.maapi.single_write_trans("admin", topo_name) as t: 23 | root = ncs.maagic.get_root(t) 24 | root.devices.authgroups.group.create(topo_name) 25 | root.devices.authgroups.group[topo_name].default_map.create() 26 | root.devices.authgroups.group[topo_name].default_map.remote_name = os.environ["HQ_USERNAME"] 27 | root.devices.authgroups.group[topo_name].default_map.remote_password = os.environ["HQ_PASSWORD"] 28 | t.apply() 29 | 30 | # Add each device in the topology to NSO 31 | for device in topology["devices"]: 32 | with ncs.maapi.single_write_trans("admin", topo_name) as t: 33 | root = ncs.maagic.get_root(t) 34 | root.devices.device.create(device["name"]) 35 | root.devices.device[device["name"]].address = device["address"] 36 | root.devices.device[device["name"]].authgroup = topo_name 37 | root.devices.device[device["name"]].device_type.cli.ned_id = device["ned_id"] 38 | root.devices.device[device["name"]].device_type.cli.protocol = "ssh" 39 | root.devices.device[device["name"]].ssh.host_key_verification = "none" 40 | root.devices.device[device["name"]].state.admin_state = "unlocked" 41 | t.apply() 42 | 43 | # Sync the config from each device 44 | with ncs.maapi.single_write_trans("admin", topo_name) as t: 45 | root = ncs.maagic.get_root(t) 46 | root.devices.device[device["name"]].sync_from() 47 | t.apply() 48 | 49 | # Build the switch-topology pseudo-service. 50 | with ncs.maapi.single_write_trans("admin", topo_name) as t: 51 | root = ncs.maagic.get_root(t) 52 | root.switch_topology.create(topo_name) 53 | root.switch_topology[topo_name].switch.create(device["name"]) 54 | if device.get("uplink_trunk"): 55 | root.switch_topology[topo_name].switch[device["name"]].uplink_trunk = device["uplink_trunk"] 56 | 57 | if device.get("downlink_trunks"): 58 | for trunk in device["downlink_trunks"]: 59 | root.switch_topology[topo_name].switch[device["name"]].downlink_trunk.create(trunk) 60 | 61 | t.apply() 62 | 63 | 64 | if __name__ == "__main__": 65 | main() 66 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.6.2 2 | async-timeout==3.0.1 3 | attrs==19.3.0 4 | certifi==2020.6.20 5 | chardet==3.0.4 6 | dill==0.3.2 7 | distro==1.5.0 8 | genie==20.6.1 9 | genie.libs.clean==20.6.1 10 | genie.libs.conf==20.6 11 | genie.libs.filetransferutils==20.6 12 | genie.libs.ops==20.6 13 | genie.libs.parser==20.6 14 | genie.libs.sdk==20.6 15 | idna==2.10 16 | importlib-metadata==1.7.0 17 | Jinja2==2.11.2 18 | jsonpickle==1.4.1 19 | junit-xml==1.9 20 | MarkupSafe==1.1.1 21 | multidict==4.7.6 22 | netaddr==0.8.0 23 | pathspec==0.8.0 24 | ply==3.11 25 | prettytable==0.7.2 26 | psutil==5.7.2 27 | pyasn1==0.4.8 28 | pyats==20.6 29 | pyats.aereport==20.6 30 | pyats.aetest==20.6 31 | pyats.async==20.6 32 | pyats.connections==20.6 33 | pyats.datastructures==20.6 34 | pyats.easypy==20.6.1 35 | pyats.kleenex==20.6 36 | pyats.log==20.6.1 37 | pyats.reporter==20.6 38 | pyats.results==20.6 39 | pyats.tcl==20.6 40 | pyats.topology==20.6 41 | pyats.utils==20.6 42 | pycryptodomex==3.9.8 43 | pysmi==0.3.4 44 | pysnmp==4.4.12 45 | python-engineio==3.13.1 46 | python-socketio==4.6.0 47 | PyYAML==5.3.1 48 | requests==2.24.0 49 | six==1.15.0 50 | tqdm==4.47.0 51 | unicon==20.6 52 | unicon.plugins==20.6 53 | urllib3==1.25.9 54 | xmltodict==0.12.0 55 | yamllint==1.24.1 56 | yarl==1.4.2 57 | zipp==3.1.0 58 | -------------------------------------------------------------------------------- /tests/setup_ncs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . /opt/ncs/ncsrc 4 | ncs-setup --package /opt/ncs/packages/neds/cisco-ios-cli-3.8 --dest /nso 5 | -------------------------------------------------------------------------------- /tests/templates/topology.j2: -------------------------------------------------------------------------------- 1 | lab: 2 | description: '' 3 | notes: '' 4 | timestamp: 1594737422.3398635 5 | title: "{{ TOPOLOGY_NAME }}" 6 | version: 0.0.3 7 | nodes: 8 | - id: n0 9 | label: sw-core 10 | node_definition: iosvl2 11 | x: -300 12 | y: -150 13 | configuration: |- 14 | Building configuration... 15 | 16 | Current configuration : 3284 bytes 17 | ! 18 | ! Last configuration change at 18:53:48 UTC Thu Jul 16 2020 19 | ! 20 | version 15.2 21 | service timestamps debug datetime msec 22 | service timestamps log datetime msec 23 | no service password-encryption 24 | service compress-config 25 | ! 26 | hostname sw-core 27 | ! 28 | boot-start-marker 29 | boot-end-marker 30 | ! 31 | ! 32 | no logging console 33 | ! 34 | username jclarke privilege 15 secret 5 $1$No/F$I/yatMX07sgtkExAG54w9/ 35 | no aaa new-model 36 | ! 37 | ! 38 | ! 39 | ! 40 | ! 41 | ! 42 | ip vrf mgmt 43 | ! 44 | ! 45 | ! 46 | ip domain-name marcuscom.com 47 | ip cef 48 | no ipv6 cef 49 | ! 50 | ! 51 | ! 52 | spanning-tree mode rapid-pvst 53 | spanning-tree extend system-id 54 | spanning-tree vlan 1-4094 priority 0 55 | ! 56 | ! 57 | ! 58 | ! 59 | ! 60 | ! 61 | ! 62 | ! 63 | ! 64 | ! 65 | ! 66 | ! 67 | ! 68 | ! 69 | ! 70 | interface GigabitEthernet0/0 71 | no switchport 72 | ip vrf forwarding mgmt 73 | ip address {{ CORE_IP_ADDRESS }} {{ SUBNET_MASK }} 74 | negotiation auto 75 | ! 76 | interface GigabitEthernet0/1 77 | description Core : Link to sw-dist 78 | switchport trunk allowed vlan 10 79 | switchport trunk encapsulation dot1q 80 | switchport mode trunk 81 | switchport nonegotiate 82 | load-interval 30 83 | negotiation auto 84 | spanning-tree portfast network 85 | spanning-tree link-type point-to-point 86 | ! 87 | interface GigabitEthernet0/2 88 | negotiation auto 89 | ! 90 | interface GigabitEthernet0/3 91 | negotiation auto 92 | ! 93 | ip forward-protocol nd 94 | ! 95 | ip http server 96 | ip http secure-server 97 | ! 98 | ip ssh version 2 99 | ip ssh server algorithm encryption aes128-ctr aes192-ctr aes256-ctr 100 | ip ssh client algorithm encryption aes128-ctr aes192-ctr aes256-ctr 101 | ! 102 | ! 103 | ! 104 | ! 105 | ! 106 | ! 107 | control-plane 108 | ! 109 | banner exec ^C 110 | ************************************************************************** 111 | * IOSv is strictly limited to use for evaluation, demonstration and IOS * 112 | * education. IOSv is provided as-is and is not supported by Cisco's * 113 | * Technical Advisory Center. Any use or disclosure, in whole or in part, * 114 | * of the IOSv Software or Documentation to any third party for any * 115 | * purposes is expressly prohibited except as otherwise authorized by * 116 | * Cisco in writing. * 117 | **************************************************************************^C 118 | banner incoming ^C 119 | ************************************************************************** 120 | * IOSv is strictly limited to use for evaluation, demonstration and IOS * 121 | * education. IOSv is provided as-is and is not supported by Cisco's * 122 | * Technical Advisory Center. Any use or disclosure, in whole or in part, * 123 | * of the IOSv Software or Documentation to any third party for any * 124 | * purposes is expressly prohibited except as otherwise authorized by * 125 | * Cisco in writing. * 126 | **************************************************************************^C 127 | banner login ^C 128 | ************************************************************************** 129 | * IOSv is strictly limited to use for evaluation, demonstration and IOS * 130 | * education. IOSv is provided as-is and is not supported by Cisco's * 131 | * Technical Advisory Center. Any use or disclosure, in whole or in part, * 132 | * of the IOSv Software or Documentation to any third party for any * 133 | * purposes is expressly prohibited except as otherwise authorized by * 134 | * Cisco in writing. * 135 | **************************************************************************^C 136 | ! 137 | line con 0 138 | exec-timeout 0 0 139 | line aux 0 140 | line vty 0 4 141 | login local 142 | line vty 5 15 143 | login local 144 | ! 145 | ! 146 | end 147 | image_definition: iosvl2-2019 148 | tags: [] 149 | interfaces: 150 | - id: i0 151 | label: Loopback0 152 | type: loopback 153 | - id: i1 154 | slot: 0 155 | label: GigabitEthernet0/0 156 | type: physical 157 | - id: i2 158 | slot: 1 159 | label: GigabitEthernet0/1 160 | type: physical 161 | - id: i3 162 | slot: 2 163 | label: GigabitEthernet0/2 164 | type: physical 165 | - id: i4 166 | slot: 3 167 | label: GigabitEthernet0/3 168 | type: physical 169 | - id: n1 170 | label: sw-dist 171 | node_definition: iosvl2 172 | x: -300 173 | y: -50 174 | configuration: |- 175 | Building configuration... 176 | 177 | Current configuration : 3534 bytes 178 | ! 179 | ! Last configuration change at 19:11:50 UTC Thu Jul 16 2020 180 | ! 181 | version 15.2 182 | service timestamps debug datetime msec 183 | service timestamps log datetime msec 184 | no service password-encryption 185 | service compress-config 186 | ! 187 | hostname sw-dist 188 | ! 189 | boot-start-marker 190 | boot-end-marker 191 | ! 192 | ! 193 | no logging console 194 | ! 195 | username jclarke privilege 15 secret 5 $1$6B8v$2YPHomvdzJP.ImOjkF.2i1 196 | no aaa new-model 197 | ! 198 | ! 199 | ! 200 | ! 201 | ! 202 | ! 203 | ip vrf mgmt 204 | ! 205 | ! 206 | ! 207 | ip domain-name marcuscom.com 208 | ip cef 209 | no ipv6 cef 210 | ! 211 | ! 212 | ! 213 | spanning-tree mode rapid-pvst 214 | spanning-tree extend system-id 215 | spanning-tree vlan 1-4094 priority 4096 216 | ! 217 | ! 218 | ! 219 | ! 220 | ! 221 | ! 222 | ! 223 | ! 224 | ! 225 | ! 226 | ! 227 | ! 228 | ! 229 | ! 230 | ! 231 | interface GigabitEthernet0/0 232 | no switchport 233 | ip vrf forwarding mgmt 234 | ip address {{ DIST_IP_ADDRESS }} {{ SUBNET_MASK }} 235 | negotiation auto 236 | ! 237 | interface GigabitEthernet0/1 238 | description Core : Link to sw-core 239 | switchport trunk allowed vlan 10 240 | switchport trunk encapsulation dot1q 241 | switchport mode trunk 242 | switchport nonegotiate 243 | load-interval 30 244 | negotiation auto 245 | spanning-tree portfast network 246 | spanning-tree link-type point-to-point 247 | ! 248 | interface GigabitEthernet0/2 249 | description Core : Link to sw-access 250 | switchport trunk allowed vlan 10 251 | switchport trunk encapsulation dot1q 252 | switchport mode trunk 253 | switchport nonegotiate 254 | load-interval 30 255 | negotiation auto 256 | spanning-tree portfast network 257 | spanning-tree link-type point-to-point 258 | ! 259 | interface GigabitEthernet0/3 260 | negotiation auto 261 | ! 262 | ip forward-protocol nd 263 | ! 264 | ip http server 265 | ip http secure-server 266 | ! 267 | ip ssh version 2 268 | ip ssh server algorithm encryption aes128-ctr aes192-ctr aes256-ctr 269 | ip ssh client algorithm encryption aes128-ctr aes192-ctr aes256-ctr 270 | ! 271 | ! 272 | ! 273 | ! 274 | ! 275 | ! 276 | control-plane 277 | ! 278 | banner exec ^C 279 | ************************************************************************** 280 | * IOSv is strictly limited to use for evaluation, demonstration and IOS * 281 | * education. IOSv is provided as-is and is not supported by Cisco's * 282 | * Technical Advisory Center. Any use or disclosure, in whole or in part, * 283 | * of the IOSv Software or Documentation to any third party for any * 284 | * purposes is expressly prohibited except as otherwise authorized by * 285 | * Cisco in writing. * 286 | **************************************************************************^C 287 | banner incoming ^C 288 | ************************************************************************** 289 | * IOSv is strictly limited to use for evaluation, demonstration and IOS * 290 | * education. IOSv is provided as-is and is not supported by Cisco's * 291 | * Technical Advisory Center. Any use or disclosure, in whole or in part, * 292 | * of the IOSv Software or Documentation to any third party for any * 293 | * purposes is expressly prohibited except as otherwise authorized by * 294 | * Cisco in writing. * 295 | **************************************************************************^C 296 | banner login ^C 297 | ************************************************************************** 298 | * IOSv is strictly limited to use for evaluation, demonstration and IOS * 299 | * education. IOSv is provided as-is and is not supported by Cisco's * 300 | * Technical Advisory Center. Any use or disclosure, in whole or in part, * 301 | * of the IOSv Software or Documentation to any third party for any * 302 | * purposes is expressly prohibited except as otherwise authorized by * 303 | * Cisco in writing. * 304 | **************************************************************************^C 305 | ! 306 | line con 0 307 | exec-timeout 0 0 308 | line aux 0 309 | line vty 0 4 310 | login local 311 | line vty 5 15 312 | login local 313 | ! 314 | ! 315 | end 316 | image_definition: iosvl2-2019 317 | tags: [] 318 | interfaces: 319 | - id: i0 320 | label: Loopback0 321 | type: loopback 322 | - id: i1 323 | slot: 0 324 | label: GigabitEthernet0/0 325 | type: physical 326 | - id: i2 327 | slot: 1 328 | label: GigabitEthernet0/1 329 | type: physical 330 | - id: i3 331 | slot: 2 332 | label: GigabitEthernet0/2 333 | type: physical 334 | - id: i4 335 | slot: 3 336 | label: GigabitEthernet0/3 337 | type: physical 338 | - id: n2 339 | label: sw-access 340 | node_definition: iosvl2 341 | x: -300 342 | y: 50 343 | configuration: |- 344 | Building configuration... 345 | 346 | Current configuration : 3289 bytes 347 | ! 348 | ! Last configuration change at 19:05:41 UTC Thu Jul 16 2020 349 | ! 350 | version 15.2 351 | service timestamps debug datetime msec 352 | service timestamps log datetime msec 353 | no service password-encryption 354 | service compress-config 355 | ! 356 | hostname sw-access 357 | ! 358 | boot-start-marker 359 | boot-end-marker 360 | ! 361 | ! 362 | no logging console 363 | ! 364 | username jclarke privilege 15 secret 5 $1$sdsI$kB9mLr6aWcs4UecP2kX6A. 365 | no aaa new-model 366 | ! 367 | ! 368 | ! 369 | ! 370 | ! 371 | ! 372 | ip vrf mgmt 373 | ! 374 | ! 375 | ! 376 | ip domain-name marcuscom.com 377 | ip cef 378 | no ipv6 cef 379 | ! 380 | ! 381 | ! 382 | spanning-tree mode rapid-pvst 383 | spanning-tree extend system-id 384 | spanning-tree vlan 1-4094 priority 8192 385 | ! 386 | ! 387 | ! 388 | ! 389 | ! 390 | ! 391 | ! 392 | ! 393 | ! 394 | ! 395 | ! 396 | ! 397 | ! 398 | ! 399 | ! 400 | interface GigabitEthernet0/0 401 | no switchport 402 | ip vrf forwarding mgmt 403 | ip address {{ ACCESS_IP_ADDRESS }} {{ SUBNET_MASK }} 404 | negotiation auto 405 | ! 406 | interface GigabitEthernet0/1 407 | description Core : Link to sw-dist 408 | switchport trunk allowed vlan 10 409 | switchport trunk encapsulation dot1q 410 | switchport mode trunk 411 | switchport nonegotiate 412 | load-interval 30 413 | negotiation auto 414 | spanning-tree portfast network 415 | spanning-tree link-type point-to-point 416 | ! 417 | interface GigabitEthernet0/2 418 | negotiation auto 419 | ! 420 | interface GigabitEthernet0/3 421 | negotiation auto 422 | ! 423 | ip forward-protocol nd 424 | ! 425 | ip http server 426 | ip http secure-server 427 | ! 428 | ip ssh version 2 429 | ip ssh server algorithm encryption aes128-ctr aes192-ctr aes256-ctr 430 | ip ssh client algorithm encryption aes128-ctr aes192-ctr aes256-ctr 431 | ! 432 | ! 433 | ! 434 | ! 435 | ! 436 | ! 437 | control-plane 438 | ! 439 | banner exec ^C 440 | ************************************************************************** 441 | * IOSv is strictly limited to use for evaluation, demonstration and IOS * 442 | * education. IOSv is provided as-is and is not supported by Cisco's * 443 | * Technical Advisory Center. Any use or disclosure, in whole or in part, * 444 | * of the IOSv Software or Documentation to any third party for any * 445 | * purposes is expressly prohibited except as otherwise authorized by * 446 | * Cisco in writing. * 447 | **************************************************************************^C 448 | banner incoming ^C 449 | ************************************************************************** 450 | * IOSv is strictly limited to use for evaluation, demonstration and IOS * 451 | * education. IOSv is provided as-is and is not supported by Cisco's * 452 | * Technical Advisory Center. Any use or disclosure, in whole or in part, * 453 | * of the IOSv Software or Documentation to any third party for any * 454 | * purposes is expressly prohibited except as otherwise authorized by * 455 | * Cisco in writing. * 456 | **************************************************************************^C 457 | banner login ^C 458 | ************************************************************************** 459 | * IOSv is strictly limited to use for evaluation, demonstration and IOS * 460 | * education. IOSv is provided as-is and is not supported by Cisco's * 461 | * Technical Advisory Center. Any use or disclosure, in whole or in part, * 462 | * of the IOSv Software or Documentation to any third party for any * 463 | * purposes is expressly prohibited except as otherwise authorized by * 464 | * Cisco in writing. * 465 | **************************************************************************^C 466 | ! 467 | line con 0 468 | exec-timeout 0 0 469 | line aux 0 470 | line vty 0 4 471 | login local 472 | line vty 5 15 473 | login local 474 | ! 475 | ! 476 | end 477 | image_definition: iosvl2-2019 478 | tags: [] 479 | interfaces: 480 | - id: i0 481 | label: Loopback0 482 | type: loopback 483 | - id: i1 484 | slot: 0 485 | label: GigabitEthernet0/0 486 | type: physical 487 | - id: i2 488 | slot: 1 489 | label: GigabitEthernet0/1 490 | type: physical 491 | - id: i3 492 | slot: 2 493 | label: GigabitEthernet0/2 494 | type: physical 495 | - id: i4 496 | slot: 3 497 | label: GigabitEthernet0/3 498 | type: physical 499 | - id: n3 500 | label: sw-mgmt 501 | node_definition: unmanaged_switch 502 | x: -550 503 | y: -50 504 | configuration: '' 505 | tags: [] 506 | interfaces: 507 | - id: i0 508 | slot: 0 509 | label: port0 510 | type: physical 511 | - id: i1 512 | slot: 1 513 | label: port1 514 | type: physical 515 | - id: i2 516 | slot: 2 517 | label: port2 518 | type: physical 519 | - id: i3 520 | slot: 3 521 | label: port3 522 | type: physical 523 | - id: i4 524 | slot: 4 525 | label: port4 526 | type: physical 527 | - id: i5 528 | slot: 5 529 | label: port5 530 | type: physical 531 | - id: i6 532 | slot: 6 533 | label: port6 534 | type: physical 535 | - id: i7 536 | slot: 7 537 | label: port7 538 | type: physical 539 | - id: n4 540 | label: OOB Management 541 | node_definition: external_connector 542 | x: -550 543 | y: -150 544 | configuration: bridge0 545 | tags: [] 546 | interfaces: 547 | - id: i0 548 | slot: 0 549 | label: port 550 | type: physical 551 | links: 552 | - id: l0 553 | i1: i0 554 | n1: n3 555 | i2: i1 556 | n2: n0 557 | - id: l1 558 | i1: i1 559 | n1: n3 560 | i2: i1 561 | n2: n1 562 | - id: l2 563 | i1: i2 564 | n1: n3 565 | i2: i1 566 | n2: n2 567 | - id: l3 568 | i1: i3 569 | n1: n3 570 | i2: i0 571 | n2: n4 572 | - id: l4 573 | i1: i2 574 | n1: n0 575 | i2: i2 576 | n2: n1 577 | - id: l5 578 | i1: i3 579 | n1: n1 580 | i2: i2 581 | n2: n2 582 | -------------------------------------------------------------------------------- /tests/test_vlan_service.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from genie.testbed import load as tbload 4 | import os 5 | import json 6 | import logging 7 | from yaml import load, dump 8 | 9 | try: 10 | from yaml import CLoader as Loader, CDumper as Dumper 11 | except ImportError: 12 | from yaml import Loader, Dumper 13 | 14 | log = logging.getLogger(__name__) 15 | logging.basicConfig(level=logging.INFO, format="%(message)s") 16 | 17 | 18 | def main(): 19 | devices = [] 20 | vlans = [] 21 | with open("hq-topology.yaml") as fd: 22 | devices = load(fd, Loader=Loader)["devices"] 23 | 24 | with open("hq-vlan-service.yaml") as fd: 25 | vlans = load(fd, Loader=Loader)["vlan"] 26 | 27 | device_details = {"devices": {}} 28 | for dev in devices: 29 | device_details["devices"][dev["name"]] = { 30 | "protocol": "ssh", 31 | "ip": dev["address"], 32 | "username": os.environ["HQ_USERNAME"], 33 | "password": os.environ["HQ_PASSWORD"], 34 | "os": dev["os"], 35 | "ssh_options": "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null", 36 | } 37 | 38 | testbed = tbload(device_details) 39 | 40 | # Get the VLAN 41 | error_occurred = False 42 | for topo_dev in devices: 43 | log.info(f"Testing to {topo_dev['name']}...") 44 | d = testbed.devices[topo_dev["name"]] 45 | d.connect( 46 | learn_hostname=True, log_stdout=False, ssh_options="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null", 47 | ) 48 | 49 | spantree = d.parse("show spanning-tree summary") 50 | num_trunks = 0 51 | if "uplink_trunk" in topo_dev: 52 | num_trunks += 1 53 | if "downlink_trunks" in topo_dev: 54 | num_trunks += len(topo_dev["downlink_trunks"]) 55 | 56 | for vlan in vlans: 57 | vname = "VLAN%04d" % vlan["vlanid"] 58 | if int(spantree["mode"]["rapid_pvst"][vname]["forwarding"]) < num_trunks: 59 | log.error( 60 | f"Spanning-tree test on {topo_dev['name']} for VLAN {vlan['vlanid']} failed: {json.dumps(spantree['mode']['rapid_pvst'][vname])}" 61 | ) 62 | error_occurred = True 63 | 64 | d.disconnect() 65 | 66 | if error_occurred: 67 | log.error("At least one test failed!") 68 | exit(1) 69 | 70 | log.info("All tests passed!") 71 | 72 | 73 | if __name__ == "__main__": 74 | main() 75 | -------------------------------------------------------------------------------- /tests/vlan-service/README: -------------------------------------------------------------------------------- 1 | This is a generated Python package, made by: 2 | 3 | ncs-make-package --service-skeleton python-and-template \ 4 | --component-class main.Main vlan-service 5 | 6 | It contains a dummy YANG model which implements a minimal Service 7 | and an Action that doesn't really do anything useful. They are 8 | there just to get you going. 9 | 10 | You will also find two test cases in: 11 | 12 | test/internal/lux/service/ 13 | test/internal/lux/action/ 14 | 15 | that you can run if you have the 'lux' testing tool. 16 | Your top Makefile also need to implement some Make targets 17 | as described in the Makefiles of the test cases. 18 | You can also just read the corresponding run.lux tests and 19 | do them manually if you wish. 20 | 21 | The 'lux' test tool can be obtained from: 22 | 23 | https://github.com/hawk/lux.git 24 | -------------------------------------------------------------------------------- /tests/vlan-service/load-dir/switch-topology.fxs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CiscoDevNet/cml-cicd/1a7083901c8574a8b7a944c8f00a5daaf1428018/tests/vlan-service/load-dir/switch-topology.fxs -------------------------------------------------------------------------------- /tests/vlan-service/load-dir/vlan-service.fxs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CiscoDevNet/cml-cicd/1a7083901c8574a8b7a944c8f00a5daaf1428018/tests/vlan-service/load-dir/vlan-service.fxs -------------------------------------------------------------------------------- /tests/vlan-service/package-meta-data.xml: -------------------------------------------------------------------------------- 1 | 2 | vlan-service 3 | 1.0 4 | Generated Python package 5 | 5.4 6 | 7 | 8 | main 9 | 10 | vlan_service.main.Main 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/vlan-service/python/vlan_service/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CiscoDevNet/cml-cicd/1a7083901c8574a8b7a944c8f00a5daaf1428018/tests/vlan-service/python/vlan_service/__init__.py -------------------------------------------------------------------------------- /tests/vlan-service/python/vlan_service/main.py: -------------------------------------------------------------------------------- 1 | # -*- mode: python; python-indent: 4 -*- 2 | import ncs 3 | from ncs.application import Service 4 | 5 | 6 | # ------------------------ 7 | # SERVICE CALLBACK EXAMPLE 8 | # ------------------------ 9 | class DataServiceCallbacks(Service): 10 | @Service.create 11 | def cb_create(self, tctx, root, service, proplist): 12 | return 13 | 14 | 15 | class ServiceCallbacks(Service): 16 | 17 | # The create() callback is invoked inside NCS FASTMAP and 18 | # must always exist. 19 | @Service.create 20 | def cb_create(self, tctx, root, service, proplist): 21 | self.log.info("Service create(service=", service._path, ")") 22 | 23 | vars = ncs.template.Variables() 24 | vars.add("DUMMY", "127.0.0.1") 25 | template = ncs.template.Template(service) 26 | 27 | vlans = service.vlan 28 | 29 | for vlan in vlans: 30 | self.log.info(f"Looking at VLAN {vlan.vlanid} ({vlan.name})") 31 | vlan_vars = ncs.template.Variables() 32 | vlan_vars.add("VLAN_ID", vlan.vlanid) 33 | vlan_vars.add("VLAN_NAME", vlan.name) 34 | switches = root.switch_topology[service.topology].switch 35 | self.log.info(f"Switches = {switches}") 36 | 37 | for switch in switches: 38 | vlan_vars.add("DEVICE_NAME", switch.device) 39 | self.log.info( 40 | f"Applying VLAN config to {switch.device} (VLAN ID: {vlan.vlanid}, VLAN Name: {vlan.name})" 41 | ) 42 | template.apply("vlan-service-template", vlan_vars) 43 | trunks = [] 44 | if switch.uplink_trunk: 45 | trunks.append(switch.uplink_trunk) 46 | elif switch.uplink_trunk_name: 47 | trunks.append(switch.uplink_trunk_name) 48 | 49 | if switch.downlink_trunk and len(switch.downlink_trunk) > 0: 50 | trunks += switch.downlink_trunk 51 | elif switch.downlink_trunk_name and len(switch.downlink_trunk_name) > 0: 52 | trunks += switch.downlink_trunk_name 53 | trunk_vars = ncs.template.Variables() 54 | trunk_vars.add("VLAN_ID", vlan.vlanid) 55 | trunk_vars.add("DEVICE_NAME", switch.device) 56 | for trunk in trunks: 57 | trunk_vars.add("TRUNK_PORT", trunk) 58 | self.log.info( 59 | f"Adding VLAN {vlan.vlanid} to port {trunk} on {switch.device}" 60 | ) 61 | template.apply("trunk-port-template", trunk_vars) 62 | 63 | 64 | # --------------------------------------------- 65 | # COMPONENT THREAD THAT WILL BE STARTED BY NCS. 66 | # --------------------------------------------- 67 | class Main(ncs.application.Application): 68 | def setup(self): 69 | # The application class sets up logging for us. It is accessible 70 | # through 'self.log' and is a ncs.log.Log instance. 71 | self.log.info("Main RUNNING") 72 | 73 | # Service callbacks require a registration for a 'service point', 74 | # as specified in the corresponding data model. 75 | # 76 | self.register_service("vlan-service-servicepoint", ServiceCallbacks) 77 | self.register_service("switch-topology-servicepoint", DataServiceCallbacks) 78 | 79 | # If we registered any callback(s) above, the Application class 80 | # took care of creating a daemon (related to the service/action point). 81 | 82 | # When this setup method is finished, all registrations are 83 | # considered done and the application is 'started'. 84 | 85 | def teardown(self): 86 | # When the application is finished (which would happen if NCS went 87 | # down, packages were reloaded or some error occurred) this teardown 88 | # method will be called. 89 | 90 | self.log.info("Main FINISHED") 91 | -------------------------------------------------------------------------------- /tests/vlan-service/src/Makefile: -------------------------------------------------------------------------------- 1 | all: fxs 2 | .PHONY: all 3 | 4 | # Include standard NCS examples build definitions and rules 5 | include $(NCS_DIR)/src/ncs/build/include.ncs.mk 6 | 7 | SRC = $(wildcard yang/*.yang) 8 | DIRS = ../load-dir java/src/$(JDIR)/$(NS) 9 | FXS = $(SRC:yang/%.yang=../load-dir/%.fxs) 10 | 11 | ## Uncomment and patch the line below if you have a dependency to a NED 12 | ## or to other YANG files 13 | YANGPATH += ${NCS_DIR}/packages/neds/cisco-ios-cli-3.8/src/ncsc-out/modules/yang 14 | YANGPATH += ./yang 15 | 16 | NCSCPATH = $(YANGPATH:%=--yangpath %) 17 | YANGERPATH = $(YANGPATH:%=--path %) 18 | 19 | fxs: $(DIRS) $(FXS) 20 | 21 | $(DIRS): 22 | mkdir -p $@ 23 | 24 | ../load-dir/%.fxs: yang/%.yang 25 | $(NCSC) `ls $*-ann.yang > /dev/null 2>&1 && echo "-a $*-ann.yang"` \ 26 | $(NCSCPATH) -c -o $@ $< 27 | 28 | clean: 29 | rm -rf $(DIRS) 30 | .PHONY: clean 31 | -------------------------------------------------------------------------------- /tests/vlan-service/src/yang/switch-topology.yang: -------------------------------------------------------------------------------- 1 | module switch-topology { 2 | namespace "http://example.com/switch-topology"; 3 | prefix switch-topology; 4 | 5 | import ietf-inet-types { 6 | prefix inet; 7 | } 8 | import tailf-common { 9 | prefix tailf; 10 | } 11 | import tailf-ncs { 12 | prefix ncs; 13 | } 14 | import tailf-ned-cisco-ios { 15 | prefix ios; 16 | } 17 | 18 | description 19 | "Representation of upstream and downstream switch links."; 20 | 21 | revision 2016-01-01 { 22 | description 23 | "Initial revision."; 24 | } 25 | 26 | list switch-topology { 27 | description 28 | "Set of switches in a given topology"; 29 | key "name"; 30 | leaf name { 31 | tailf:info "Topology name"; 32 | type string; 33 | description 34 | "Topology name"; 35 | } 36 | uses ncs:service-data; 37 | ncs:servicepoint "switch-topology-servicepoint"; 38 | list switch { 39 | ordered-by user; 40 | key "device"; 41 | leaf device { 42 | type leafref { 43 | path "/ncs:devices/ncs:device/ncs:name"; 44 | } 45 | description 46 | "Name of the switch in the topology"; 47 | } 48 | choice downlink { 49 | default "other"; 50 | case ios { 51 | when "/ncs:devices/ncs:device[ncs:name=current()/device]/ncs:config/ios:interface"; 52 | leaf-list downlink-trunk { 53 | type leafref { 54 | path "deref(../device)/../ncs:config/ios:interface/ios:GigabitEthernet/ios:name"; 55 | } 56 | description 57 | "Downlink trunk port"; 58 | } 59 | } 60 | case other { 61 | when "not(/ncs:devices/ncs:device[ncs:name=current()/device]/ncs:config/ios:interface)"; 62 | leaf-list downlink-trunk-name { 63 | type string; 64 | description 65 | "Downlink trunk port name"; 66 | } 67 | } 68 | } 69 | choice uplink { 70 | default "other"; 71 | case ios { 72 | when "/ncs:devices/ncs:device[ncs:name=current()/device]/ncs:config/ios:interface"; 73 | leaf uplink-trunk { 74 | type leafref { 75 | path "deref(../device)/../ncs:config/ios:interface/ios:GigabitEthernet/ios:name"; 76 | } 77 | description 78 | "Uplink trunk port"; 79 | } 80 | } 81 | case other { 82 | when "not(/ncs:devices/ncs:device[ncs:name=current()/device]/ncs:config/ios:interface)"; 83 | leaf uplink-trunk-name { 84 | type string; 85 | description 86 | "Uplink trunk port name"; 87 | } 88 | } 89 | } 90 | description 91 | "Switches in this topology"; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /tests/vlan-service/src/yang/vlan-service.yang: -------------------------------------------------------------------------------- 1 | module vlan-service { 2 | namespace "http://example.com/vlan-service"; 3 | prefix vlan-service; 4 | 5 | import ietf-inet-types { 6 | prefix inet; 7 | } 8 | import tailf-common { 9 | prefix tailf; 10 | } 11 | import tailf-ncs { 12 | prefix ncs; 13 | } 14 | import switch-topology { 15 | prefix switch-topology; 16 | } 17 | 18 | description 19 | "Define a set of VLANs."; 20 | 21 | revision 2016-01-01 { 22 | description 23 | "Initial revision."; 24 | } 25 | 26 | list vlan-service { 27 | description 28 | "Devices that provide a particular set of VLANs"; 29 | key "name"; 30 | leaf name { 31 | tailf:info "VLAN Set name"; 32 | type string; 33 | description 34 | "VLAN Set Name"; 35 | } 36 | leaf topology { 37 | type leafref { 38 | path "/switch-topology:switch-topology/switch-topology:name"; 39 | } 40 | mandatory true; 41 | description 42 | "The switch topology to which this set of VLANs belong"; 43 | } 44 | uses ncs:service-data; 45 | ncs:servicepoint "vlan-service-servicepoint"; 46 | list vlan { 47 | key "vlanid"; 48 | leaf vlanid { 49 | type uint16 { 50 | range "1..4094"; 51 | } 52 | description 53 | "Unique ID for this VLAN"; 54 | tailf:info "Unique ID for this VLAN"; 55 | } 56 | leaf name { 57 | type string { 58 | pattern "[\\d\\w_\\.-]+"; 59 | } 60 | mandatory true; 61 | tailf:info "Name of this VLAN"; 62 | description 63 | "Name of this VLAN"; 64 | } 65 | description 66 | "Set of VLANs for this service"; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/vlan-service/templates/trunk-port-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {$DEVICE_NAME} 6 | 7 | 8 | 9 | {$TRUNK_PORT} 10 | 11 | 12 | 13 | 14 | {$VLAN_ID} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/vlan-service/templates/vlan-service-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {$DEVICE_NAME} 6 | 7 | 8 | 9 | {$VLAN_ID} 10 | {$VLAN_NAME} 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/vlan-service/test/Makefile: -------------------------------------------------------------------------------- 1 | DIRS = external internal 2 | 3 | ifeq ($(BUILD_JOB),external) 4 | DIR = external 5 | endif 6 | 7 | ifeq ($(BUILD_JOB),internal_realhw) 8 | DIR = internal 9 | JOB_DIR = realhw 10 | endif 11 | 12 | ifeq ($(BUILD_JOB),internal_simulated) 13 | DIR = internal 14 | JOB_DIR = simulated 15 | endif 16 | 17 | ifeq ($(BUILD_JOB),) 18 | DIR = internal 19 | JOB_DIR = simulated 20 | endif 21 | 22 | all: test 23 | 24 | build: 25 | $(MAKE) -C $(DIR) build JOB_DIR=$(JOB_DIR) || exit 1 26 | 27 | clean: 28 | $(MAKE) -C $(DIR) clean JOB_DIR=$(JOB_DIR) || exit 1 29 | 30 | test: 31 | $(MAKE) -C $(DIR) test JOB_DIR=$(JOB_DIR) || exit 1 32 | 33 | desc: 34 | @echo "==Test Cases for NED==" 35 | @for d in $(DIRS) ; do \ 36 | $(MAKE) -sC $$d desc || exit 1; \ 37 | done 38 | -------------------------------------------------------------------------------- /tests/vlan-service/test/internal/Makefile: -------------------------------------------------------------------------------- 1 | DIRS = lux 2 | 3 | build: 4 | @for d in $(DIRS) ; do \ 5 | $(MAKE) -C $$d build || exit 1; \ 6 | done 7 | 8 | clean: 9 | @for d in $(DIRS) ; do \ 10 | $(MAKE) -C $$d clean || exit 1; \ 11 | done 12 | 13 | test: 14 | @for d in $(DIRS) ; do \ 15 | $(MAKE) -C $$d test || exit 1; \ 16 | done 17 | 18 | desc: 19 | @for d in $(DIRS) ; do \ 20 | $(MAKE) -C $$d desc || exit 1; \ 21 | done 22 | -------------------------------------------------------------------------------- /tests/vlan-service/test/internal/lux/Makefile: -------------------------------------------------------------------------------- 1 | DIRS = service 2 | 3 | build: 4 | @for d in $(DIRS) ; do \ 5 | $(MAKE) -C $$d build || exit 1; \ 6 | done 7 | 8 | clean: 9 | @for d in $(DIRS) ; do \ 10 | $(MAKE) -C $$d clean || exit 1; \ 11 | done 12 | 13 | test: 14 | @for d in $(DIRS) ; do \ 15 | $(MAKE) -C $$d test || exit 1; \ 16 | done 17 | 18 | desc: 19 | @for d in $(DIRS) ; do \ 20 | $(MAKE) -C $$d desc || exit 1; \ 21 | done 22 | -------------------------------------------------------------------------------- /tests/vlan-service/test/internal/lux/service/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # The 'lux' test tool can be obtained from: 3 | # 4 | # https://github.com/hawk/lux.git 5 | # 6 | 7 | # Make sure the TARGET_DIR has got the following make targets: 8 | .PHONY: clean build start stop 9 | 10 | export TARGET_DIR=../../../../../.. 11 | 12 | .PHONY: test 13 | test: 14 | lux run.lux 15 | 16 | clean: 17 | $(MAKE) -C $(TARGET_DIR) clean 18 | 19 | build: 20 | $(MAKE) -C $(TARGET_DIR) build 21 | 22 | start: 23 | $(MAKE) -C $(TARGET_DIR) start 24 | 25 | stop: 26 | $(MAKE) -C $(TARGET_DIR) stop 27 | -------------------------------------------------------------------------------- /tests/vlan-service/test/internal/lux/service/dummy-device.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | eth 5 |
192.168.110.1
6 | 7 | southbound-locked 8 | 9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /tests/vlan-service/test/internal/lux/service/dummy-service.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | 6 | 7 | 8 | 33 9 | eth 10 | 127.0.0.1 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/vlan-service/test/internal/lux/service/pyvm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ./logs/ncs-python-vm 5 | level-info 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/vlan-service/test/internal/lux/service/run.lux: -------------------------------------------------------------------------------- 1 | # 2 | # The 'lux' test tool can be obtained from: 3 | # 4 | # https://github.com/hawk/lux.git 5 | # 6 | [global target_dir=../../../../../..] 7 | [config skip_unless=PYTHON] 8 | 9 | [shell top] 10 | !make stop build 11 | !echo ==$$?== 12 | ?==0== 13 | ?SH-PROMPT: 14 | 15 | !rm ${target_dir}/ncs-cdb/* 16 | ?SH-PROMPT: 17 | !cp pyvm.xml ${target_dir}/ncs-cdb/. 18 | ?SH-PROMPT: 19 | 20 | !make start 21 | !echo ==$$?== 22 | ?==0== 23 | ?SH-PROMPT: 24 | 25 | [progress \nCreate a dummy device...\n] 26 | !ncs_load -lm dummy-device.xml 27 | ?SH-PROMPT: 28 | [progress \nCreate a dummy device...ok\n] 29 | 30 | [sleep 3] 31 | 32 | [progress \nCreate the dummy service...\n] 33 | !ncs_load -lm dummy-service.xml 34 | ?SH-PROMPT: 35 | [progress \nCreate the dummy service...ok\n] 36 | 37 | 38 | [shell log] 39 | !cd ${target_dir} 40 | ?SH-PROMPT: 41 | 42 | [progress \nVerify that the service code has been invoked...\n] 43 | !tail -f ./logs/ncs-python-vm-vlan-service.log 44 | ?.*Worker RUNNING.* 45 | ?.*Service create.* 46 | [progress \nVerify that the service code has been invoked...ok\n] 47 | 48 | 49 | [cleanup] 50 | !make stop 51 | !echo ==$$?== 52 | ?==0== 53 | ?SH-PROMPT: 54 | --------------------------------------------------------------------------------