How can Ansible help people building simulations with Cisco Modeling Labs (CML)?
Similar to Terraform, Ansible is a common, open-source automation tool often used in
Continuous Integration/Continuous Deployment (CI/CD) DevOps methodologies. They are both a type of Infrastructure as Code (IaC) or Infrastructure as Data that allow you to render your infrastructure as text files and control it using tools such as Git. The advantage is reproducibility, consistency, speed, and the knowledge that, when you change the code, people approve, and it gets tested before it’s pushed out to your production network. This paradigm allows enterprises to run their network infrastructure in the same way they run their software and cloud practices. Afterall, the infrastructure is there to support the apps, so why manage them differently?
Although overlaps exist in the capabilities of Terraform and Ansible, they are very complementary. While Terraform is better at the initial deployment and ensuring ongoing consistency of the underlying infrastructure, Ansible is better at the initial configuration and ongoing management of the things that live in that infrastructure, such as systems, network devices, and so on.
In a common workflow in which an operator wants to make a change to the network, let’s say adding a new network to be advertised via BGP, a network engineer would specify that change in the code or more likely as configuration data in YAML or JSON. In a typical CI workflow, that change would need to be approved by others for correctness or adherence to corporate and security concerns, for instance. In addition to the eyeball tests, a series of automated testing validates the data and then deploys the proposed change in a test network. Those tests can be run in a physical test network, a virtual test network, or a combination of the two. That flow might look like the following:
The advantage of leveraging virtual test networks is profound. The cost is dramatically lower, and the ability to automate testing is increased significantly. For example, a network engineer can spin up and configure a new, complex topology multiple times without the likelihood of old tests messing up the accuracy of the current testing. Cisco Modeling Labs is a great tool for this type of test.
Here’s where the Ansible CML Collection comes in. Similar to the CML Terraform integration covered in a previous blog, the Ansible CML Collection can automate the deployment of topologies in CML for testing. The Ansible CML Collection has modules to create, start, and stop a topology and the hosts within it, but more importantly, it has a dynamic inventory plugin for getting information about the topology. This is important for automation because topologies can change. Or multiple topologies could exist, depending on the tests being performed. If your topology uses dynamic host configuration protocol (DHCP) and/or CML’s PATty functionality, the information for how Ansible communicates with the nodes needs to be communicated to the playbook.
Let’s go over some of the features of the Ansible CML Collection’s dynamic inventory plugin.
First, we need to install the collection:
ansible-galaxy collection install cisco.cml
Next, we create a cml.yml in the inventory with the following contents to tell Ansible to use the Ansible CML Collection’s dynamic inventory plugin:
plugin: cisco.cml.cml_inventory
group_tags: network, ios, nxos, router
In addition to specifying the plugin name, we can also define tags that, when found on the devices in the topology, add that device to an Ansible group to be used later in the playbook:
In addition to specifying the plugin name, we can also define tags that, when found on the devices in the topology, add that device to an Ansible group to be used later in the playbook:
- CML_USERNAME: Username for the CML user
- CML_PASSWORD: Password for the CML user
- CML_HOST: The CML host
- CML_LAB: The name of the lab
Once the plugin knows how to communicate with the CML server and which lab to use, it can return information about the nodes in the lab:
ok: [hq-rtr1] => {
"cml_facts": {
"config": "hostname hq-rtr1\nvrf definition Mgmt-intf\n!\naddress-family ipv4\nexit-address-family\n!\naddress-family ipv6\nexit-address-family\n!\nusername admin privilege 15 secret 0 admin\ncdp run\nno aaa new-model\nip domain-name mdd.cisco.com\n!\ninterface GigabitEthernet1\nvrf forwarding Mgmt-intf\nip address dhcp\nnegotiation auto\nno cdp enable\nno shutdown\n!\ninterface GigabitEthernet2\ncdp enable\n!\ninterface GigabitEthernet3\ncdp enable\n!\ninterface GigabitEthernet4\ncdp enable\n!\nip http server\nip http secure-server\nip http max-connections 2\n!\nip ssh time-out 60\nip ssh version 2\nip ssh server algorithm encryption aes128-ctr aes192-ctr aes256-ctr\nip ssh client algorithm encryption aes128-ctr aes192-ctr aes256-ctr\n!\nline vty 0 4\nexec-timeout 30 0\nabsolute-timeout 60\nsession-limit 16\nlogin local\ntransport input ssh\n!\nend",
"cpus": 1,
"data_volume": null,
"image_definition": null,
"interfaces": [
{
"ipv4_addresses": null,
"ipv6_addresses": null,
"mac_address": null,
"name": "Loopback0",
"state": "STARTED"
},
{
"ipv4_addresses": [
"192.168.255.199"
],
"ipv6_addresses": [],
"mac_address": "52:54:00:13:51:66",
"name": "GigabitEthernet1",
"state": "STARTED"
}
],
"node_definition": "csr1000v",
"ram": 3072,
"state": "BOOTED"
}
}
The first IPv4 address found (in order of the interfaces) is used as `ansible_host` to enable the playbook to connect to the device. We can use the cisco.cml.inventory playbook included in the collection to show the inventory. In this case, we only specify that we want devices that are in the “router” group created by the inventory plugin as informed by the tags on the devices:
mdd % ansible-playbook cisco.cml.inventory --limit=router
ok: [hq-rtr1] => {
"msg": "Node: hq-rtr1(csr1000v), State: BOOTED, Address: 192.168.255.199:22"
}
ok: [hq-rtr2] => {
"msg": "Node: hq-rtr2(csr1000v), State: BOOTED, Address: 192.168.255.53:22"
}
ok: [site1-rtr1] => {
"msg": "Node: site1-rtr1(csr1000v), State: BOOTED, Address: 192.168.255.63:22"
}
ok: [site2-rtr1] => {
"msg": "Node: site2-rtr1(csr1000v), State: BOOTED, Address: 192.168.255.7:22"
}
In addition to group tags, the CML dynamic inventory plugin will also parse tags to pass information from PATty and to create generic inventory facts:
If a CML tag is specified that matches `^pat:(?:tcp|udp)?:?(\d+):(\d+)`, the CML server address (as opposed to the first IPv4 address found) will be used for `ansible_host`. To change `ansible_port` to point to the translated SSH port, the tag `ansible:ansible_port=2020` can be set. These two tags tell the Ansible playbook to connect to port 2020 of the CML server to automate the specified host in the topology. The `ansible:` tag can also be used to specify other host facts. For example, the tag `ansible:nso_api_port=2021` can be used to tell the playbook the port to use to reach the Cisco NSO API. Any arbitrary fact can be set in this way.
Getting started
Trying out the CML Ansible Collection is easy. You can use the playbooks provided in the collection to load and start a topology in your CML server. To start, define the environment variable that tells the collection how to access your CML server:
% export CML_HOST=my-cml-server.my-domain.com
% export CML_USERNAME=my-cml-username
% export CML_PASSWORD=my-cml-password
The next step is to define your topology file. This is a standard topology file you can export from CML. There are two ways to define the topology file. First, you can use an environment variable:
% export CML_LAB=my-cml-labfile
Alternatively, you can specify the topology file when you run the playbook as an extra–var. For example, to spin up a topology using the built in cisco.cml.build playbook:
% ansible-playbook cisco.cml.build -e wait='yes' -e cml_lab_file=topology.yaml
This command loads and starts the topology; then it waits until all nodes are running to complete. If -e startup=’host’ is specified, the playbook will start each host individually as opposed to starting them all at once. This allows for the config to be generated and fed into the host on startup. When cml_config_file is defined in the host’s inventory, it is parsed as a Jinja file and fed into that host as config at startup. This allows for just-in-time configuration to occur.
Once the playbook completes, you can use another built-in playbook, cisco.cml.inventory, to get the inventory for the topology. In order to use it, first create a cml.yml in the inventory directory as shown above, then run the playbook as follows:
% ansible-playbook cisco.cml.inventory
PLAY [cml_hosts] **********************************************************************
TASK [debug] **********************************************************************
ok: [WAN-rtr1] => {
"msg": "Node: WAN-rtr1(csr1000v), State: BOOTED, Address: 192.168.255.53:22"
}
ok: [nso1] => {
"msg": "Node: nso1(ubuntu), State: BOOTED, Address: my-cml-server.my-domain.com:2010"
}
ok: [site1-host1] => {
"msg": "Node: site1-host1(ubuntu), State: BOOTED, Address: site1-host1:22"
}
In this truncated output, three different scenarios are shown. First, WAN-rtr1 is assigned the DHCP address it received for its ansible_host value, and ansible port is 22. If the host running the playbook has IP connectivity (either in the topology or a network connected to the topology with an external connector), it will be able to reach that host.
The second scenario shows an example of the PATty functionality with the host nso1 in which the dynamic inventory plugin reads those tags to determine that the host is available through the CML server’s interface (i.e. ansible_host is set to my-cml-server.my-domain.com). Also, it knows that ansible_port should be set to the port specified in the tags (i.e. 2010). After these values are set, the ansible playbook can reach the host in the topology using the PATty functionality in CML.
The last example, site1-host1, shows the scenario in which the CML dynamic inventory script can either find a DHCP allocated address or tags to specify to what ansible_host should be set, so it uses the node name. For the playbook to reach those hosts, it would have to have IP connectivity and be able to resolve the node name to an IP address.
These built-in playbooks show examples of how to use the functionality in the CML Ansible Collection to build your own playbooks, but you can also use them directly as part of your pipeline. In fact, we often use them directly in the pipelines we build for customers.
Source: cisco.com