OVN Manual Setup

I’m glad you found this page. If you are at all like I am, you are trying to figure out how to use this OVN thing.

You have a couple of choices, you can decide to use OpenStack or some other CMS or you can do it by hand, increasing your understanding of a complex system.

Installing OVN

There are two major OVN systems, the control system and the host system. We are starting with the control system.

$ sudo apt install ovn-central

This will install and do a basic configuration of the OVN Northbound and OVN Southbound databases. It should also give you the OVN tools. At the very least, you will need ovn-nbctl, and ovn-sbctl

With ovn-central installed, you can create your logical network. Just like a physical network, a logical network is made up of switches and routers.

If you are familiar with Cisco routers/switches, be aware that they are both L2 and L3 devices. When you create a Cisco VLAN, you can “attach” that VLAN to any port. All ports that are attached to the same VLAN act like they are on a switch, i.e., a L2 device.

If you have multiple VLANs within your Cisco router/switch attached to different ports, the device will act as a L3 router between those VLANs, assuming you have configured it to do routing.

In OVN, switches and routers are separate entities and need to be treated as such.

With the default settings in place, we are going to create our first logical switch:

# ovn-nbctl ls-add SW1

Ok, you have just purchased your first L2 only switch and plugged it into the wall. Wonderful. Now what?

We need to connect the switch. We will connect that switch to a router.


# ovn-nbctl lr-add R1
# ovn-nbctl lrp-add R1 R1-SW1 mac-addr ip4-addr/CIDR
# ovn-nbctl lsp-add SW1 SW1-R1
# ovn-nbctl lsp-set-type SW1-R1 router
# ovn-nbctl lsp-set-addresses SW1-R1 router
# ovn-nbctl lsp-set-options SW1-R1 router-port=R1-SW1

The first line, lr-add adds a logical router named R1. The names can be anything you want, I am using SW for switch and R for router.

Having created a router, we add a port to the router. The port must have a mac address and one or more IP addresses. Since you are defining a network, the IP address should also include the CIDR length

To make things easier, I have a script that generates unique MAC addresses for use. All with a 02 prefix.


#!/bin/bash
# genmac: returns an unique MAC address based on the text passed in.
#
if [ $# -lt 1 ] ; then
echo "Usage: $0 seed_or_name"
exit 1
fi
macaddr=$(echo $1|md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/')
echo $macaddr

This allows me to create MAC addresses at will and in the command line:


# ovn-nbctl lrp-add R1 R1-SW1 $(genmac RW-SW1) 172.17.1.1/24

Having created a router and router port, we need to “plug it into the switch”. We start by creating a port on the switch.

We then have to set the addresses for this port. In a physical switch, each port has one or more mac addresses associated with it. The switch discovers these MAC addresses by analyzing the traffic that comes into the port.

In OVN, we can explicitly set the MAC address or dynamically, or we can have the port discover its MAC address.

When we set the addresses of the switch port to “router”, we are telling the switch that it should take its MAC address from the router port.

We also need to set the type of the switch port to “router” and then we need to connect the switch port to the router port.


# ovn-nbctl lsp-set-options SW1 SW1-R1 router-port=R1-SW1

Another Database?

Yeah, everything you have done to this point is make database entries. Anything with “NB” in it is a reference to the Northbound database. “SB” is a reference to the Southbound database.

The NB database is where “logical” things are done. This is where you create switches, routers, routing, DHCP entries and many other emulations of physical things.

In designing these databases, it was recognized that there were things that could not be anticipated but which would happen. To resolve these issues, the tables have extra columns to store key-value pairs. In this example, the “option” column of the logical_switch_port table in the Northbound database for the row “SW1-R1” has the key-value pair of “router-port”-“R1-SW1”.

We will be touching on other databases a bit later.

Making it work!

But we really want to do something with this virtual network. This means that we need to somehow connect “physical” devices to the virtual devices. This is done with OpenVSwitch.

OpenVSwitch actual moves packets. We are not going to worry about it at this point. We just need to make it all work.

We start by installing the second half of the OVN system ovn-host.

ovn-host is responsible for installing OpenVSwitch and the daemons that connect the three different databases. Northbound, Southbound, and OpenVSwitch.

As soon as we install the host component, we will see that our system has some new interfaces.

$ ip addr show dev br-int

This is the integration bridge. This is where we will be attaching ports to instantiate them.

We will see something that looks like:


root@ovn2:~# ovs-vsctl show
f6cd1415-1ce9-4fca-9eb0-bedd01495a6f
Bridge br-int
fail_mode: secure
datapath_type: system
Port br-int
Interface br-int
type: internal
ovs_version: "3.3.0"

For our interfaces, we see:

root@ovn2:~# ip addr
1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: enp1s0: mtu 9000 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:38:58:ea brd ff:ff:ff:ff:ff:ff
inet 172.31.1.12/24 scope global enp1s0
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe38:58ea/64 scope link
valid_lft forever preferred_lft forever
3: ovs-system: mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 36:ce:93:07:5e:0c brd ff:ff:ff:ff:ff:ff
4: br-int: mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 4e:e2:58:2d:06:62 brd ff:ff:ff:ff:ff:ff

One of the first things to note is that the interface “br-int” is NOT the bridge “br-int”. It is the port “br-int” within the “br-int” bridge.

At this point, OVN and OpenVSwitch are not communicating with each other.

The daemon ovn-controller is responsible for interfacing between OpenVSwitch and the Southbound database. It gets its configuration from the OVS database.

ovn-controller needs to know how to create tunnels and how to speak to the Southbound database.

Configuring ovn-controller

The first thing that we need to do is to connect the OVS database with the Southbound database. We do that by setting the ovn-remote field in OVS.

There are three different communications methods used by OVN. Unix sockets, clear text TCP/IP and encrypted SSL. If we are going to only run OVN on a single node, we can use Unix sockets. If we want to connect across nodes, we have to use TCP or SSL.

We will follow the K.I.S.S. principal and use TCP. Later we will investigate using SSL.

By default, the Southbound database server listens on port 6642. Our node is 172.31.1.12. This means our connection string will be tcp:172.31.1.12:6642. If we have multiple database servers, we can add the other remotes, comma separated.
tcp:172.31.1.12:6642,tcp:172.31.1.13:6642 for example.

We are going to use unix:/var/run/ovn/ovnsb_db.sock because we are still a single node.

ovs-vsctl set Open_vSwitch . external_ids:ovn-remote=unix:/var/run/ovn/ovnsb_db.sock

“Open_vSwitch” is the name of the table. “.” is the name of the row. “external_ids” is the name of the column. “ovn-remote=unix:/var/run/ovn/ovnsb_db.sock” is the key value that we are setting.

We also need to tell the controller how to create bridges.

ovs-vsctl set open . external_ids:ovn-encap-type=geneve external_ids:ovn-encap-ip=172.31.1.12

Here we see some shortcuts allowed on ovsdb servers. We can shorten the name of the table to the shortest unique name. We can also perform more than one action on the same command. Here we are setting both ovn-encap-type and ovn-encap-ip.

The IP used here is the local IP address. This tells the OVN system where the tunnel terminates, on this node.

root@ovn2:~# ovn-sbctl show
Chassis "ceb53269-1027-4e6b-afb8-3272e0f76ad6"
hostname: ovn2
Encap geneve
ip: "172.31.1.12"
options: {csum="true"}

This says that we have one Chassis (node) in our OVN network.

Our First Interfaces


# ovn-nbctl lsp-add SW1 phy-1
# ovs-vsctl add-port br-int phy-1 -- set interface phy-1 type=internal external_ids:iface-id=phy-1

Here, we see the use of another shortcut on the ovs-vsctl command. We separate the “add-port” command from the “set” command.

We are creating a new port named “phy-1” on the logical switch. We also created an interface named “phy-1” on the “br-int” bridge.

We then directly access the database to set the interface type to “internal”. Then we pass a parameter from OVS to OVN. The name of the OVN interface (iface-id) is “phy-1”.

Having performed this action, we can see the results by looking at the Southbound database:


root@ovn2:~# ovn-sbctl show
Chassis "ceb53269-1027-4e6b-afb8-3272e0f76ad6"
hostname: ovn2
Encap geneve
ip: "172.31.1.12"
options: {csum="true"}
Port_Binding phy-1

We can see that the “phy-1” port has been bound to an interface on “ovn2”.

Let’s do that again with different names:


ovn-nbctl lsp-add SW1 port-2
ovs-vsctl add-port br-int phy-2 -- set interface phy-2 type=internal external_ids:iface-id=port-2

root@ovn2:~# ovn-sbctl show
Chassis "ceb53269-1027-4e6b-afb8-3272e0f76ad6"
hostname: ovn2
Encap geneve
ip: "172.31.1.12"
options: {csum="true"}
Port_Binding port-2
Port_Binding phy-1

We don’t want phy-2/port-2 so we will delete them.


ovn-nbctl lsp-del port-2
ovs-vsctl del-port br-int phy-2

We want an internal port that we can test against. So we will create and configure a port to ping.


ovn-nbctl lsp-add SW1 sub
ovn-nbctl lsp-set-addresses sub "$(genmac sub) 172.17.1.100"
root@ovn2:~# ovn-nbctl show
switch 5a008508-d220-475c-95d0-a54ee1fc9506 (SW1)
port sub
addresses: ["02:9c:13:4b:68:bd 172.17.1.100"]
port SW1-R1
type: router
router-port: R1-SW1
port phy-1
router 085d2308-70ce-4b89-8c94-1fa1d22b4de4 (R1)
port R1-SW1
mac: "02:fe:10:80:19:4d"
networks: ["172.17.1.1/24"]
root@ovn2:~# ovn-sbctl show
Chassis "ceb53269-1027-4e6b-afb8-3272e0f76ad6"
hostname: ovn2
Encap geneve
ip: "172.31.1.12"
options: {csum="true"}
Port_Binding phy-1

If you use `genmac` you’ll get the same MAC. “sub” is short for submarine. We will be “pinging” it.


root@ovn2:~# ping -c 2 172.17.1.1
PING 172.17.1.1 (172.17.1.1) 56(84) bytes of data.

--- 172.17.1.1 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1020ms

root@ovn2:~# ping -c 2 172.17.1.100
PING 172.17.1.100 (172.17.1.100) 56(84) bytes of data.

--- 172.17.1.100 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1049ms

Hmm, we can’t talk to the router, nor can we talk to the new port we just created. What’s going on?


root@ovn2:~# ip addr show dev phy-1
8: phy-1: mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 2e:83:2e:85:1c:87 brd ff:ff:ff:ff:ff:ff

We haven’t configured phy-1 yet. We need to configure both the logical switch port and the physical interface.


ovn-nbctl lsp-set-addresses phy-1 "2e:83:2e:85:1c:87 172.17.1.200"
ip addr add 172.17.1.200/24 dev phy-1
ip link set phy-1 up

Now we can test:


root@ovn2:~# ovn-nbctl show
switch 5a008508-d220-475c-95d0-a54ee1fc9506 (SW1)
port sub
addresses: ["02:9c:13:4b:68:bd 172.17.1.100"]
port SW1-R1
type: router
router-port: R1-SW1
port phy-1
addresses: ["2e:83:2e:85:1c:87 172.17.1.200"]
router 085d2308-70ce-4b89-8c94-1fa1d22b4de4 (R1)
port R1-SW1
mac: "02:fe:10:80:19:4d"
networks: ["172.17.1.1/24"]
root@ovn2:~# ip addr show dev phy-1
8: phy-1: mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
link/ether 2e:83:2e:85:1c:87 brd ff:ff:ff:ff:ff:ff
inet 172.17.1.200/24 scope global phy-1
valid_lft forever preferred_lft forever
inet6 fe80::2c83:2eff:fe85:1c87/64 scope link
valid_lft forever preferred_lft forever
root@ovn2:~# ping -c 2 172.17.1.200
PING 172.17.1.200 (172.17.1.200) 56(84) bytes of data.
64 bytes from 172.17.1.200: icmp_seq=1 ttl=64 time=0.072 ms
64 bytes from 172.17.1.200: icmp_seq=2 ttl=64 time=0.040 ms

--- 172.17.1.200 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1055ms
rtt min/avg/max/mdev = 0.040/0.056/0.072/0.016 ms
root@ovn2:~# ping -c 2 172.17.1.1
PING 172.17.1.1 (172.17.1.1) 56(84) bytes of data.
64 bytes from 172.17.1.1: icmp_seq=1 ttl=254 time=0.327 ms
64 bytes from 172.17.1.1: icmp_seq=2 ttl=254 time=0.387 ms

--- 172.17.1.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1012ms
rtt min/avg/max/mdev = 0.327/0.357/0.387/0.030 ms
root@ovn2:~# ping -c 2 172.17.1.100
PING 172.17.1.100 (172.17.1.100) 56(84) bytes of data.

--- 172.17.1.100 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1056ms

An important aspect of this test is that while we can’t talk to “sub” yet, we can talk to the router. But, we don’t talk through the router. This node, ovn2, is connected to the same logical switch.

In our next article, we’ll figure out why we can’t talk to “sub” and configure our OVN databases to use TCP instead of Unix sockets.