Add example SFC Heat template to docs
Change-Id: Ibe38fb829d1467f84d9b4bddf1a50787298f86eb
This commit is contained in:
327
doc/source/sfc-example/sfc-example.yaml
Normal file
327
doc/source/sfc-example/sfc-example.yaml
Normal file
@@ -0,0 +1,327 @@
|
||||
heat_template_version: 2015-04-30
|
||||
|
||||
description: |
|
||||
SFC example deployment
|
||||
The script deploys 2 Fedora VMs:
|
||||
* A VM with a UDP echo server, that listens on port 2345, and replies any
|
||||
any datagram it receives back to the sender.
|
||||
* A VM acting as a service function, that receives all port 2345 UDP packets
|
||||
originating from the first VM, and replaces all instances of sf_filter
|
||||
with sf_sub.
|
||||
|
||||
How to deploy:
|
||||
$ openstack stack create -t doc/source/sfc-example/sfc-example.yaml stackname
|
||||
Wait a few minutes
|
||||
$ openstack stack show stackname
|
||||
Look for server_fip address
|
||||
e.g.:
|
||||
server_fip=$(openstack stack show -f yaml stackname |
|
||||
shyaml get-value outputs.0.output_value)
|
||||
$ echo dragonflow | nc -u $server_fip 2345
|
||||
DRAGONFLOW
|
||||
|
||||
The service function VM needs a few minutes to install dependencies.
|
||||
|
||||
parameters:
|
||||
key_name:
|
||||
type: string
|
||||
label: Keypair name
|
||||
default: stack
|
||||
image_id:
|
||||
type: string
|
||||
label: Image ID
|
||||
default: Fedora-Cloud-Base-25-1.3.x86_64
|
||||
provider_net:
|
||||
type: string
|
||||
label: Provider net to use
|
||||
default: public
|
||||
sf_filter:
|
||||
type: string
|
||||
label: Filter to look for in returned messages
|
||||
default: dragonflow
|
||||
sf_sub:
|
||||
type: string
|
||||
label: The text to plug instead of filtered messages
|
||||
default: DRAGONFLOW
|
||||
|
||||
resources:
|
||||
flavor:
|
||||
type: OS::Nova::Flavor
|
||||
properties:
|
||||
name: sfc-test-flavor
|
||||
disk: 3
|
||||
ram: 1024
|
||||
vcpus: 1
|
||||
|
||||
private_net:
|
||||
type: OS::Neutron::Net
|
||||
properties:
|
||||
name: sfc-test-net
|
||||
|
||||
private_subnet:
|
||||
type: OS::Neutron::Subnet
|
||||
properties:
|
||||
name: sfc-test-subnet
|
||||
network_id: { get_resource: private_net }
|
||||
cidr: 20.0.0.0/24
|
||||
gateway_ip: 20.0.0.1
|
||||
enable_dhcp: true
|
||||
allocation_pools:
|
||||
- start: 20.0.0.10
|
||||
end: 20.0.0.100
|
||||
|
||||
router:
|
||||
type: OS::Neutron::Router
|
||||
properties:
|
||||
name: sfc-test-router
|
||||
external_gateway_info:
|
||||
network: { get_param: provider_net }
|
||||
|
||||
router_interface:
|
||||
type: OS::Neutron::RouterInterface
|
||||
properties:
|
||||
router_id: { get_resource: router }
|
||||
subnet_id: { get_resource: private_subnet }
|
||||
|
||||
sec_group:
|
||||
type: OS::Neutron::SecurityGroup
|
||||
properties:
|
||||
name: sfc-test-sg
|
||||
rules:
|
||||
- remote_ip_prefix: 0.0.0.0/0
|
||||
protocol: tcp
|
||||
- remote_ip_prefix: 0.0.0.0/0
|
||||
protocol: udp
|
||||
- remote_ip_prefix: 0.0.0.0/0
|
||||
protocol: icmp
|
||||
|
||||
source_vm_port:
|
||||
type: OS::Neutron::Port
|
||||
properties:
|
||||
name: sfc-test-src-vm-port
|
||||
network_id: { get_resource: private_net }
|
||||
fixed_ips:
|
||||
- subnet_id: { get_resource: private_subnet }
|
||||
security_groups:
|
||||
- { get_resource: sec_group }
|
||||
|
||||
source_vm:
|
||||
type: OS::Nova::Server
|
||||
properties:
|
||||
name: sfc-test-src-vm
|
||||
admin_pass: test
|
||||
key_name: { get_param: key_name }
|
||||
flavor: { get_resource: flavor }
|
||||
image: { get_param: image_id }
|
||||
networks:
|
||||
- port: { get_resource: source_vm_port }
|
||||
user_data_format: RAW
|
||||
user_data: |
|
||||
#cloud-config
|
||||
write_files:
|
||||
- content: |
|
||||
import socket
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.bind(('', 2345))
|
||||
while True:
|
||||
data, address = sock.recvfrom(1024)
|
||||
sock.sendto(data, address)
|
||||
path: /tmp/echo.py
|
||||
runcmd:
|
||||
- python3 /tmp/echo.py
|
||||
|
||||
source_fip:
|
||||
type: OS::Neutron::FloatingIP
|
||||
properties:
|
||||
floating_network: { get_param: provider_net }
|
||||
port_id: { get_resource: source_vm_port }
|
||||
|
||||
sf_port_ctrl:
|
||||
type: OS::Neutron::Port
|
||||
properties:
|
||||
name: sfc-test-sf-port-ctrl
|
||||
network_id: { get_resource: private_net }
|
||||
fixed_ips:
|
||||
- subnet_id: { get_resource: private_subnet }
|
||||
security_groups:
|
||||
- { get_resource: sec_group }
|
||||
|
||||
sf_port_ingress:
|
||||
type: OS::Neutron::Port
|
||||
properties:
|
||||
name: sfc-test-sf-port-ingress
|
||||
network_id: { get_resource: private_net }
|
||||
fixed_ips:
|
||||
- subnet_id: { get_resource: private_subnet }
|
||||
port_security_enabled: false
|
||||
|
||||
sf_port_egress:
|
||||
type: OS::Neutron::Port
|
||||
properties:
|
||||
name: sfc-test-sf-port-egress
|
||||
network_id: { get_resource: private_net }
|
||||
fixed_ips:
|
||||
- subnet_id: { get_resource: private_subnet }
|
||||
port_security_enabled: false
|
||||
|
||||
sf_vm:
|
||||
type: OS::Nova::Server
|
||||
properties:
|
||||
name: sfc-test-sf
|
||||
admin_pass: test
|
||||
key_name: { get_param: key_name }
|
||||
flavor: { get_resource: flavor }
|
||||
image: { get_param: image_id }
|
||||
networks:
|
||||
- port: { get_resource: sf_port_ctrl }
|
||||
- port: { get_resource: sf_port_ingress }
|
||||
- port: { get_resource: sf_port_egress }
|
||||
user_data_format: RAW
|
||||
user_data:
|
||||
str_replace:
|
||||
template: |
|
||||
#cloud-config
|
||||
write_files:
|
||||
- content: |
|
||||
import os
|
||||
from ryu.base import app_manager
|
||||
from ryu.controller import ofp_event
|
||||
from ryu.controller.handler import CONFIG_DISPATCHER
|
||||
from ryu.controller.handler import MAIN_DISPATCHER
|
||||
from ryu.controller.handler import set_ev_cls
|
||||
from ryu.lib.packet import packet
|
||||
from ryu.lib.packet import ethernet
|
||||
from ryu.lib.packet import ipv4
|
||||
from ryu.lib.packet import mpls
|
||||
from ryu.lib.packet import udp
|
||||
from ryu.ofproto import ofproto_v1_3
|
||||
FILTER = os.environ.get('SF_FILTER')
|
||||
SUB = os.environ.get('SF_SUB')
|
||||
class SimpleServiceFunction(app_manager.RyuApp):
|
||||
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
|
||||
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
|
||||
def switch_features_handler(self, ev):
|
||||
msg = ev.msg
|
||||
dp = msg.datapath
|
||||
ofp_parser = dp.ofproto_parser
|
||||
message = dp.ofproto_parser.OFPFlowMod(
|
||||
datapath=dp,
|
||||
table_id=0,
|
||||
command=dp.ofproto.OFPFC_ADD,
|
||||
priority=100,
|
||||
match=ofp_parser.OFPMatch(in_port=1, eth_type=0x8847),
|
||||
instructions=[
|
||||
ofp_parser.OFPInstructionActions(
|
||||
dp.ofproto.OFPIT_APPLY_ACTIONS,
|
||||
[
|
||||
ofp_parser.OFPActionOutput(
|
||||
ofproto_v1_3.OFPP_CONTROLLER,
|
||||
ofproto_v1_3.OFPCML_NO_BUFFER,
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
dp.send_msg(message)
|
||||
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
|
||||
def packet_in_handler(self, ev):
|
||||
msg = ev.msg
|
||||
dp = msg.datapath
|
||||
ofp_parser = dp.ofproto_parser
|
||||
pkt = packet.Packet(msg.data)
|
||||
payload = pkt.protocols[-1]
|
||||
if isinstance(payload, (bytes, bytearray)):
|
||||
new_payload = payload.decode(
|
||||
'utf-8'
|
||||
).replace(
|
||||
FILTER,
|
||||
SUB,
|
||||
).encode('utf-8')
|
||||
new_pkt = packet.Packet()
|
||||
new_pkt.add_protocol(pkt.get_protocol(ethernet.ethernet))
|
||||
new_pkt.add_protocol(pkt.get_protocol(mpls.mpls))
|
||||
pkt_ip = pkt.get_protocol(ipv4.ipv4)
|
||||
pkt_ip.csum = 0
|
||||
pkt_ip.total_length = 0
|
||||
new_pkt.add_protocol(pkt_ip)
|
||||
pkt_udp = pkt.get_protocol(udp.udp)
|
||||
pkt_udp.csum = 0
|
||||
new_pkt.add_protocol(pkt_udp)
|
||||
new_pkt.add_protocol(new_payload)
|
||||
new_pkt.serialize()
|
||||
pkt = new_pkt
|
||||
actions = [ofp_parser.OFPActionOutput(port=2)]
|
||||
out = ofp_parser.OFPPacketOut(
|
||||
datapath=dp,
|
||||
buffer_id=ofproto_v1_3.OFP_NO_BUFFER,
|
||||
in_port=ofproto_v1_3.OFPP_CONTROLLER,
|
||||
data=pkt.data,
|
||||
actions=actions,
|
||||
)
|
||||
dp.send_msg(out)
|
||||
path: /tmp/controller.py
|
||||
- content: |
|
||||
#!/bin/bash
|
||||
dnf install -y openvswitch python3-ryu
|
||||
systemctl start openvswitch
|
||||
ovs-vsctl add-br br-sf
|
||||
ovs-vsctl set-controller br-sf tcp:127.0.0.1:6653
|
||||
ovs-vsctl add-port br-sf eth1
|
||||
ovs-vsctl add-port br-sf eth2
|
||||
ovs-ofctl del-flows br-sf
|
||||
ip link set dev eth1 up
|
||||
ip link set dev eth2 up
|
||||
SF_FILTER=$filter SF_SUB=$sub ryu-manager-3 /tmp/controller.py
|
||||
path: /tmp/run.sh
|
||||
runcmd:
|
||||
- sudo bash -x /tmp/run.sh
|
||||
params:
|
||||
$filter: { get_param: sf_filter }
|
||||
$sub: { get_param: sf_sub }
|
||||
|
||||
sf_fip:
|
||||
type: OS::Neutron::FloatingIP
|
||||
properties:
|
||||
floating_network: { get_param: provider_net }
|
||||
port_id: { get_resource: sf_port_ctrl }
|
||||
|
||||
port_pair:
|
||||
type: OS::Neutron::PortPair
|
||||
properties:
|
||||
name: sfc-test-pp
|
||||
ingress: { get_resource: sf_port_ingress }
|
||||
egress: { get_resource: sf_port_egress }
|
||||
service_function_parameters:
|
||||
correlation: mpls
|
||||
depends_on: sf_vm
|
||||
|
||||
port_pair_group:
|
||||
type: OS::Neutron::PortPairGroup
|
||||
properties:
|
||||
name: sfc-test-ppg
|
||||
port_pairs:
|
||||
- { get_resource: port_pair }
|
||||
|
||||
flow_classifier:
|
||||
type: OS::Neutron::FlowClassifier
|
||||
properties:
|
||||
name: sfc-test-fc
|
||||
logical_source_port: { get_resource: source_vm_port }
|
||||
ethertype: IPv4
|
||||
protocol: udp
|
||||
source_port_range_min: 2345
|
||||
source_port_range_max: 2345
|
||||
|
||||
port_chain:
|
||||
type: OS::Neutron::PortChain
|
||||
properties:
|
||||
name: sfc-test-pc
|
||||
flow_classifiers:
|
||||
- { get_resource: flow_classifier }
|
||||
port_pair_groups:
|
||||
- { get_resource: port_pair_group }
|
||||
|
||||
outputs:
|
||||
server_fip:
|
||||
description: Floating IP of the echo server
|
||||
value: { get_attr: [source_fip, floating_ip_address] }
|
||||
Reference in New Issue
Block a user