├── shared
├── .gitkeep
├── lesson02
│ ├── reset_vtep_db
│ ├── get_locator_uuid.json
│ ├── server1_topology.py
│ ├── server2_topology.py
│ ├── common.py
│ ├── server1_flows.txt
│ ├── attach_blue2.json
│ ├── attach_red2.json
│ └── ovs-vtep
├── lesson01
│ ├── _red2_.txt
│ ├── CREDITS
│ ├── server1_topology.py
│ ├── server2_topology.py
│ ├── common.py
│ ├── server1_flows.txt
│ ├── server2_flows.txt
│ └── commands.txt
├── lesson03
│ ├── reset_todo_db.sh
│ ├── chat.ovsschema
│ ├── reset_chat_db.sh
│ ├── stamps
│ │ ├── templates
│ │ │ ├── view.html
│ │ │ └── index.html
│ │ ├── reset_stamps_db.sh
│ │ ├── stamps.ovsschema
│ │ ├── main.py
│ │ └── seed_stamps.py
│ ├── sender.py
│ ├── todo.ovsschema
│ ├── receiver.py
│ └── ovsdb_client.py
└── lesson04
│ └── start_guests
├── puppet
└── manifests
│ ├── site.pp
│ ├── nodes
│ ├── internet.pp
│ ├── server1.pp
│ └── server2.pp
│ ├── nodes.pp
│ └── nodes.pp.build_from_source
├── .gitignore
├── LICENSE
├── README.txt
└── Vagrantfile
/shared/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/puppet/manifests/site.pp:
--------------------------------------------------------------------------------
1 | import 'nodes.pp'
2 |
--------------------------------------------------------------------------------
/puppet/manifests/nodes/internet.pp:
--------------------------------------------------------------------------------
1 | node internet inherits basenode {}
2 |
--------------------------------------------------------------------------------
/puppet/manifests/nodes/server1.pp:
--------------------------------------------------------------------------------
1 | node server1 inherits servernode {}
2 |
--------------------------------------------------------------------------------
/puppet/manifests/nodes/server2.pp:
--------------------------------------------------------------------------------
1 | node server2 inherits servernode {}
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vagrant
2 | .DS_Store
3 | shared/ovs.zip
4 | shared/ovs/*.*
5 | *.pyc
6 |
--------------------------------------------------------------------------------
/shared/lesson02/reset_vtep_db:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | ovs-appctl -t ovs-vswitchd exit
4 | ovs-appctl -t ovsdb-server exit
5 | rm /etc/openvswitch/vtep.db
6 | /etc/init.d/openvswitch-switch start
7 |
--------------------------------------------------------------------------------
/shared/lesson01/_red2_.txt:
--------------------------------------------------------------------------------
1 | 9147
2 | tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
3 | listening on red2-eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
4 |
--------------------------------------------------------------------------------
/shared/lesson01/CREDITS:
--------------------------------------------------------------------------------
1 | This lesson is adapted from David Mahler's introductory video
2 | titled "VXLAN overlay networks with Open vSwitch" which is
3 | available at https://www.youtube.com/watch?v=tnSkHhsLqpM
4 |
--------------------------------------------------------------------------------
/shared/lesson02/get_locator_uuid.json:
--------------------------------------------------------------------------------
1 | {
2 | "method": "transact",
3 | "params": [
4 | "hardware_vtep",
5 | {
6 | "op": "select",
7 | "table": "Physical_Locator",
8 | "columns": ["_uuid"],
9 | "where": [
10 | ["dst_ip", "==", "192.168.1.10"]
11 | ]
12 | }
13 | ],
14 | "id": 11
15 | }
16 |
--------------------------------------------------------------------------------
/shared/lesson03/reset_todo_db.sh:
--------------------------------------------------------------------------------
1 | ovs-appctl -t ovsdb-server exit
2 |
3 | if [ ! -d "/etc/lesson03" ]; then
4 | mkdir /etc/lesson03
5 | else
6 | rm -f /etc/lesson03/todo.db
7 | fi
8 |
9 | ovsdb-tool create /etc/lesson03/todo.db /vagrant/shared/lesson03/todo.ovsschema
10 | ovsdb-server --pidfile --detach --log-file --remote punix:/var/run/openvswitch/db.sock /etc/lesson03/todo.db
11 | ovs-appctl -t ovsdb-server ovsdb-server/add-remote ptcp:6640
12 |
--------------------------------------------------------------------------------
/shared/lesson03/chat.ovsschema:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chat",
3 | "version": "1.0.0",
4 | "cksum": "1234567 891011",
5 | "tables": {
6 | "Message": {
7 | "columns": {
8 | "sender": {
9 | "type": "string"
10 | },
11 | "message": {
12 | "type": "string"
13 | }
14 | },
15 | "isRoot": true
16 | }
17 | }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/shared/lesson03/reset_chat_db.sh:
--------------------------------------------------------------------------------
1 | ovs-appctl -t ovsdb-server exit
2 |
3 | if [ ! -d "/etc/lesson03" ]; then
4 | mkdir /etc/lesson03
5 | else
6 | rm -f /etc/lesson03/chat.db
7 | fi
8 |
9 | ovsdb-tool create /etc/lesson03/chat.db /vagrant/shared/lesson03/chat.ovsschema
10 | ovsdb-server --pidfile --detach --log-file --remote punix:/var/run/openvswitch/db.sock /etc/lesson03/chat.db
11 | ovs-appctl -t ovsdb-server ovsdb-server/add-remote ptcp:6640
12 | ovs-appctl -t ovsdb-server vlog/set dbg
13 |
--------------------------------------------------------------------------------
/shared/lesson01/server1_topology.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | from common import createTopology
4 |
5 |
6 | createTopology(
7 | 's1',
8 | [
9 | [
10 | 'red1',
11 | {
12 | 'ip': '10.0.0.1/8',
13 | 'mac': '00:00:00:00:aa:01'
14 | }
15 | ],
16 | [
17 | 'blue1',
18 | {
19 | 'ip': '10.0.0.1/8',
20 | 'mac': '00:00:00:00:aa:01'
21 | }
22 | ]
23 | ]
24 | )
25 |
--------------------------------------------------------------------------------
/shared/lesson01/server2_topology.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | from common import createTopology
4 |
5 |
6 | createTopology(
7 | 's2',
8 | [
9 | [
10 | 'red2',
11 | {
12 | 'ip': '10.0.0.2/8',
13 | 'mac': '00:00:00:00:aa:02'
14 | }
15 | ],
16 | [
17 | 'blue2',
18 | {
19 | 'ip': '10.0.0.2/8',
20 | 'mac': '00:00:00:00:aa:02'
21 | }
22 | ]
23 | ]
24 | )
25 |
--------------------------------------------------------------------------------
/shared/lesson02/server1_topology.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | from common import createTopology
4 |
5 |
6 | createTopology(
7 | 's1',
8 | [
9 | [
10 | 'red1',
11 | {
12 | 'ip': '10.0.0.1/8',
13 | 'mac': '00:00:00:00:aa:01'
14 | }
15 | ],
16 | [
17 | 'blue1',
18 | {
19 | 'ip': '10.0.0.1/8',
20 | 'mac': '00:00:00:00:aa:01'
21 | }
22 | ]
23 | ]
24 | )
25 |
--------------------------------------------------------------------------------
/shared/lesson02/server2_topology.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | from common import createTopology
4 |
5 |
6 | createTopology(
7 | 's2',
8 | [
9 | [
10 | 'red2',
11 | {
12 | 'ip': '10.0.0.2/8',
13 | 'mac': '00:00:00:00:aa:02'
14 | }
15 | ],
16 | [
17 | 'blue2',
18 | {
19 | 'ip': '10.0.0.2/8',
20 | 'mac': '00:00:00:00:aa:02'
21 | }
22 | ]
23 | ]
24 | )
25 |
--------------------------------------------------------------------------------
/shared/lesson03/stamps/templates/view.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Open vStamps
5 |
6 |
7 | OVSDB: Open vStamps Database
8 |
9 |
{{ stamp['name'] }}
10 |

11 |
12 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/shared/lesson03/stamps/reset_stamps_db.sh:
--------------------------------------------------------------------------------
1 | ovs-appctl -t ovsdb-server exit
2 |
3 | if [ ! -d "/etc/lesson03" ]; then
4 | mkdir /etc/lesson03
5 | else
6 | rm -f /etc/lesson03/stamps.db
7 | fi
8 |
9 | ovsdb-tool create /etc/lesson03/stamps.db /vagrant/shared/lesson03/stamps/stamps.ovsschema
10 | ovsdb-server --pidfile --detach --log-file --remote punix:/var/run/openvswitch/db.sock /etc/lesson03/stamps.db
11 | ovs-appctl -t ovsdb-server ovsdb-server/add-remote ptcp:6640
12 | ovs-appctl -t ovsdb-server vlog/set dbg
13 | python seed_stamps.py
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2014 Mark S. Maglana
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/shared/lesson01/common.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | from mininet.cli import CLI
4 | from mininet.net import Mininet
5 | from mininet.log import setLogLevel
6 | from mininet.topo import Topo
7 | from mininet.util import dumpNodeConnections
8 |
9 |
10 | def createTopology(switch, hosts):
11 | setLogLevel('info')
12 | topo = Topo()
13 | switch = topo.addSwitch(switch)
14 |
15 | for (hostname, opts) in hosts:
16 | host = topo.addHost(hostname, **opts)
17 | topo.addLink(host, switch)
18 |
19 | network = Mininet(topo, controller=None)
20 | network.start()
21 | print "*** Dumping host connections"
22 | dumpNodeConnections(network.hosts)
23 | CLI(network)
24 | network.stop()
25 |
--------------------------------------------------------------------------------
/shared/lesson02/common.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | from mininet.cli import CLI
4 | from mininet.net import Mininet
5 | from mininet.log import setLogLevel
6 | from mininet.topo import Topo
7 | from mininet.util import dumpNodeConnections
8 |
9 |
10 | def createTopology(switch, hosts):
11 | setLogLevel('info')
12 | topo = Topo()
13 | switch = topo.addSwitch(switch)
14 |
15 | for (hostname, opts) in hosts:
16 | host = topo.addHost(hostname, **opts)
17 | topo.addLink(host, switch, None)
18 |
19 | network = Mininet(topo, controller=None)
20 | network.start()
21 | print "*** Dumping host connections"
22 | dumpNodeConnections(network.hosts)
23 | CLI(network)
24 | network.stop()
25 |
--------------------------------------------------------------------------------
/shared/lesson01/server1_flows.txt:
--------------------------------------------------------------------------------
1 | table=0,in_port=1,actions=set_field:100->tun_id,resubmit(,1)
2 | table=0,in_port=2,actions=set_field:200->tun_id,resubmit(,1)
3 | table=0,actions=resubmit(,1)
4 |
5 | table=1,tun_id=100,dl_dst=00:00:00:00:aa:01,actions=output:1
6 | table=1,tun_id=200,dl_dst=00:00:00:00:aa:01,actions=output:2
7 | table=1,tun_id=100,dl_dst=00:00:00:00:aa:02,actions=output:10
8 | table=1,tun_id=200,dl_dst=00:00:00:00:aa:02,actions=output:10
9 |
10 | table=1,tun_id=100,arp,nw_dst=10.0.0.1,actions=output:1
11 | table=1,tun_id=200,arp,nw_dst=10.0.0.1,actions=output:2
12 | table=1,tun_id=100,arp,nw_dst=10.0.0.2,actions=output:10
13 | table=1,tun_id=200,arp,nw_dst=10.0.0.2,actions=output:10
14 | table=1,priority=100,actions=drop
15 |
--------------------------------------------------------------------------------
/shared/lesson01/server2_flows.txt:
--------------------------------------------------------------------------------
1 | table=0,in_port=1,actions=set_field:100->tun_id,resubmit(,1)
2 | table=0,in_port=2,actions=set_field:200->tun_id,resubmit(,1)
3 | table=0,actions=resubmit(,1)
4 |
5 | table=1,tun_id=100,dl_dst=00:00:00:00:aa:02,actions=output:1
6 | table=1,tun_id=200,dl_dst=00:00:00:00:aa:02,actions=output:2
7 | table=1,tun_id=100,dl_dst=00:00:00:00:aa:01,actions=output:10
8 | table=1,tun_id=200,dl_dst=00:00:00:00:aa:01,actions=output:10
9 |
10 | table=1,tun_id=100,arp,nw_dst=10.0.0.2,actions=output:1
11 | table=1,tun_id=200,arp,nw_dst=10.0.0.2,actions=output:2
12 | table=1,tun_id=100,arp,nw_dst=10.0.0.1,actions=output:10
13 | table=1,tun_id=200,arp,nw_dst=10.0.0.1,actions=output:10
14 | table=1,priority=100,actions=drop
15 |
--------------------------------------------------------------------------------
/shared/lesson02/server1_flows.txt:
--------------------------------------------------------------------------------
1 | table=0,in_port=1,actions=set_field:100->tun_id,resubmit(,1)
2 | table=0,in_port=2,actions=set_field:200->tun_id,resubmit(,1)
3 | table=0,actions=resubmit(,1)
4 |
5 | table=1,tun_id=100,dl_dst=00:00:00:00:aa:01,actions=output:1
6 | table=1,tun_id=200,dl_dst=00:00:00:00:aa:01,actions=output:2
7 | table=1,tun_id=100,dl_dst=00:00:00:00:aa:02,actions=output:10
8 | table=1,tun_id=200,dl_dst=00:00:00:00:aa:02,actions=output:10
9 |
10 | table=1,tun_id=100,arp,nw_dst=10.0.0.1,actions=output:1
11 | table=1,tun_id=200,arp,nw_dst=10.0.0.1,actions=output:2
12 | table=1,tun_id=100,arp,nw_dst=10.0.0.2,actions=output:10
13 | table=1,tun_id=200,arp,nw_dst=10.0.0.2,actions=output:10
14 | table=1,priority=100,actions=drop
15 |
--------------------------------------------------------------------------------
/shared/lesson03/sender.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from ovsdb_client import OVSDBClient
3 |
4 | nickname = sys.argv[2]
5 | message_id = 0
6 |
7 | while True:
8 | message = raw_input('Your message: ')
9 |
10 | client = OVSDBClient(sys.argv[1], 6640)
11 | message_id += 1
12 |
13 | response = client.request({
14 | 'id': message_id,
15 | 'method': 'transact',
16 | 'params': [
17 | 'chat',
18 | {
19 | 'op': 'insert',
20 | 'table': 'Message',
21 | 'row': {
22 | 'sender': nickname,
23 | 'message': message
24 | }
25 | }
26 | ]
27 | })
28 |
29 | if response and response['error']:
30 | raise "Could not send message!"
31 |
--------------------------------------------------------------------------------
/shared/lesson03/stamps/stamps.ovsschema:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stamps",
3 | "version": "1.0.0",
4 | "cksum": "1234567 891011",
5 | "tables": {
6 | "Stamp": {
7 | "columns": {
8 | "name": {
9 | "type": "string"
10 | },
11 | "url": {
12 | "type": "string"
13 | },
14 | "categories": {
15 | "type": {
16 | "max": "unlimited",
17 | "min": 0,
18 | "key": {
19 | "type": "string"
20 | }
21 | }
22 | }
23 | },
24 | "isRoot": true,
25 | "indexes": [
26 | ["name"]
27 | ]
28 | }
29 | }
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/shared/lesson03/todo.ovsschema:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todo",
3 | "version": "1.0.0",
4 | "cksum": "1234567 891011",
5 | "tables": {
6 | "List": {
7 | "columns": {
8 | "name": {
9 | "type": "string"
10 | },
11 | "items": {
12 | "type": {
13 | "key": {
14 | "type": "uuid",
15 | "refTable": "Item"
16 | },
17 | "min": 0,
18 | "max": "unlimited"
19 | }
20 | }
21 | },
22 | "isRoot": true,
23 | "indexes": [
24 | ["name"]
25 | ]
26 | },
27 | "Item": {
28 | "columns": {
29 | "status": {
30 | "type": "string"
31 | },
32 | "description": {
33 | "type": "string"
34 | }
35 | }
36 | }
37 | }
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/shared/lesson03/stamps/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Open vStamps
5 |
6 |
7 | OVSDB: Open vStamps Database
8 |
9 |
10 | | Thumbnail |
11 | Source |
12 | Categories |
13 | Actions |
14 |
15 | {% for stamp in stamps %}
16 |
17 |  |
18 | {{ stamp['url'][0:50] }}... |
19 |
20 |
21 | {% if stamp['categories'][0] == 'set' %}
22 | {% for category in stamp['categories'][1] %}
23 | - {{ category }}
24 | {% endfor %}
25 | {% else %}
26 | - {{ stamp['categories'] }}
27 | {% endif %}
28 |
29 | |
30 | View |
31 |
32 | {% endfor %}
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/shared/lesson01/commands.txt:
--------------------------------------------------------------------------------
1 | RUN IN SERVER 1
2 | ===============
3 |
4 | sudo su -
5 |
6 | cd /vagrant/shared/lesson01
7 |
8 | ./server1_topology.py
9 |
10 | sh ovs-vsctl add-port s1 vtep -- set interface vtep type=vxlan option:remote_ip=192.168.2.20 option:key=flow ofport_request=10
11 |
12 | sh ovs-vsctl show
13 |
14 | sh ovs-ofctl show s1
15 |
16 | sh ovs-ofctl add-flows s1 server1_flows.txt
17 |
18 |
19 |
20 | RUN IN SERVER 2
21 | ===============
22 |
23 | sudo su -
24 |
25 | cd /vagrant/shared/lesson01
26 |
27 | ./server2_topology.py
28 |
29 | sh ovs-vsctl add-port s2 vtep -- set interface vtep type=vxlan option:remote_ip=192.168.1.10 option:key=flow ofport_request=10
30 |
31 | sh ovs-vsctl show
32 |
33 | sh ovs-ofctl show s2
34 |
35 | sh ovs-ofctl add-flows s2 server2_flows.txt
36 |
37 |
38 |
39 | RUN IN SERVER 1
40 | ===============
41 |
42 | red1 ping 10.0.0.2
43 |
44 |
45 |
46 | RUN IN SERVER 2
47 | ===============
48 |
49 | red2 ifconfig red2-eth0 down
50 |
51 | (red1 should stop receiving ping replies at this point)
52 |
53 | red2 ifconfig red2-eth0 up
54 |
55 | (red1 should resume receiving ping replies)
56 |
--------------------------------------------------------------------------------
/shared/lesson03/receiver.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from ovsdb_client import OVSDBClient
3 |
4 | client = OVSDBClient(sys.argv[1], 6640)
5 |
6 | print "Connecting to chat room..."
7 |
8 | response = client.request({
9 | 'id': 1,
10 | 'method': 'monitor',
11 | 'params': [
12 | 'chat',
13 | 1,
14 | {
15 | 'Message': {}
16 | }
17 | ]
18 | })
19 |
20 | if response['error']:
21 | raise "Could not connect to chat room!"
22 | else:
23 | print "Connected."
24 |
25 | while True:
26 | # Check for messages from server
27 | message = None
28 | try:
29 | message = client.receive()
30 | except OVSDBClient.ReceiveTimeoutError:
31 | pass
32 |
33 | # Skip the rest if no message received
34 | if not message:
35 | continue
36 |
37 | # Respond to inactivity probe from OVSDB
38 | if message['method'] == 'echo':
39 | print "(OVSDB) PING?"
40 | client.send({
41 | 'id': message['id'],
42 | 'error': None,
43 | 'result': message['params']
44 | })
45 | print "PONG!"
46 |
47 | # Extract message from update notification
48 | if message['method'] == 'update':
49 | row = message['params'][1]['Message'].values()[0]['new']
50 | print "({}) {}".format(row['sender'], row['message'])
51 |
--------------------------------------------------------------------------------
/shared/lesson03/stamps/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import os
4 | import sys
5 |
6 | from flask import Flask, render_template, request
7 |
8 | sys.path.append(os.path.abspath(os.path.join(__file__, '../..')))
9 |
10 | from ovsdb_client import OVSDBClient
11 |
12 | app = Flask(__name__)
13 |
14 |
15 | @app.route('/')
16 | def index():
17 | client = OVSDBClient('localhost', 6640)
18 |
19 | query = {
20 | 'id': 1,
21 | 'method': 'transact',
22 | 'params': [
23 | 'stamps',
24 | {
25 | 'op': 'select',
26 | 'table': 'Stamp',
27 | 'where': []
28 | }
29 | ]
30 | }
31 |
32 | response = client.request(query)
33 | return render_template('index.html', stamps=response['result'][0]['rows'])
34 |
35 |
36 | @app.route('/view')
37 | def view():
38 | client = OVSDBClient('localhost', 6640)
39 |
40 | query = {
41 | 'id': 2,
42 | 'method': 'transact',
43 | 'params': [
44 | 'stamps',
45 | {
46 | 'op': 'select',
47 | 'table': 'Stamp',
48 | 'where': [
49 | ['_uuid', '==', ['uuid', request.args.get('id')]]
50 | ]
51 | }
52 | ]
53 | }
54 |
55 | response = client.request(query)
56 | return render_template('view.html', stamp=response['result'][0]['rows'][0])
57 |
58 |
59 | if __name__ == "__main__":
60 | app.debug = True
61 | app.run(host='192.168.101.10', port=8080)
62 |
--------------------------------------------------------------------------------
/shared/lesson02/attach_blue2.json:
--------------------------------------------------------------------------------
1 | {
2 | "method": "transact",
3 | "params": [
4 | "hardware_vtep",
5 | {
6 | "op": "insert",
7 | "table": "Logical_Switch",
8 | "row": {
9 | "name": "blue-network",
10 | "tunnel_key": 200
11 | },
12 | "uuid-name": "BLUENETWORK"
13 | },
14 | {
15 | "op": "insert",
16 | "table": "Ucast_Macs_Remote",
17 | "row": {
18 | "logical_switch": ["named-uuid", "BLUENETWORK"],
19 | "MAC": "00:00:00:00:aa:01",
20 | "locator": [
21 | "set",
22 | [
23 | ["uuid", "***REPLACE WITH UUID OF 192.168.1.10***"]
24 | ]
25 | ]
26 | },
27 | "uuid-name": "BLUE1"
28 | },
29 | {
30 | "op": "mutate",
31 | "table": "Physical_Port",
32 | "where": [
33 | ["name", "==", "s2-eth2"]
34 | ],
35 | "mutations": [
36 | [
37 | "vlan_bindings",
38 | "delete",
39 | [
40 | "set",
41 | [
42 | 0
43 | ]
44 | ]
45 | ],
46 | [
47 | "vlan_bindings",
48 | "insert",
49 | [
50 | "map",
51 | [
52 | [
53 | 0,
54 | ["named-uuid", "BLUENETWORK"]
55 | ]
56 | ]
57 | ]
58 | ]
59 | ]
60 | }
61 | ],
62 | "id": 10
63 | }
64 |
--------------------------------------------------------------------------------
/shared/lesson03/stamps/seed_stamps.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 | sys.path.append(os.path.abspath(os.path.join(__file__, '../..')))
5 |
6 | from ovsdb_client import OVSDBClient
7 |
8 | stamps = [
9 | {
10 | 'name': 'Forever',
11 | 'url': 'http://www.spellman.org/images/forever_stamp.jpg',
12 | 'categories': [
13 | 'historic'
14 | ]
15 | },
16 |
17 | {
18 | 'name': 'Aborigine',
19 | 'url': 'http://www.smh.com.au/ffxImage/'
20 | 'urlpicture_id_1023864658551_2002/06/28/nat_stamp29.jpg',
21 | 'categories': [
22 | 'historic',
23 | 'australia'
24 | ]
25 | },
26 |
27 | {
28 | 'name': 'Amur Tiger Cub',
29 | 'url': 'http://goodnature.nathab.com/'
30 | 'wp-content/uploads/2011/10/Tiger-face1.png',
31 | 'categories': [
32 | 'animals'
33 | ]
34 | },
35 |
36 | {
37 | 'name': 'Spiderman',
38 | 'url': 'http://a.abcnews.go.com/images/Entertainment/'
39 | 'ap_comicstamp_01_ssv.jpg',
40 | 'categories': [
41 | 'cartoons'
42 | ]
43 | },
44 |
45 | {
46 | 'name': 'Homer Simpson',
47 | 'url': 'http://s08214.biss.wikispaces.net/file/'
48 | 'view/homer-stamp-14.jpg/125737775/homer-stamp-14.jpg',
49 | 'categories': [
50 | 'cartoons'
51 | ]
52 | }
53 | ]
54 |
55 | operations = ['stamps']
56 |
57 | for stamp in stamps:
58 | operations.append({
59 | 'op': 'insert',
60 | 'table': 'Stamp',
61 | 'row': {
62 | 'name': stamp['name'],
63 | 'url': stamp['url'],
64 | 'categories': [
65 | 'set',
66 | stamp['categories']
67 | ]
68 | }
69 | })
70 |
71 | client = OVSDBClient('localhost', 6640)
72 |
73 | response = client.request({
74 | 'id': 1,
75 | 'method': 'transact',
76 | 'params': operations
77 | })
78 |
79 |
80 | if response['error'] is None:
81 | print "Stamps database seeded"
82 | else:
83 | print response
84 |
--------------------------------------------------------------------------------
/shared/lesson02/attach_red2.json:
--------------------------------------------------------------------------------
1 | {
2 | "method": "transact",
3 | "params": [
4 | "hardware_vtep",
5 | {
6 | "op": "insert",
7 | "table": "Logical_Switch",
8 | "row": {
9 | "name": "red-network",
10 | "tunnel_key": 100
11 | },
12 | "uuid-name": "REDNETWORK"
13 | },
14 | {
15 | "op": "insert",
16 | "table": "Physical_Locator",
17 | "row": {
18 | "dst_ip": "192.168.1.10",
19 | "encapsulation_type": "vxlan_over_ipv4"
20 | },
21 | "uuid-name": "SERVER1"
22 | },
23 | {
24 | "op": "insert",
25 | "table": "Ucast_Macs_Remote",
26 | "row": {
27 | "logical_switch": ["named-uuid", "REDNETWORK"],
28 | "MAC": "00:00:00:00:aa:01",
29 | "locator": [
30 | "set",
31 | [
32 | ["named-uuid", "SERVER1"]
33 | ]
34 | ]
35 | },
36 | "uuid-name": "RED1"
37 | },
38 | {
39 | "op": "mutate",
40 | "table": "Physical_Port",
41 | "where": [
42 | ["name", "==", "s2-eth1"]
43 | ],
44 | "mutations": [
45 | [
46 | "vlan_bindings",
47 | "delete",
48 | [
49 | "set",
50 | [
51 | 0
52 | ]
53 | ]
54 | ],
55 | [
56 | "vlan_bindings",
57 | "insert",
58 | [
59 | "map",
60 | [
61 | [
62 | 0,
63 | ["named-uuid", "REDNETWORK"]
64 | ]
65 | ]
66 | ]
67 | ]
68 | ]
69 | }
70 | ],
71 | "id": 10
72 | }
73 |
--------------------------------------------------------------------------------
/README.txt:
--------------------------------------------------------------------------------
1 | Open vSwitch Lab
2 | ================
3 |
4 | This is a companion project to the Open vSwitch series of articles at
5 | http://www.relaxdiego.com.
6 |
7 |
8 | Requirements
9 | ------------
10 |
11 | 1. VirtualBox. Install from https://www.virtualbox.org/wiki/Downloads
12 |
13 | 2. Vagrant v1.6.3 or above. Install from http://www.vagrantup.com/downloads.html
14 |
15 | 3. The vagrant-vbguest plugin. Install with `vagrant plugin install vagrant-vbguest`
16 |
17 |
18 | Installation
19 | ------------
20 |
21 | Estimated time for the following steps including automated provisioning:
22 | 30 minutes. Time will vary depending on your connection speed.
23 |
24 | 1. Clone this repo and cd to it
25 |
26 | 2. Run `vagrant up`
27 |
28 |
29 | Whoa! What Just Happened???
30 | ---------------------------
31 |
32 | You just created two servers that can talk to each other over the "Internet!"
33 |
34 | +-----------+ +-----------+
35 | | |192.168.1.10/24 +------------+ 192.168.2.20/24| |
36 | | server1 |-------------------| "INTERNET" |-------------------| server2 |
37 | | | +------------+ | |
38 | +-----------+ +-----------+
39 |
40 | The two servers are Ubuntu VMs with mininet and OVS 2.x installed while the
41 | "Internet" is really a VM with IP forwarding enabled. All networks above are
42 | implemented as VirtualBox internal networks which are not directly accessible
43 | from anywhere else including your local machine. However, you can SSH to any
44 | of the three VMs using `vagrant ssh`. For example: `vagrant ssh internet`, or
45 | `vagrant ssh server1` and then get to the internal networks from there.
46 |
47 |
48 | Stopping and starting the VMs
49 | -----------------------------
50 |
51 | To suspend your VMs, run `vagrant suspend`. To shut them down, run
52 | `vagrant halt`.
53 |
54 |
55 | Viewing VM status
56 | -----------------
57 |
58 | `vagrant status`
59 |
60 |
61 | Troubleshooting
62 | ---------------
63 |
64 | PROBLEM: I'm getting an error about Vagrant not able to compile nokogiri
65 |
66 | SOLUTION: You're missing developer tools needed to locally compile binaries
67 | needed by nokogiri. In OS X, install the OS X Developer Tools for your OS X
68 | version.
69 |
--------------------------------------------------------------------------------
/puppet/manifests/nodes.pp:
--------------------------------------------------------------------------------
1 | node basenode {
2 |
3 | exec { "apt_get_update":
4 | command => "/usr/bin/apt-get -y update"
5 | }
6 |
7 | # Ensure the above command runs before we attempt
8 | # to install any package in other manifests
9 | Exec["apt_get_update"] -> Package<| |>
10 |
11 | package { "unzip":
12 | ensure => installed,
13 | }
14 |
15 | exec { "download_ovs":
16 | command => "/usr/bin/wget https://www.dropbox.com/sh/k5t492id6hc6ynf/AAA2GzeOowsf7Kdjv6eC5Yfca?dl=1 -O /vagrant/shared/ovs.zip",
17 | cwd => "/root",
18 | creates => "/vagrant/shared/ovs.zip",
19 | }
20 |
21 | exec { "extract_ovs":
22 | command => "/usr/bin/unzip /vagrant/shared/ovs.zip -d /vagrant/shared/ovs",
23 | cwd => "/vagrant/shared",
24 | require => [
25 | Package["unzip"],
26 | Exec["download_ovs"],
27 | ],
28 | returns => [0, 2],
29 | creates => "/vagrant/shared/ovs/openvswitch-common_2.3.0-1_amd64.deb",
30 | }
31 |
32 | package { "ovs_common":
33 | name => "openvswitch-common",
34 | ensure => installed,
35 | provider => dpkg,
36 | source => "/vagrant/shared/ovs/openvswitch-common_2.3.0-1_amd64.deb",
37 | require => [
38 | Exec["extract_ovs"],
39 | ]
40 | }
41 |
42 | package { "ovs_switch":
43 | name => "openvswitch-switch",
44 | ensure => installed,
45 | provider => dpkg,
46 | source => "/vagrant/shared/ovs/openvswitch-switch_2.3.0-1_amd64.deb",
47 | require => [
48 | Package["ovs_common"],
49 | ],
50 | }
51 |
52 | }
53 |
54 |
55 |
56 | node servernode inherits basenode {
57 |
58 | package { "ovs_python":
59 | name => "python-openvswitch",
60 | ensure => installed,
61 | provider => dpkg,
62 | source => "/vagrant/shared/ovs/python-openvswitch_2.3.0-1_all.deb",
63 | require => [
64 | Package["ovs_common"],
65 | ],
66 | }
67 |
68 | package { "ovs_vtep":
69 | name => "openvswitch-vtep",
70 | ensure => installed,
71 | provider => dpkg,
72 | source => "/vagrant/shared/ovs/openvswitch-vtep_2.3.0-1_amd64.deb",
73 | require => [
74 | Package["ovs_python"],
75 | ],
76 | }
77 |
78 | package { "mininet":
79 | ensure => installed,
80 | require => [
81 | Package["ovs_switch"],
82 | ]
83 | }
84 |
85 | }
86 |
87 |
88 |
89 | import 'nodes/*.pp'
90 |
--------------------------------------------------------------------------------
/shared/lesson04/start_guests:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import argparse
4 | import json
5 | from mininet.cli import CLI
6 | from mininet.net import Mininet
7 | from mininet.log import setLogLevel
8 | from mininet.topo import Topo
9 | from mininet.util import dumpNodeConnections
10 | import uuid
11 |
12 |
13 | def initialize(switch_id,
14 | number_of_guests=10):
15 |
16 | setLogLevel('info')
17 |
18 | # Build the topology
19 | topo = Topo()
20 | switch_name = "br{}".format(switch_id)
21 | switch = topo.addSwitch(switch_name)
22 |
23 | for number in range(1, number_of_guests + 1):
24 | guest_name = "g{}".format(number, switch_id)
25 | ip = "192.168.{}.{}/24".format(number, switch_id)
26 | mac = "00:00:00:00:{:02x}:{:02x}".format(number, switch_id)
27 | host = topo.addHost(name=guest_name, ip=ip, mac=mac)
28 | topo.addLink(host, switch, None)
29 |
30 | # Instantiate the network using above topology
31 | network = Mininet(topo, controller=None)
32 | network.start()
33 |
34 | # Configure the switch with settings required by NSX
35 | switch = network.switches[0]
36 | switch.cmd("ovs-vsctl br-set-external-id "
37 | "{0} bridge-id {0}".format(switch_name))
38 | switch.cmd("ovs-vsctl set Bridge {0} "
39 | "fail-mode=standalone".format(switch_name))
40 |
41 | # Set the external ids of each bridge port so that NSX
42 | # will recognize them as virtual interfaces
43 | for host in network.hosts:
44 | br_intf_name = host.connectionsTo(switch)[0][1].name
45 | ops = json.dumps([
46 | 'Open_vSwitch',
47 | {
48 | 'op': 'select',
49 | 'table': 'Interface',
50 | 'where': [['name', '==', br_intf_name]],
51 | 'columns': ['_uuid']
52 | }
53 | ])
54 | response = switch.cmd("ovsdb-client transact '{}'".format(ops))
55 | br_intf_uuid = json.loads(response)[0]['rows'][0]['_uuid'][1]
56 |
57 | external_ids = {
58 | 'attached-mac': host.MAC(),
59 | 'iface-id': uuid.uuid4(),
60 | 'iface-status': 'active',
61 | 'vm-id': uuid.uuid4()
62 | }
63 |
64 | for key, value in external_ids.items():
65 | switch.cmd("ovs-vsctl set Interface {} external_ids:{}={}"
66 | .format(br_intf_uuid, key, value)
67 | )
68 |
69 | print "*** Host connections:"
70 | dumpNodeConnections(network.hosts)
71 | CLI(network)
72 | network.stop()
73 |
74 | if __name__ == "__main__":
75 | parser = argparse.ArgumentParser(
76 | description='Creates a mininet topology of 1 bridge and X guests')
77 |
78 | parser.add_argument('-s', '--switch-id', required=True,
79 | dest='switch_id', metavar='NUMBER', action='store',
80 | help='A numeric identifier for the switch')
81 | parser.add_argument('-g', '--guests', required=False,
82 | dest='number_of_guests', metavar='NUMBER',
83 | action='store', default=10,
84 | help='The number of guests to spawn. Default is 10')
85 |
86 | args = parser.parse_args()
87 |
88 | initialize(switch_id=int(args.switch_id),
89 | number_of_guests=int(args.number_of_guests))
90 |
--------------------------------------------------------------------------------
/puppet/manifests/nodes.pp.build_from_source:
--------------------------------------------------------------------------------
1 | node basenode {
2 |
3 | exec { "apt_get_update":
4 | command => "/usr/bin/apt-get -y update"
5 | }
6 |
7 | # Ensure the above command runs before we attempt
8 | # to install any package in other manifests
9 | Exec["apt_get_update"] -> Package<| |>
10 |
11 | $base_packages = [
12 | "git",
13 | "autoconf",
14 | "automake1.10",
15 | "build-essential",
16 | "debhelper",
17 | "fakeroot",
18 | "libffi-dev",
19 | "libssl-dev",
20 | "libtool",
21 | "pkg-config",
22 | "python-all",
23 | "python-qt4",
24 | "python-zopeinterface",
25 | "python-twisted-conch"
26 | ]
27 |
28 | package { $base_packages:
29 | ensure => installed,
30 | }
31 |
32 | exec { "download_ovs":
33 | command => "/usr/bin/wget https://github.com/openvswitch/ovs/archive/v2.3.tar.gz -O /root/ovs.tar.gz",
34 | cwd => "/root",
35 | creates => "/root/ovs.tar.gz",
36 | }
37 |
38 | file { "/root/ovs":
39 | ensure => directory,
40 | }
41 |
42 | exec { "extract_ovs":
43 | command => "/bin/tar xvfz ovs.tar.gz -C /root/ovs --strip-components=1",
44 | cwd => "/root",
45 | require => [
46 | Exec["download_ovs"],
47 | ],
48 | creates => "/root/ovs/README",
49 | }
50 |
51 | exec { "build_ovs":
52 | command => "/usr/bin/fakeroot debian/rules binary",
53 | environment => "DEB_BUILD_OPTIONS='parallel=8 nocheck'",
54 | cwd => "/root/ovs",
55 | logoutput => true,
56 | loglevel => verbose,
57 | timeout => 0,
58 | creates => "/root/openvswitch-common_2.3.0-1_amd64.deb",
59 | require => [
60 | Package["build-essential"],
61 | Package["fakeroot"],
62 | Package[$base_packages],
63 | Exec["extract_ovs"],
64 | ],
65 | }
66 |
67 | package { "ovs_common":
68 | name => "openvswitch-common",
69 | ensure => installed,
70 | provider => dpkg,
71 | source => "/root/openvswitch-common_2.3.0-1_amd64.deb?dl=0",
72 | require => [ Exec["build_ovs"] ],
73 | }
74 |
75 | package { "ovs_switch":
76 | name => "openvswitch-switch",
77 | ensure => installed,
78 | provider => dpkg,
79 | source => "/root/openvswitch-switch_2.3.0-1_amd64.deb",
80 | require => [ Package["ovs_common"] ],
81 | }
82 |
83 | }
84 |
85 |
86 |
87 | node servernode inherits basenode {
88 |
89 | package { "ovs_python":
90 | name => "python-openvswitch",
91 | ensure => installed,
92 | provider => dpkg,
93 | source => "/root/python-openvswitch_2.3.0-1_all.deb",
94 | require => [ Package["ovs_switch"] ],
95 | }
96 |
97 | package { "ovs_vtep":
98 | name => "openvswitch-vtep",
99 | ensure => installed,
100 | provider => dpkg,
101 | source => "/root/openvswitch-vtep_2.3.0-1_amd64.deb",
102 | require => [ Package["ovs_python"] ],
103 | }
104 |
105 | package { "mininet":
106 | ensure => installed,
107 | require => [
108 | Package["ovs_switch"],
109 | ]
110 | }
111 |
112 | }
113 |
114 |
115 |
116 | import 'nodes/*.pp'
117 |
--------------------------------------------------------------------------------
/shared/lesson03/ovsdb_client.py:
--------------------------------------------------------------------------------
1 | import json
2 | import select
3 | import socket
4 | import time
5 |
6 |
7 | class OVSDBClient(object):
8 |
9 | # ===========
10 | # EXCEPTIONS
11 | # ===========
12 |
13 | class ConnectionError(Exception):
14 |
15 | def __init__(self, hostname, port, errno, message):
16 | message = "Unable to connect to %s:%s. More info: (%i) %s" \
17 | % (hostname, port, errno, message)
18 |
19 | super(self.__class__, self).__init__(message)
20 |
21 | class ReceiveTimeoutError(Exception):
22 |
23 | def __init__(self, timeout, buff):
24 | if not buff:
25 | buff = ""
26 | message = "Did not get a notification after %i seconds. " \
27 | "Data received so far: %s" % (timeout, buff)
28 |
29 | super(self.__class__, self).__init__(message)
30 |
31 | class RequestTimeoutError(Exception):
32 |
33 | def __init__(self, formatted_json, timeout, buff):
34 | if not buff:
35 | buff = ""
36 | message = "Request timed out after %i seconds. Request " \
37 | "details: %s. Data received so far: %s" \
38 | % (timeout, formatted_json, buff)
39 |
40 | super(self.__class__, self).__init__(message)
41 |
42 | # ===========
43 | # INITIALIZER
44 | # ===========
45 |
46 | def __init__(self, hostname=None, port=None, request_timeout=5):
47 | self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
48 |
49 | try:
50 | self._socket.connect((hostname, port))
51 | except socket.error as (errno, message):
52 | raise OVSDBClient.ConnectionError(hostname, port, errno, message)
53 |
54 | self._request_timeout = request_timeout
55 |
56 | # =================
57 | # PUBLIC PROPERTIES
58 | # =================
59 |
60 | @property
61 | def buffer(self, max_chunk_size=12288):
62 | try:
63 | ready_to_read, ready_to_write, in_error = \
64 | select.select([self._socket], [], [], 0)
65 | if self._socket in ready_to_read:
66 | self._buffer += self._socket.recv(max_chunk_size)
67 | else:
68 | self._buffer += ""
69 | except socket.error:
70 | self._buffer += ""
71 |
72 | return self._buffer
73 |
74 | # ===============
75 | # PUBLIC METHODS
76 | # ===============
77 |
78 | def receive(self):
79 | self._flush_buffer()
80 | message = ""
81 | start = time.time()
82 |
83 | while len(message) == 0 or message.count("}") < message.count("{"):
84 | message = self.buffer
85 | duration = time.time() - start
86 | if duration > self._request_timeout:
87 | raise OVSDBClient.ReceiveTimeoutError(duration, message)
88 |
89 | return json.loads(message)
90 |
91 | def request(self, dict):
92 | message = json.dumps(dict)
93 | self._flush_buffer()
94 |
95 | self._socket.send(message)
96 |
97 | response = ""
98 | start = time.time()
99 |
100 | while len(response) == 0 or \
101 | response.count("}") < response.count("{"):
102 | response = self.buffer
103 | duration = time.time() - start
104 | if duration > self._request_timeout:
105 | raise OVSDBClient.RequestTimeoutError(
106 | message, duration, response)
107 |
108 | return json.loads(response)
109 |
110 | def send(self, dict):
111 | self._flush_buffer()
112 | self._socket.send(json.dumps(dict))
113 |
114 | # ================
115 | # PRIVATE METHODS
116 | # ================
117 |
118 | def _flush_buffer(self):
119 | self._buffer = ""
120 |
--------------------------------------------------------------------------------
/Vagrantfile:
--------------------------------------------------------------------------------
1 | # -*- mode: ruby -*-
2 | # vi: set ft=ruby :
3 |
4 | VAGRANTFILE_API_VERSION = "2"
5 |
6 | # NOTE: Netmask is assumed 255.255.255.0 for all
7 |
8 | SERVER1_TUNNEL_IP = "192.168.1.10"
9 | SERVER1_TUNNEL_GATEWAY = "192.168.1.254"
10 | SERVER1_MGMT_IP = "192.168.101.10"
11 |
12 | SERVER2_TUNNEL_IP = "192.168.2.20"
13 | SERVER2_TUNNEL_GATEWAY = "192.168.2.254"
14 | SERVER2_MGMT_IP = "192.168.101.20"
15 |
16 | INTERNET_MGMT_IP = "192.168.101.254"
17 |
18 |
19 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
20 | config.vm.box = "ubuntu/trusty64"
21 |
22 |
23 | # "INTERNET"
24 | config.vm.define "internet" do |server|
25 | server.vm.hostname = "internet"
26 |
27 | # Server 1 tunnel transport network
28 | server.vm.network "private_network",
29 | virtualbox__intnet: "server1_net",
30 | ip: SERVER1_TUNNEL_GATEWAY,
31 | netmask: "255.255.255.0"
32 |
33 | # Server 2 tunnel transport network
34 | server.vm.network "private_network",
35 | virtualbox__intnet: "server2_net",
36 | ip: SERVER2_TUNNEL_GATEWAY,
37 | netmask: "255.255.255.0"
38 |
39 | # Management/Control network
40 | server.vm.network "private_network",
41 | ip: INTERNET_MGMT_IP,
42 | netmask: "255.255.255.0"
43 |
44 | server.vm.provider "virtualbox" do |vb|
45 | vb.customize ["modifyvm", :id, "--memory", 256]
46 | vb.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/v-root", "1"]
47 | end
48 |
49 | server.vm.provision "shell", inline: <<-SCRIPT
50 | [ "$(sysctl --values net.ipv4.ip_forward)" -eq "1" ] || {
51 | # Persist across reboots
52 | echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf # enable ip4 forwarding
53 | sysctl -p # apply settings from /etc/sysctl.conf
54 | }
55 | SCRIPT
56 |
57 | server.vm.provision "puppet" do |puppet|
58 | puppet.manifests_path = "puppet/manifests"
59 | puppet.manifest_file = "site.pp"
60 | puppet.options = "--verbose --debug"
61 | end
62 | end
63 |
64 |
65 | # SERVER 1
66 | config.vm.define "server1" do |server|
67 | server.vm.hostname = "server1"
68 |
69 | # Tunnel transport network
70 | server.vm.network "private_network",
71 | virtualbox__intnet: "server1_net",
72 | ip: SERVER1_TUNNEL_IP,
73 | netmask: "255.255.255.0",
74 | auto_config: false
75 |
76 | # Provision tunnel transport interface via file, so we can
77 | # add a persistent static route to tunnel gateway
78 | server.vm.provision "shell", inline: <<-SCRIPT
79 | [ -e /etc/network/interfaces.d/eth1.cfg ] || {
80 | cat << EOT >/etc/network/interfaces.d/eth1.cfg
81 | auto eth1
82 | iface eth1 inet static
83 | address #{SERVER1_TUNNEL_IP}
84 | netmask 255.255.255.0
85 | up ip route add #{SERVER2_TUNNEL_IP}/32 via #{SERVER1_TUNNEL_GATEWAY} dev eth1
86 |
87 | EOT
88 | ifup eth1
89 | }
90 | SCRIPT
91 |
92 | # Management/Control network
93 | server.vm.network "private_network",
94 | ip: SERVER1_MGMT_IP,
95 | netmask: "255.255.255.0"
96 |
97 | server.vm.provider "virtualbox" do |vb|
98 | vb.customize ["modifyvm", :id, "--memory", 256]
99 | vb.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/v-root", "1"]
100 | end
101 |
102 | server.vm.provision "puppet" do |puppet|
103 | puppet.manifests_path = "puppet/manifests"
104 | puppet.manifest_file = "site.pp"
105 | puppet.options = "--verbose --debug"
106 | end
107 | end
108 |
109 |
110 | # SERVER 2
111 | config.vm.define "server2" do |server|
112 | server.vm.hostname = "server2"
113 |
114 | # Tunnel transport network
115 | server.vm.network "private_network",
116 | virtualbox__intnet: "server2_net",
117 | ip: SERVER2_TUNNEL_IP,
118 | netmask: "255.255.255.0",
119 | auto_config: false
120 |
121 | # Provision tunnel transport interface via file, so we can
122 | # add a persistent static route to tunnel gateway
123 | server.vm.provision "shell", inline: <<-SCRIPT
124 | [ -e /etc/network/interfaces.d/eth1.cfg ] || {
125 | cat << EOT >/etc/network/interfaces.d/eth1.cfg
126 | auto eth1
127 | iface eth1 inet static
128 | address #{SERVER2_TUNNEL_IP}
129 | netmask 255.255.255.0
130 | up ip route add #{SERVER1_TUNNEL_IP}/32 via #{SERVER2_TUNNEL_GATEWAY} dev eth1
131 |
132 | EOT
133 | ifup eth1
134 | }
135 | SCRIPT
136 |
137 | # Management/Control network
138 | server.vm.network "private_network",
139 | ip: SERVER2_MGMT_IP,
140 | netmask: "255.255.255.0"
141 |
142 | server.vm.provider "virtualbox" do |vb|
143 | vb.customize ["modifyvm", :id, "--memory", 256]
144 | vb.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/v-root", "1"]
145 | end
146 |
147 | server.vm.provision "puppet" do |puppet|
148 | puppet.manifests_path = "puppet/manifests"
149 | puppet.manifest_file = "site.pp"
150 | puppet.options = "--verbose --debug"
151 | end
152 | end
153 |
154 |
155 | end
156 |
--------------------------------------------------------------------------------
/shared/lesson02/ovs-vtep:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # Copyright (C) 2013 Nicira, Inc. All Rights Reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at:
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | # Limitations:
17 | # - Doesn't support multicast other than "unknown-dst"
18 |
19 | # NOTE: This is a modifed version of https://github.com/openvswitch/ovs/blob/master/vtep/ovs-vtep
20 |
21 | import argparse
22 | import re
23 | import subprocess
24 | import sys
25 | import time
26 | import types
27 |
28 | import ovs.dirs
29 | import ovs.util
30 | import ovs.daemon
31 | import ovs.unixctl.server
32 | import ovs.vlog
33 |
34 |
35 | VERSION = "0.99"
36 |
37 | root_prefix = ""
38 |
39 | __pychecker__ = 'no-reuseattr' # Remove in pychecker >= 0.8.19.
40 | vlog = ovs.vlog.Vlog("ovs-vtep")
41 | exiting = False
42 |
43 | Tunnel_Ip = ""
44 | Lswitches = {}
45 | Bindings = {}
46 | ls_count = 0
47 | tun_id = 0
48 |
49 | def call_prog(prog, args_list):
50 | cmd = [prog, "-vconsole:off"] + args_list
51 | output = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()
52 | if len(output) == 0 or output[0] == None:
53 | output = ""
54 | else:
55 | output = output[0].strip()
56 | return output
57 |
58 | def ovs_vsctl(args):
59 | return call_prog("ovs-vsctl", args.split())
60 |
61 | def ovs_ofctl(args):
62 | return call_prog("ovs-ofctl", args.split())
63 |
64 | def vtep_ctl(args):
65 | return call_prog("vtep-ctl", args.split())
66 |
67 |
68 | def unixctl_exit(conn, unused_argv, unused_aux):
69 | global exiting
70 | exiting = True
71 | conn.reply(None)
72 |
73 |
74 | class Logical_Switch(object):
75 | def __init__(self, ls_name):
76 | global ls_count
77 | self.name = ls_name
78 | ls_count += 1
79 | self.short_name = "vtep_ls" + str(ls_count)
80 | vlog.info("creating lswitch %s (%s)" % (self.name, self.short_name))
81 | self.ports = {}
82 | self.tunnels = {}
83 | self.local_macs = set()
84 | self.remote_macs = {}
85 | self.unknown_dsts = set()
86 | self.tunnel_key = 0
87 | self.setup_ls()
88 |
89 | def __del__(self):
90 | vlog.info("destroying lswitch %s" % self.name)
91 |
92 | def setup_ls(self):
93 | column = vtep_ctl("--columns=tunnel_key find logical_switch "
94 | "name=%s" % self.name)
95 | tunnel_key = column.partition(":")[2].strip()
96 | if (tunnel_key and type(eval(tunnel_key)) == types.IntType):
97 | self.tunnel_key = tunnel_key
98 | vlog.info("using tunnel key %s in %s"
99 | % (self.tunnel_key, self.name))
100 | else:
101 | self.tunnel_key = 0
102 | vlog.warn("invalid tunnel key for %s, using 0" % self.name)
103 |
104 | ovs_vsctl("--may-exist add-br %s" % self.short_name)
105 | ovs_vsctl("br-set-external-id %s vtep_logical_switch true"
106 | % self.short_name)
107 | ovs_vsctl("br-set-external-id %s logical_switch_name %s"
108 | % (self.short_name, self.name))
109 |
110 | vtep_ctl("clear-local-macs %s" % self.name)
111 | vtep_ctl("add-mcast-local %s unknown-dst %s" % (self.name, Tunnel_Ip))
112 |
113 | ovs_ofctl("del-flows %s" % self.short_name)
114 | ovs_ofctl("add-flow %s priority=0,action=drop" % self.short_name)
115 |
116 | def update_flood(self):
117 | flood_ports = self.ports.values()
118 |
119 | # Traffic flowing from one 'unknown-dst' should not be flooded to
120 | # port belonging to another 'unknown-dst'.
121 | for tunnel in self.unknown_dsts:
122 | port_no = self.tunnels[tunnel][0]
123 | ovs_ofctl("add-flow %s table=1,priority=1,in_port=%s,action=%s"
124 | % (self.short_name, port_no, ",".join(flood_ports)))
125 |
126 | # Traffic coming from a VTEP physical port should only be flooded to
127 | # one 'unknown-dst' and to all other physical ports that belong to that
128 | # VTEP device and this logical switch.
129 | for tunnel in self.unknown_dsts:
130 | port_no = self.tunnels[tunnel][0]
131 | flood_ports.append(port_no)
132 | break
133 |
134 | ovs_ofctl("add-flow %s table=1,priority=0,action=%s"
135 | % (self.short_name, ",".join(flood_ports)))
136 |
137 | def add_lbinding(self, lbinding):
138 | vlog.info("adding %s binding to %s" % (lbinding, self.name))
139 | port_no = ovs_vsctl("get Interface %s ofport" % lbinding)
140 | self.ports[lbinding] = port_no
141 | ovs_ofctl("add-flow %s in_port=%s,action=learn(table=1,"
142 | "priority=1000,idle_timeout=15,cookie=0x5000,"
143 | "NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],"
144 | "output:NXM_OF_IN_PORT[]),resubmit(,1)"
145 | % (self.short_name, port_no))
146 |
147 | self.update_flood()
148 |
149 | def del_lbinding(self, lbinding):
150 | vlog.info("removing %s binding from %s" % (lbinding, self.name))
151 | port_no = self.ports[lbinding]
152 | ovs_ofctl("del-flows %s in_port=%s" % (self.short_name, port_no));
153 | del self.ports[lbinding]
154 | self.update_flood()
155 |
156 | def add_tunnel(self, tunnel):
157 | global tun_id
158 | vlog.info("adding tunnel %s" % tunnel)
159 | encap, ip = tunnel.split("/")
160 |
161 | if encap != "vxlan_over_ipv4":
162 | vlog.warn("unsupported tunnel format %s" % encap)
163 | return
164 |
165 | tun_id += 1
166 | tun_name = "vx" + str(tun_id)
167 |
168 | ovs_vsctl("add-port %s %s -- set Interface %s type=vxlan "
169 | "options:key=%s options:remote_ip=%s"
170 | % (self.short_name, tun_name, tun_name, self.tunnel_key, ip))
171 |
172 | for i in range(10):
173 | port_no = ovs_vsctl("get Interface %s ofport" % tun_name)
174 | if port_no != "-1":
175 | break
176 | elif i == 9:
177 | vlog.warn("couldn't create tunnel %s" % tunnel)
178 | ovs_vsctl("del-port %s %s" % (self.short_name, tun_name))
179 | return
180 |
181 | # Give the system a moment to allocate the port number
182 | time.sleep(0.5)
183 |
184 | self.tunnels[tunnel] = (port_no, tun_name)
185 |
186 | ovs_ofctl("add-flow %s table=0,priority=1000,in_port=%s,"
187 | "actions=resubmit(,1)"
188 | % (self.short_name, port_no))
189 |
190 | def del_tunnel(self, tunnel):
191 | vlog.info("removing tunnel %s" % tunnel)
192 |
193 | port_no, tun_name = self.tunnels[tunnel]
194 | ovs_ofctl("del-flows %s table=0,in_port=%s"
195 | % (self.short_name, port_no))
196 | ovs_vsctl("del-port %s %s" % (self.short_name, tun_name))
197 |
198 | del self.tunnels[tunnel]
199 |
200 | def update_local_macs(self):
201 | flows = ovs_ofctl("dump-flows %s cookie=0x5000/-1,table=1"
202 | % self.short_name).splitlines()
203 | macs = set()
204 | for f in flows:
205 | mac = re.split(r'.*dl_dst=(.*) .*', f)
206 | if len(mac) == 3:
207 | macs.add(mac[1])
208 |
209 | for mac in macs.difference(self.local_macs):
210 | vlog.info("adding local ucast %s to %s" % (mac, self.name))
211 | vtep_ctl("add-ucast-local %s %s %s" % (self.name, mac, Tunnel_Ip))
212 |
213 | for mac in self.local_macs.difference(macs):
214 | vlog.info("removing local ucast %s from %s" % (mac, self.name))
215 | vtep_ctl("del-ucast-local %s %s" % (self.name, mac))
216 |
217 | self.local_macs = macs
218 |
219 | def add_remote_mac(self, mac, tunnel):
220 | port_no = self.tunnels.get(tunnel, (0,""))[0]
221 | if not port_no:
222 | return
223 |
224 | ovs_ofctl("add-flow %s table=1,priority=1000,dl_dst=%s,action=%s"
225 | % (self.short_name, mac, port_no))
226 |
227 | def del_remote_mac(self, mac):
228 | ovs_ofctl("del-flows %s table=1,dl_dst=%s" % (self.short_name, mac))
229 |
230 | def update_remote_macs(self):
231 | remote_macs = {}
232 | unknown_dsts = set()
233 | tunnels = set()
234 | parse_ucast = True
235 |
236 | mac_list = vtep_ctl("list-remote-macs %s" % self.name).splitlines()
237 | for line in mac_list:
238 | if (line.find("mcast-mac-remote") != -1):
239 | parse_ucast = False
240 | continue
241 |
242 | entry = re.split(r' (.*) -> (.*)', line)
243 | if len(entry) != 4:
244 | continue
245 |
246 | if parse_ucast:
247 | remote_macs[entry[1]] = entry[2]
248 | else:
249 | if entry[1] != "unknown-dst":
250 | continue
251 |
252 | unknown_dsts.add(entry[2])
253 |
254 | tunnels.add(entry[2])
255 |
256 | old_tunnels = set(self.tunnels.keys())
257 |
258 | for tunnel in tunnels.difference(old_tunnels):
259 | self.add_tunnel(tunnel)
260 |
261 | for tunnel in old_tunnels.difference(tunnels):
262 | self.del_tunnel(tunnel)
263 |
264 | for mac in remote_macs.keys():
265 | if (self.remote_macs.get(mac) != remote_macs[mac]):
266 | self.add_remote_mac(mac, remote_macs[mac])
267 |
268 | for mac in self.remote_macs.keys():
269 | if not remote_macs.has_key(mac):
270 | self.del_remote_mac(mac)
271 |
272 | self.remote_macs = remote_macs
273 |
274 | if (self.unknown_dsts != unknown_dsts):
275 | self.unknown_dsts = unknown_dsts
276 | self.update_flood()
277 |
278 | def update_stats(self):
279 | # Map Open_vSwitch's "interface:statistics" to columns of
280 | # vtep's logical_binding_stats. Since we are using the 'interface' from
281 | # the logical switch to collect stats, packets transmitted from it
282 | # is received in the physical switch and vice versa.
283 | stats_map = {'tx_packets':'packets_to_local',
284 | 'tx_bytes':'bytes_to_local',
285 | 'rx_packets':'packets_from_local',
286 | 'rx_bytes':'bytes_from_local'}
287 |
288 | # Go through all the logical switch's interfaces that end with "-l"
289 | # and copy the statistics to logical_binding_stats.
290 | for interface in self.ports.iterkeys():
291 | if not interface.endswith("--l"):
292 | continue
293 | vlan, pp_name, logical = interface.split("--")
294 | uuid = vtep_ctl("get physical_port %s vlan_stats:%s"
295 | % (pp_name, vlan))
296 | if not uuid:
297 | continue
298 |
299 | for (mapfrom, mapto) in stats_map.iteritems():
300 | value = ovs_vsctl("get interface %s statistics:%s"
301 | % (interface, mapfrom)).strip('"')
302 | vtep_ctl("set logical_binding_stats %s %s=%s"
303 | % (uuid, mapto, value))
304 |
305 | def run(self):
306 | self.update_local_macs()
307 | self.update_remote_macs()
308 | self.update_stats()
309 |
310 | def add_binding(ps_name, binding, ls):
311 | vlog.info("adding binding %s" % binding)
312 |
313 | vlan, pp_name = binding.split("--")
314 | pbinding = binding+"--p"
315 | lbinding = binding+"--l"
316 |
317 | # Create a patch port that connects the VLAN+port to the lswitch.
318 | # Do them as two separate calls so if one side already exists, the
319 | # other side is created.
320 | ovs_vsctl("add-port %s %s "
321 | " -- set Interface %s type=patch options:peer=%s"
322 | % (ps_name, pbinding, pbinding, lbinding))
323 | ovs_vsctl("add-port %s %s "
324 | " -- set Interface %s type=patch options:peer=%s"
325 | % (ls.short_name, lbinding, lbinding, pbinding))
326 |
327 | port_no = ovs_vsctl("get Interface %s ofport" % pp_name)
328 | patch_no = ovs_vsctl("get Interface %s ofport" % pbinding)
329 | vlan_ = vlan.lstrip('0')
330 | if vlan_:
331 | ovs_ofctl("add-flow %s in_port=%s,dl_vlan=%s,action=strip_vlan,%s"
332 | % (ps_name, port_no, vlan_, patch_no))
333 | ovs_ofctl("add-flow %s in_port=%s,action=mod_vlan_vid:%s,%s"
334 | % (ps_name, patch_no, vlan_, port_no))
335 | else:
336 | ovs_ofctl("add-flow %s in_port=%s,action=%s"
337 | % (ps_name, port_no, patch_no))
338 | ovs_ofctl("add-flow %s in_port=%s,action=%s"
339 | % (ps_name, patch_no, port_no))
340 |
341 | # Create a logical_bindings_stats record.
342 | if not vlan_:
343 | vlan_ = "0"
344 | vtep_ctl("set physical_port %s vlan_stats:%s=@stats --\
345 | --id=@stats create logical_binding_stats packets_from_local=0"\
346 | % (pp_name, vlan_))
347 |
348 | ls.add_lbinding(lbinding)
349 | Bindings[binding] = ls.name
350 |
351 | def del_binding(ps_name, binding, ls):
352 | vlog.info("removing binding %s" % binding)
353 |
354 | vlan, pp_name = binding.split("--")
355 | pbinding = binding+"--p"
356 | lbinding = binding+"--l"
357 |
358 | port_no = ovs_vsctl("get Interface %s ofport" % pp_name)
359 | patch_no = ovs_vsctl("get Interface %s ofport" % pbinding)
360 | vlan_ = vlan.lstrip('0')
361 | if vlan_:
362 | ovs_ofctl("del-flows %s in_port=%s,dl_vlan=%s"
363 | % (ps_name, port_no, vlan_))
364 | ovs_ofctl("del-flows %s in_port=%s" % (ps_name, patch_no))
365 | else:
366 | ovs_ofctl("del-flows %s in_port=%s" % (ps_name, port_no))
367 | ovs_ofctl("del-flows %s in_port=%s" % (ps_name, patch_no))
368 |
369 | ls.del_lbinding(lbinding)
370 |
371 | # Destroy the patch port that connects the VLAN+port to the lswitch
372 | ovs_vsctl("del-port %s %s -- del-port %s %s"
373 | % (ps_name, pbinding, ls.short_name, lbinding))
374 |
375 | # Remove the record that links vlan with stats in logical_binding_stats.
376 | vtep_ctl("remove physical_port %s vlan_stats %s" % (pp_name, vlan))
377 |
378 | del Bindings[binding]
379 |
380 | def handle_physical(ps_name):
381 | # Gather physical ports except the patch ports we created
382 | ovs_ports = ovs_vsctl("list-ports %s" % ps_name).split()
383 | ovs_port_set = set([port for port in ovs_ports if port[-2:] != "-p"])
384 |
385 | vtep_pp_set = set(vtep_ctl("list-ports %s" % ps_name).split())
386 |
387 | for pp_name in ovs_port_set.difference(vtep_pp_set):
388 | vlog.info("adding %s to %s" % (pp_name, ps_name))
389 | vtep_ctl("add-port %s %s" % (ps_name, pp_name))
390 |
391 | for pp_name in vtep_pp_set.difference(ovs_port_set):
392 | vlog.info("deleting %s from %s" % (pp_name, ps_name))
393 | vtep_ctl("del-port %s %s" % (ps_name, pp_name))
394 |
395 | new_bindings = set()
396 | for pp_name in vtep_pp_set:
397 | binding_set = set(vtep_ctl("list-bindings %s %s"
398 | % (ps_name, pp_name)).splitlines())
399 |
400 | for b in binding_set:
401 | vlan, ls_name = b.split()
402 | if ls_name not in Lswitches:
403 | Lswitches[ls_name] = Logical_Switch(ls_name)
404 |
405 | binding = "%s--%s" % (vlan, pp_name)
406 | ls = Lswitches[ls_name]
407 | new_bindings.add(binding)
408 |
409 | if Bindings.has_key(binding):
410 | if Bindings[binding] == ls_name:
411 | continue
412 | else:
413 | del_binding(ps_name, binding, Lswitches[Bindings[binding]])
414 |
415 | add_binding(ps_name, binding, ls)
416 |
417 |
418 | dead_bindings = set(Bindings.keys()).difference(new_bindings)
419 | for binding in dead_bindings:
420 | ls_name = Bindings[binding]
421 | ls = Lswitches[ls_name]
422 |
423 | del_binding(ps_name, binding, ls)
424 |
425 | if not len(ls.ports):
426 | ovs_vsctl("del-br %s" % Lswitches[ls_name].short_name)
427 | del Lswitches[ls_name]
428 |
429 | def setup(ps_name):
430 | br_list = ovs_vsctl("list-br").split()
431 | if (ps_name not in br_list):
432 | ovs.util.ovs_fatal(0, "couldn't find OVS bridge %s" % ps_name, vlog)
433 |
434 | call_prog("vtep-ctl", ["set", "physical_switch", ps_name,
435 | 'description="OVS VTEP Emulator"'])
436 |
437 | tunnel_ips = vtep_ctl("get physical_switch %s tunnel_ips"
438 | % ps_name).strip('[]"').split(", ")
439 | if len(tunnel_ips) != 1 or not tunnel_ips[0]:
440 | ovs.util.ovs_fatal(0, "exactly one 'tunnel_ips' should be set", vlog)
441 |
442 | global Tunnel_Ip
443 | Tunnel_Ip = tunnel_ips[0]
444 |
445 | ovs_ofctl("del-flows %s" % ps_name)
446 |
447 | # Remove any logical bridges from the previous run
448 | for br in br_list:
449 | if ovs_vsctl("br-get-external-id %s vtep_logical_switch"
450 | % br) == "true":
451 | # Remove the remote side of any logical switch
452 | ovs_ports = ovs_vsctl("list-ports %s" % br).split()
453 | for port in ovs_ports:
454 | port_type = ovs_vsctl("get Interface %s type"
455 | % port).strip('"')
456 | if port_type != "patch":
457 | continue
458 |
459 | peer = ovs_vsctl("get Interface %s options:peer"
460 | % port).strip('"')
461 | if (peer):
462 | ovs_vsctl("del-port %s" % peer)
463 |
464 | ovs_vsctl("del-br %s" % br)
465 |
466 |
467 | def main():
468 | parser = argparse.ArgumentParser()
469 | parser.add_argument("ps_name", metavar="PS-NAME",
470 | help="Name of physical switch.")
471 | parser.add_argument("--root-prefix", metavar="DIR",
472 | help="Use DIR as alternate root directory"
473 | " (for testing).")
474 | parser.add_argument("--version", action="version",
475 | version="%s %s" % (ovs.util.PROGRAM_NAME, VERSION))
476 |
477 | ovs.vlog.add_args(parser)
478 | ovs.daemon.add_args(parser)
479 | args = parser.parse_args()
480 | ovs.vlog.handle_args(args)
481 | ovs.daemon.handle_args(args)
482 |
483 | global root_prefix
484 | if args.root_prefix:
485 | root_prefix = args.root_prefix
486 |
487 | ps_name = args.ps_name
488 |
489 | ovs.daemon.daemonize()
490 |
491 | ovs.unixctl.command_register("exit", "", 0, 0, unixctl_exit, None)
492 | error, unixctl = ovs.unixctl.server.UnixctlServer.create(None,
493 | version=VERSION)
494 | if error:
495 | ovs.util.ovs_fatal(error, "could not create unixctl server", vlog)
496 |
497 | setup(ps_name)
498 |
499 | while True:
500 | unixctl.run()
501 | if exiting:
502 | break
503 |
504 | handle_physical(ps_name)
505 |
506 | for ls_name, ls in Lswitches.items():
507 | ls.run()
508 |
509 | poller = ovs.poller.Poller()
510 | unixctl.wait(poller)
511 | poller.timer_wait(1000)
512 | poller.block()
513 |
514 | unixctl.close()
515 |
516 | if __name__ == '__main__':
517 | try:
518 | main()
519 | except SystemExit:
520 | # Let system.exit() calls complete normally
521 | raise
522 | except:
523 | vlog.exception("traceback")
524 | sys.exit(ovs.daemon.RESTART_EXIT_CODE)
525 |
--------------------------------------------------------------------------------