328 lines
11 KiB
YAML
328 lines
11 KiB
YAML
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 os_ken.base import app_manager
|
|
from os_ken.controller import ofp_event
|
|
from os_ken.controller.handler import CONFIG_DISPATCHER
|
|
from os_ken.controller.handler import MAIN_DISPATCHER
|
|
from os_ken.controller.handler import set_ev_cls
|
|
from os_ken.lib.packet import packet
|
|
from os_ken.lib.packet import ethernet
|
|
from os_ken.lib.packet import ipv4
|
|
from os_ken.lib.packet import mpls
|
|
from os_ken.lib.packet import udp
|
|
from os_ken.ofproto import ofproto_v1_3
|
|
FILTER = os.environ.get('SF_FILTER')
|
|
SUB = os.environ.get('SF_SUB')
|
|
class SimpleServiceFunction(app_manager.OsKenApp):
|
|
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] }
|