Intro

Recently I converted from using the Virtualbox provider for Vagrant to using the Libvirt provider. This post will cover how to create a Cisco csr1000v Vagrant box for use with the vagrant-libvirt provider.

For reference the following software will be used in this post.

  • Cisco csr1000v - 3.15.00.S.155-2.S
  • Vagrant - Vagrant 1.9.6
  • vagrant-libvirt - 0.4.0
  • Ubuntu - 1604

Download

Cisco does not provide a Vagrant Libvirt box, but they do provide a qcow2 image. You will need to download the qcow2 from the Cisco software download section.

Search for csr1000v and download csr1000v-universalk9.03.15.00.S.155-2.S-std.qcow2 .

Note
A CCO account is required to download the software but its free to register and no support contract is required.

Install

Create and change into directory for csr1000v files. I like to keep my custom vagrant boxes under ~/vagrant/boxes/ .

cmd
mkdir -p ~/vagrant/boxes/cisco/csr1kv
cd ~/vagrant/boxes/cisco/csr1kv

Copy the qcow2 file downloaded earlier to the ~/vagrant/boxes/cisco/csr1kv directory.

cmd
cp ~/Downloads/csr1000v-universalk9.03.15.00.S.155-2.S-std.qcow2 .

The maintainers of the vagrant-libvirt plugin have a script that can be used to convert qcow2 images to a vagrant box. Download the libvirt conversion script.

cmd
curl -O https://raw.githubusercontent.com/vagrant-libvirt/vagrant-libvirt/master/tools/create_box.sh

Now lets build the csr1000v VM and apply a bootstrap configuration.

cmd
virt-install \
    --connect=qemu:///system \
    --name=csr1000v \
    --os-type=linux \
    --os-variant=rhel4 \
    --arch=x86_64 \
    --cpu host \
    --vcpus=1 \
    --hvm \
    --ram=4096 \
    --disk path=csr1000v-universalk9.03.15.00.S.155-2.S-std.qcow2,bus=ide,format=qcow2 \
    --network=network:vagrant-libvirt,model=virtio \
    --import

A virtual terminal will open that can be used to configure the VM, as far as I am aware you cannot copy and past into this terminal so it is not very convenient. Once the VM is booted configure the VM to use the serial port instead of the virtual console.

cmd
enable
conf t
platform console serial
end
copy system:running-config nvram:startup-config
reload

When the VM reboots, conenct via the console port and apply the bootstrap configuration.

cmd
virsh console csr1000v

Once connected apply the bootstrap configuration.

cmd
en
conf t
hostname csr
ip domain-name lab.local
!
crypto key generate rsa modulus 2048
ip ssh version 2
!
aaa new-model
!
aaa authentication login default local
aaa authorization exec default local
!
username vagrant privilege 15 secret vagrant
!
ip ssh pubkey-chain
  username vagrant
    key-hash ssh-rsa DD3BB82E850406E9ABFFA80AC0046ED6
!
!
interface GigabitEthernet1
  description vagrant-management
  ip address dhcp
  no shutdown
!
end
wr mem

Check the IP address assigned to interface GigabitEthernet1.

cmd
show ip int brie

# output

Interface              IP-Address      OK? Method Status                Protocol
GigabitEthernet1       192.168.121.227 YES DHCP   up                    up

To exit the console use one of these key combinations (Assuming English keyboard).

  • CTRL + ]
  • CTRL + 5
  • Press and hold CTRL and SHIFT while pressing 6 then ]

Now confirm that it is possible to SSH to the VM with the insecure_private_key .

cmd
# from host shell

ssh vagrant@192.168.121.227 -i ~/.vagrant.d/insecure_private_key

# now logged into guest csr

csr#

If you can successfully SSH to the csr1000v with the insecure_private_key its time to package the VM into a Vagrant box. First shutdown the VM.

cmd
virsh shutdown csr1000v

# output

Domain csr1000v is being shutdown

Create a metadata.json file with the following contents.

file
{"provider":"libvirt","format":"qcow2","virtual_size":8}

Use the previously downloaded create_box.sh script to make a Vagrant box from the qcow2 image.

cmd
bash create_box.sh csr1000v-universalk9.03.15.00.S.155-2.S-std.qcow2

# output

{8}
==> Creating box, tarring and gzipping
./metadata.json
./Vagrantfile
./box.img
Total bytes written: 1369313280 (1.3GiB, 33MiB/s)
==> csr1000v-universalk9.03.15.00.S.155-2.S-std.box created
==> You can now add the box:
==>   'vagrant box add csr1000v-universalk9.03.15.00.S.155-2.S-std.box --name csr1000v-universalk9.03.15.00.S.155-2.S-std'

Create a metadata file called csr1kv.json so that the box is added with the correct version number.

file
{
  "name": "cisco/csr1000v",
  "description": "Cisco csr1000v",
  "versions": [
    {
      "version": "03.15.00.S-155-2-S",
      "providers": [
        {
          "name": "libvirt",
          "url": "file:///home/bradmin/vagrant/boxes/cisco/csr1kv/csr1000v-universalk9.03.15.00.S.155-2.S-std.box"
        }
      ]
    }
  ]
}

Add the box to Vagrant using the csr1kv.json file.

cmd
vagrant box add csr1kv.json

# output

==> box: Loading metadata for box 'csr1kv.json'
    box: URL: file:///home/bradmin/vagrant/boxes/cisco/csr1kv.json
==> box: Adding box 'cisco/csr1000v' (v03.15.00.S-155-2-S) for provider: libvirt
    box: Unpacking necessary files from: file:///home/bradmin/vagrant/boxes/cisco/csr1000v-universalk9.03.15.00.S.155-2.S-std.box
==> box: Successfully added box 'cisco/csr1000v' (v03.15.00.S-155-2-S) for 'libvirt'!

Confirm the csr1000v box was added successfully.

cmd
vagrant box list

# output

CumulusCommunity/cumulus-vx          (libvirt, 3.4.2)
CumulusCommunity/cumulus-vx          (libvirt, 3.4.3)
CumulusCommunity/cumulus-vx          (virtualbox, 3.4.1)
arista/veos                          (libvirt, 4.18.1F)
arista/veos                          (virtualbox, 4.16.9M)
arista/veos                          (virtualbox, 4.17.5M)
arista/veos                          (virtualbox, 4.18.1F)
centos/7                             (libvirt, 1708.01)
centos/7                             (virtualbox, 1705.02)
cisco/asav                           (virtualbox, 9.8.1)
cisco/csr1000v                       (libvirt, 03.15.00.S-155-2-S)

cisco/csr1000v                       (virtualbox, 03.15.00)

Vagrantfile

Use this Vagrantfile to test out the new csr1000v Vagrant box.

file
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "cisco/csr1000v"

  # Turn off shared folders
  config.vm.synced_folder ".", "/vagrant", disabled: true

  # Do not try to insert new SSH key
  config.ssh.insert_key = false

  # Give VM time to boot
  config.vm.boot_timeout = 180

  # Set guest type to prevent guest type detection
  config.vm.guest = :freebsd

  # Provider-specific configuration
  config.vm.provider :libvirt do |domain|
    domain.nic_adapter_count = 8
    domain.memory = 4096
    domain.cpus = 2
    domain.driver = "kvm"
  end

end
Note
I found that the VM will crash on boot if the config.vm.guest parameter is not configured. It looks like Vagrant tries to determine the guest OS type for use with post build provisioning tasks. Since we will not be performing any of that, statically configuring the value to something looks to disable the OS detection step.

Testing

Ok, with that all said and done lets vagrant up

cmd
vagrant up

# output

Bringing machine 'default' up with 'libvirt' provider...
==> default: Creating image (snapshot of base box volume).
==> default: Creating domain with the following settings...
==> default:  -- Name:              cisco-test_default
==> default:  -- Domain type:       kvm
==> default:  -- Cpus:              2
==> default:  -- Feature:           acpi
==> default:  -- Feature:           apic
==> default:  -- Feature:           pae
==> default:  -- Memory:            4096M
==> default:  -- Management MAC:
==> default:  -- Loader:
==> default:  -- Base box:          cisco/csr1000v
==> default:  -- Storage pool:      default
==> default:  -- Image:             /var/lib/libvirt/images/cisco-test_default.img (8G)
==> default:  -- Volume Cache:      default
==> default:  -- Kernel:
==> default:  -- Initrd:
==> default:  -- Graphics Type:     vnc
==> default:  -- Graphics Port:     5900
==> default:  -- Graphics IP:       127.0.0.1
==> default:  -- Graphics Password: Not defined
==> default:  -- Video Type:        cirrus
==> default:  -- Video VRAM:        9216
==> default:  -- Sound Type:
==> default:  -- Keymap:            en-us
==> default:  -- TPM Path:
==> default:  -- INPUT:             type=mouse, bus=ps2
==> default: Creating shared folders metadata...
==> default: Starting domain.
==> default: Waiting for domain to get an IP address...
==> default: Waiting for SSH to become available...
==> default: Configuring and enabling network interfaces...

Now SSH into csr1000v.

cmd
# from host shell

vagrant ssh

# now in csr1000v shell

csr#show inventory
NAME: "Chassis", DESCR: "Cisco CSR1000V Chassis"
PID: CSR1000V          , VID: V00, SN: 9NNML42HBX8

NAME: "module R0", DESCR: "Cisco CSR1000V Route Processor"
PID: CSR1000V          , VID: V00, SN: JAB1303001C

NAME: "module F0", DESCR: "Cisco CSR1000V Embedded Services Processor"
PID: CSR1000V          , VID:    , SN:

Good stuff our csr1000v is ready to go, finally clean up the box.

cmd
# from host shell

vagrant destroy -f

# output

==> default: Removing domain...

Outro

There you have it a Cisco csr1000v for use with the vagrant-libvirt provider.