├── 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 |
--------------------------------------------------------------------------------