Merge "Adds GW type/class (both BGP and static uplinks)"

This commit is contained in:
Jenkins 2016-07-26 16:23:26 +00:00 committed by Gerrit Code Review
commit 23c87540ae
14 changed files with 1306 additions and 814 deletions

369
files/gateway/functions Normal file
View File

@ -0,0 +1,369 @@
#!/usr/bin/env bash
# Copyright 2016 Midokura SARL
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Useful common functions are defined here. Some of them were shamelessly
# stolen from devstack.
# Save trace setting
XTRACE=$(set +o | grep xtrace)
set +o xtrace
# Control Functions
# -----------------
# Prints line number and "message" in warning format
# warn $LINENO "message"
function warn {
local exitcode=$?
local xtrace=$(set +o | grep xtrace)
set +o xtrace
local msg="[WARNING] ${BASH_SOURCE[2]}:$1 $2"
echo $msg 1>&2;
if [[ -n ${LOGDIR} ]]; then
echo $msg >> "${LOGDIR}/error.log"
fi
$xtrace
return $exitcode
}
# Prints backtrace info
# filename:lineno:function
# backtrace level
function backtrace {
local level=$1
local deep=$((${#BASH_SOURCE[@]} - 1))
echo "[Call Trace]"
while [ $level -le $deep ]; do
echo "${BASH_SOURCE[$deep]}:${BASH_LINENO[$deep-1]}:${FUNCNAME[$deep-1]}"
deep=$((deep - 1))
done
}
# Prints line number and "message" in error format
# err $LINENO "message"
function err {
local exitcode=$?
local xtrace=$(set +o | grep xtrace)
set +o xtrace
local msg="[ERROR] ${BASH_SOURCE[2]}:$1 $2"
echo $msg 1>&2;
if [[ -n ${LOGDIR} ]]; then
echo $msg >> "${LOGDIR}/error.log"
fi
$xtrace
return $exitcode
}
# Prints line number and "message" then exits
# die $LINENO "message"
function die {
local exitcode=$?
set +o xtrace
local line=$1; shift
if [ $exitcode == 0 ]; then
exitcode=1
fi
backtrace 2
err $line "$*"
# Give buffers a second to flush
sleep 1
exit $exitcode
}
# Checks an environment variable is not set or has length 0 OR if the
# exit code is non-zero and prints "message" and exits
# NOTE: env-var is the variable name without a '$'
# die_if_not_set $LINENO env-var "message"
function die_if_not_set {
local exitcode=$?
local xtrace=$(set +o | grep xtrace)
set +o xtrace
local line=$1; shift
local evar=$1; shift
if ! is_set $evar || [ $exitcode != 0 ]; then
die $line "$*"
fi
$xtrace
}
# Test if the named environment variable is set and not zero length
# is_set env-var
function is_set {
local var=\$"$1"
eval "[ -n \"$var\" ]" # For ex.: sh -c "[ -n \"$var\" ]" would be better, but several exercises depends on this
}
# Package Functions
# -----------------
# Wrapper for ``apt-get``
# apt_get operation package [package ...]
function apt_get {
sudo DEBIAN_FRONTEND=noninteractive \
apt-get --option "Dpkg::Options::=--force-confnew" --assume-yes "$@"
}
# Update package repository
# Uses globals ``NO_UPDATE_REPOS``, ``REPOS_UPDATED``, ``RETRY_UPDATE``
# install_package package [package ...]
function update_package_repo {
NO_UPDATE_REPOS=${NO_UPDATE_REPOS:-False}
REPOS_UPDATED=${REPOS_UPDATED:-False}
if [[ "$NO_UPDATE_REPOS" = "True" ]]; then
return 0
fi
local xtrace=$(set +o | grep xtrace)
set +o xtrace
if [[ "$REPOS_UPDATED" != "True" ]]; then
# if there are transient errors pulling the updates, that's fine.
# It may be secondary repositories that we don't really care about.
apt_get update || /bin/true
REPOS_UPDATED=True
fi
$xtrace
}
# Package installer
# install_package package [package ...]
function install_package {
update_package_repo
apt_get install --no-install-recommends "$@"
}
# Function to tell if a package is installed
# is_package_installed package [package ...]
function is_package_installed {
dpkg -s "$@" > /dev/null 2> /dev/null
}
# System Functions
# -----------------
# Service wrapper to stop services
# stop_service service-name
# Checks if kmod is loaded
function is_kmod_loaded {
lsmod | grep -w $1 >& /dev/null
}
# Process Functions
# -----------------
function is_screen_running {
type -p screen > /dev/null && screen -ls | egrep -q "[0-9]\.$1"
}
function create_screen {
local name=$1
screen -d -m -S $name -t shell -s /bin/bash
sleep 1
# Set a reasonable status bar
SCREEN_HARDSTATUS='%{= .} %-Lw%{= .}%> %n%f %t*%{= .}%+Lw%< %-=%{g}(%{d}%H/%l%{g})'
screen -r $name -X hardstatus alwayslastline "$SCREEN_HARDSTATUS"
screen -r $name -X setenv PROMPT_COMMAND /bin/true
}
# Helper to launch a process in a named screen
# Uses globals ``CURRENT_LOG_TIME``, ``LOGDIR``,
# ``SERVICE_DIR``
# screen_process name "command-line"
# Run a command in a shell in a screen window
function screen_process {
local name=$1
local command="$2"
SERVICE_DIR=${SERVICE_DIR:-/tmp/status}
mkdir -p ${SERVICE_DIR}/${SCREEN_NAME}
# Append the process to the screen rc file
screen_rc ${SCREEN_NAME} "$name" "$command"
screen -S ${SCREEN_NAME} -X screen -t $name
if [[ -n ${LOGDIR} ]]; then
screen -S ${SCREEN_NAME} -p $name -X logfile ${LOGDIR}/${name}.log.${CURRENT_LOG_TIME}
screen -S ${SCREEN_NAME} -p $name -X log on
ln -sf ${LOGDIR}/${name}.log.${CURRENT_LOG_TIME} ${LOGDIR}/${name}.log
fi
# sleep to allow bash to be ready to be send the command - we are
# creating a new window in screen and then sends characters, so if
# bash isn't running by the time we send the command, nothing happens
sleep 3
NL=`echo -ne '\015'`
screen -S ${SCREEN_NAME} -p $name -X stuff "$command & echo \$! >$SERVICE_DIR/${SCREEN_NAME}/${name}.pid; fg || echo \"$name failed to start\" | tee \"$SERVICE_DIR/${SCREEN_NAME}/${name}.failure\"$NL"
}
# _run_process() is designed to be backgrounded by run_process() to simulate a
# fork. It includes the dirty work of closing extra filehandles and preparing log
# files to produce the same logs as screen_it(). The log filename is derived
# from the service name.
# _run_process service "command-line"
function _run_process {
local service=$1
local command="$2"
# Undo logging redirections and close the extra descriptors
exec 1>&3
exec 2>&3
exec 3>&-
exec 6>&-
local real_logfile="${LOGDIR}/${service}.log.${CURRENT_LOG_TIME}"
if [[ -n ${LOGDIR} ]]; then
exec 1>&"$real_logfile" 2>&1
ln -sf "$real_logfile" ${LOGDIR}/${service}.log
# Hack to get stdout from the Python interpreter for the logs.
export PYTHONUNBUFFERED=1
fi
SERVICE_DIR=${SERVICE_DIR:-/tmp/status}
mkdir -p ${SERVICE_DIR}/${SCREEN_NAME}
setsid $command & echo $! > ${SERVICE_DIR}/${SCREEN_NAME}/${service}.pid
# Just silently exit this process
exit 0
}
# Run a single service under screen or directly
# If the command includes shell metachatacters (;<>*) it must be run using a shell
# run_process service "command-line"
function run_process {
local service=$1
local command="$2"
if [[ "$USE_SCREEN" = "True" ]]; then
screen_process "$service" "cd $TOP_DIR && $command"
else
# Spawn directly without screen
cd $TOP_DIR
_run_process "$service" "$command" &
cd -
fi
}
# Screen rc file builder
# Uses globals ``SCREENRC``
# screen_rc service "command-line"
function screen_rc {
local screen=$1
SCREENRC=$DEVMIDO_DIR/$screen-screenrc
if [[ ! -e $SCREENRC ]]; then
# Name the screen session
echo "sessionname $screen" > $SCREENRC
# Set a reasonable statusbar
echo "hardstatus alwayslastline '$SCREEN_HARDSTATUS'" >> $SCREENRC
# Some distributions override PROMPT_COMMAND for the screen terminal type - turn that off
echo "setenv PROMPT_COMMAND /bin/true" >> $SCREENRC
echo "screen -t shell bash" >> $SCREENRC
fi
# If this service doesn't already exist in the screenrc file
if ! grep $1 $SCREENRC 2>&1 > /dev/null; then
NL=`echo -ne '\015'`
echo "screen -t $1 bash" >> $SCREENRC
echo "stuff \"$2$NL\"" >> $SCREENRC
if [[ -n ${LOGDIR} ]]; then
echo "logfile ${LOGDIR}/${1}.log.${CURRENT_LOG_TIME}" >>$SCREENRC
echo "log on" >>$SCREENRC
fi
fi
}
# Set an option in an INI file
# iniset config-file section option value
function iniset {
local xtrace=$(set +o | grep xtrace)
set +o xtrace
local file=$1
local section=$2
local option=$3
local value=$4
[[ -z $section || -z $option ]] && return
if ! grep -q "^\[$section\]" "$file" 2>/dev/null; then
# Add section at the end
echo -e "\n[$section]" >>"$file"
fi
if ! ini_has_option "$file" "$section" "$option"; then
# Add it
sed -i -e "/^\[$section\]/ a\\
$option = $value
" "$file"
else
local sep=$(echo -ne "\x01")
# Replace it
sed -i -e '/^\['${section}'\]/,/^\[.*\]/ s'${sep}'^\('${option}'[ \t]*=[ \t]*\).*$'${sep}'\1'"${value}"${sep} "$file"
fi
$xtrace
}
# Determinate is the given option present in the INI file
# ini_has_option config-file section option
function ini_has_option {
local xtrace=$(set +o | grep xtrace)
set +o xtrace
local file=$1
local section=$2
local option=$3
local line
line=$(sed -ne "/^\[$section\]/,/^\[.*\]/ { /^$option[ \t]*=/ p; }" "$file")
$xtrace
[ -n "$line" ]
}
function stop_process {
local service=$1
# Kill via pid if we have one available
pkill -F $SERVICE_DIR/$SCREEN_NAME/$service.pid
rm $SERVICE_DIR/$SCREEN_NAME/$service.pid
screen -S $SCREEN_NAME -p $service -X kill
}
# MN Functions
# ------------
# Wrapper for mn-conf command
# Uses globals ``ZOOKEEPER_HOSTS``
function configure_mn {
local value="$2"
# quote with "" only when necessary. we don't always quote because
# mn-conf complains for quoted booleans. eg. "false"
if [[ "${value}" =~ ":" || "${value}" = "" ]]; then
value="\"${value}\""
fi
# In some commands, mn-conf creates a local file, which requires root
# access. For simplicity, always call mn-conf with root for now.
echo $1 : "${value}" | MIDO_ZOOKEEPER_HOSTS="$ZOOKEEPER_HOSTS" sudo mn-conf set
}
# Restore xtrace
$XTRACE

View File

@ -1,309 +0,0 @@
if RUBY_VERSION == '1.8.7'
require 'rubygems'
end
require 'uri'
require 'faraday'
require 'json'
Puppet::Type.type(:midonet_gateway).provide(:midonet_api_caller) do
def create
define_connection(resource[:midonet_api_url])
# For each remote BGP peer, create a virtual port on
# the MidoNet Provider Router that is going to be used
# for the BGP communication. Connection to midonet api
# is assumed
router_id = call_get_provider_router()[0]['id']
message = Hash.new
message['portAddress'] = resource[:bgp_port]["port_address"]
message['networkAddress'] = resource[:bgp_port]["net_prefix"]
message['networkLength'] = resource[:bgp_port]["net_length"].to_i
message['type'] = "Router"
port = call_create_uplink_port(router_id, message)
port_id = port[0]['id']
# Configure BGP on the virtual ports. Port is
# assumed created
remote_peers = resource[:remote_peers]
if remote_peers.class == Hash
remote_peers = [remote_peers]
end
remote_peers.each do |rp|
message = Hash.new
message['localAS'] = resource[:local_as]
message['peerAS'] = rp["as"]
message['peerAddr'] = rp["ip"]
call_add_bgp_to_port(port_id, message)
end
# Add route for 'MidoNet Provider Router' uplink port
message = Hash.new
message['type'] = "Normal"
message['srcNetworkAddr'] = "0.0.0.0"
message['srcNetworkLength'] = 0
message['dstNetworkAddr'] = resource[:bgp_port]["net_prefix"]
message['dstNetworkLength'] = resource[:bgp_port]["net_length"].to_i
message['weight'] = 100
message['nextHopPort'] = port_id
call_add_route_for_uplink_port(router_id, message)
# In order to provide external connectivity for hosted
# virtual machines, the floating IP network has to be
# advertised to the BGP peers. BGP connection is assumed created
bgp_connections = call_get_bgp_connections(port_id)
#TODO(carmela): make this modification more elegant... or whatever
advertise_networks = resource[:advertise_net]
if advertise_networks.class == Hash
advertise_networks = [advertise_networks]
end
bgp_connections.each do |bgp_c|
advertise_networks.each do |net|
message = Hash.new
message['nwPrefix'] = net["net_prefix"]
message['prefixLength'] = net["net_length"]
bgp_id = bgp_c["id"]
call_advertise_route_to_bgp(bgp_id, message)
end
end
# Bind the MidoNet Provider Routers virtual ports to
# the physical network interfaces on the Gateway Nodes.
# Host and port are assumed created. Interface name should
# be an string
host_id = call_get_host()[0]['id']
message = Hash.new
message['interfaceName'] = resource[:interface]
message['portId'] = port_id
call_bind_port_to_interface(host_id, message)
# Configure a stateful port group
spg = call_get_stateful_port_group()
if spg.empty?
message = Hash.new
message['name'] = "uplink-spg"
message['stateful'] = "true"
message['tenantId'] = call_get_tenant()
spg = call_create_stateful_port_group(message)
end
# Add the ports to the port group
message = Hash.new
message['portId'] = port_id
call_add_ports_to_port_group(spg[0]["id"], message)
end
def destroy
define_connection(resource[:midonet_api_url])
router_id = call_get_provider_router()[0]['id']
port_address = resource[:bgp_port]['port_address']
port = call_get_uplink_port(router_id, port_address)
port_id = port[0]["id"]
# Delete the stateful port group
# TODO(carmela): delete only in case is the last port on the port group
# port_group_id = call_get_stateful_port_group()[0]['id']
# call_delete_stateful_port_group(port_group_id)
# Delete uplink port
call_delete_uplink_port(port_id)
end
def exists?
define_connection(resource[:midonet_api_url])
router = call_get_provider_router()
if router.empty?
raise 'MidoNet Provider Router does not exist. We cannot create uplink ports'
end
host = call_get_host()
if host.empty?
raise 'There is no MidoNet agent running on this host'
end
uplink_port = call_get_uplink_port(router[0]['id'], resource[:bgp_port]['port_address'])
if uplink_port.empty?
return false
end
return true
end
def define_connection(url)
@connection = Faraday.new(:url => url,
:ssl => { :verify =>false }) do |builder|
builder.request(:retry, {
:max => 5,
:interval => 0.05,
:exceptions => [
Faraday::Error::TimeoutError,
Faraday::ConnectionFailed,
Errno::ETIMEDOUT,
'Timeout::Error',
],
})
builder.request(:basic_auth, resource[:username], resource[:password])
builder.adapter(:net_http)
end
@connection.headers['X-Auth-Token'] = call_get_token()
end
def call_get_token()
res = @connection.get do |req|
req.url "/midonet-api/login"
end
return JSON.parse(res.body)['key']
end
def call_get_tenant()
res = @connection.get do |req|
req.url "/midonet-api/tenants"
end
return JSON.parse(res.body)[0]['id']
end
def call_get_provider_router()
res = @connection.get do |req|
req.url "/midonet-api/routers"
end
output = JSON.parse(res.body)
return output.select { |name| name['name'] == resource[:router]}
end
def call_get_stateful_port_group()
res = @connection.get do |req|
req.url "/midonet-api/port_groups"
end
output = JSON.parse(res.body)
return output.select { |name| name['name'] == 'uplink-spg'}
end
def call_get_uplink_port(router_id, port_address)
res = @connection.get do |req|
req.url "/midonet-api/routers/#{router_id}/ports"
end
output = JSON.parse(res.body)
return output.select { |port| port['portAddress'] == port_address }
end
def call_get_host()
res = @connection.get do |req|
req.url "/midonet-api/hosts"
end
output = JSON.parse(res.body)
return output.select{ |host| host['name'] == resource[:hostname].to_s }
end
def call_create_uplink_port(router_id, message)
res = @connection.post do |req|
req.url "/midonet-api/routers/#{router_id}/ports"
req.headers['Content-Type'] = "application/vnd.org.midonet.Port-v2+json"
req.body = message.to_json
end
return call_get_uplink_port(router_id, message["portAddress"])
end
def call_delete_uplink_port(port_id)
res = @connection.delete do |req|
req.url "/midonet-api/ports/#{port_id}"
end
end
def call_add_bgp_to_port(port_id, message)
res = @connection.post do |req|
req.url "/midonet-api/ports/#{port_id}/bgps"
req.headers['Content-Type'] = "application/vnd.org.midonet.Bgp-v1+json"
req.body = message.to_json
end
end
def call_add_route_for_uplink_port(router_id, message)
res = @connection.post do |req|
req.url "/midonet-api/routers/#{router_id}/routes"
req.headers['Content-Type'] = "application/vnd.org.midonet.Route-v1+json"
req.body = message.to_json
end
end
def call_get_bgp_connections(port_id)
res = @connection.get do |req|
req.url "/midonet-api/ports/#{port_id}/bgps"
end
output = JSON.parse(res.body)
return output
end
def call_advertise_route_to_bgp(bgp_id, message)
res = @connection.post do |req|
req.url "/midonet-api/bgps/#{bgp_id}/ad_routes"
req.headers['Content-Type'] = "application/vnd.org.midonet.AdRoute-v1+json"
req.body = message.to_json
end
end
def call_bind_port_to_interface(host_id, message)
res = @connection.post do |req|
req.url "/midonet-api/hosts/#{host_id}/ports"
req.headers['Content-Type'] = "application/vnd.org.midonet.HostInterfacePort-v1+json"
req.body = message.to_json
end
end
def call_create_stateful_port_group(message)
res = @connection.post do |req|
req.url "/midonet-api/port_groups"
req.headers['Content-Type'] = "application/vnd.org.midonet.PortGroup-v1+json"
req.body = message.to_json
end
return call_get_stateful_port_group()
end
def call_add_ports_to_port_group(port_group_id, message)
res = @connection.post do |req|
req.url "/midonet-api/port_groups/#{port_group_id}/ports"
req.headers['Content-Type'] = "application/vnd.org.midonet.PortGroupPort-v1+json"
req.body = message.to_json
end
end
def call_delete_stateful_port_group(port_group_id)
res = @connection.delete do |req|
req.url "/midonet-api/port_groups/#{port_group_id}"
end
end
private :call_add_bgp_to_port
:call_add_ports_to_port_group
:call_add_route_for_uplink_port
:call_advertise_route_to_bgp
:call_bind_port_to_interface
:call_create_stateful_port_group
:call_create_uplink_port
:call_delete_stateful_port_group
:call_delete_uplink_port
:call_get_bgp_connections
:call_get_host
:call_get_stateful_port_group
:call_get_provider_router
:call_get_tenant
:call_get_uplink_port
:define_connection
end

View File

@ -0,0 +1,217 @@
if RUBY_VERSION == '1.8.7'
require 'rubygems'
end
require 'uri'
require 'faraday'
require 'json'
Puppet::Type.type(:midonet_gateway_bgp).provide(:midonet_api_caller) do
def create
define_connection(resource[:midonet_api_url])
# Get the edge router uuid
provider_router = call_get_provider_router()[0]
provider_router_id = provider_router['id']
# Assign local ASN to the provider router
asn = provider_router['asNumber']
call_assign_asn(provider_router_id, resource[:bgp_local_as_number]) unless asn == resource[:bgp_local_as_number]
# Sync BGP peers
bgp_neighbors = call_get_bgp_peers(provider_router_id)
m = Array.new
bgp_neighbors.each do |bgp_neighbor|
n = { "ip_address" => bgp_neighbor["address"],
"remote_asn" => bgp_neighbor["asNumber"] }
m << n
end
tbd_peers = m - resource[:bgp_neighbors]
tba_peers = resource[:bgp_neighbors] - m
tba_peers.each { |a| call_add_bgp_peer(provider_router_id, a['ip_address'], a['remote_asn']) }
tbd_peers.each do |d|
bgp_peer_id = bgp_neighbors.select { |bgp_neighbor| bgp_neighbor['asNumber'] == d['remote_asn'] }[0]["id"]
call_delete_bgp_peer(bgp_peer_id)
end
# Advertise floating IP networks
bgp_advertised_networks = call_get_bgp_networks(provider_router_id)
j = Array.new
bgp_advertised_networks.each do |bgp_advertised_network|
k = [ bgp_advertised_network["subnetAddress"], bgp_advertised_network["subnetLength"] ].join("/")
j << k
end
tbd_bgp_networks = j - resource[:bgp_advertised_networks]
tba_bgp_networks = resource[:bgp_advertised_networks] - j
tba_bgp_networks.each { |a| call_advertise_bgp_network(provider_router_id, a) }
tbd_bgp_networks.each do |d|
bgp_network_id = bgp_advertised_networks.select { |bgp_advertised_network| bgp_advertised_network['subnetAddress'] == d.split("/")[0] && bgp_advertised_network['subnetLength'] == d.split("/")[1] }[0]["id"]
call_delete_bgp_network(provider_router_id, bgp_network_id)
end
end
def destroy
define_connection(resource[:midonet_api_url])
# Get the edge router uuid
provider_router_id = call_get_provider_router()[0]['id']
# "Unset" asNumber by setting it to -1 (default value)
call_assign_asn(provider_router_id, "-1")
# Remove BGP peers from router
bgp_peers = call_get_bgp_peers(provider_router_id)
bgp_peers.each do |bgp_peer|
call_delete_bgp_peer(bgp_peer["id"])
end
# De-advertise floating IP networks
bgp_networks = call_get_bgp_networks(provider_router_id)
bgp_networks.each do |bgp_network|
call_delete_bgp_network(bgp_network)
end
end
def exists?
define_connection(resource[:midonet_api_url])
# Get the edge router uuid
provider_router = call_get_provider_router()[0]
provider_router_id = provider_router['id']
result_array = Array.new
# Check if local ASN is the same
result_array.push(provider_router["asNumber"] == resource[:bgp_local_as_number])
# Check if BGP neighbors are the same
bgp_neighbors = call_get_bgp_peers(provider_router_id)
m = Array.new
bgp_neighbors.each do |bgp_neighbor|
n = { "ip_address" => bgp_neighbor["address"],
"remote_asn" => bgp_neighbor["asNumber"] }
m << n
end
result_array.push(m == resource[:bgp_neighbors])
# Check if advertised networks are the same
bgp_advertised_networks = call_get_bgp_networks(provider_router_id)
j = Array.new
bgp_advertised_networks.each do |bgp_advertised_network|
k = [ bgp_advertised_network["subnetAddress"], bgp_advertised_network["subnetLength"] ].join("/")
j << k
end
result_array.push(j == resource[:bgp_advertised_networks])
# Test if all tests are positive
return result_array.uniq == [true]
end
def define_connection(url)
@connection = Faraday.new(:url => url,
:ssl => { :verify =>false }) do |builder|
builder.request(:retry, {
:max => 5,
:interval => 0.05,
:exceptions => [
Faraday::Error::TimeoutError,
Faraday::ConnectionFailed,
Errno::ETIMEDOUT,
'Timeout::Error',
],
})
builder.request(:basic_auth, resource[:username], resource[:password])
builder.adapter(:net_http)
end
@connection.headers['X-Auth-Token'] = call_get_token()
end
def call_get_token()
res = @connection.get do |req|
req.url "/midonet-api/login"
end
return JSON.parse(res.body)['key']
end
def call_get_provider_router()
res = @connection.get do |req|
req.url "/midonet-api/routers"
end
output = JSON.parse(res.body)
provider_router = output.select { |r| r['name'] == resource[:router]}
raise "Router #{resource[:router]} does not exist" if provider_router.empty?
return provider_router
end
def call_assign_asn( provider_router_id, bgp_local_as_number )
res = @connection.post do |req|
req.url "/midonet-api/routers"
req.headers['Content-Type'] = "application/vnd.org.midonet.Router-v3+json"
req.body = { 'id' => provider_router_id,
'asNumber' => bgp_local_as_number }.to_json
end
end
def call_add_bgp_peer( provider_router_id, ip_address, remote_asn )
res = @connection.post do |req|
req.url "/midonet-api/#{provider_router_id}/bgp_peers"
req.headers['Content-Type'] = "application/vnd.org.midonet.BgpPeer-v1+json"
req.body = { 'address' => ip_address,
'asNumber' => remote_asn }.to_json
end
end
def call_advertise_bgp_network( provider_router_id, bgp_advertised_network )
subnet_address, subnet_length = bgp_advertised_network.split("/")
res = @connection.post do |req|
req.url "/midonet-api/#{provider_router_id}/bgp_networks"
req.headers['Content-Type'] = "application/vnd.org.midonet.BgpNetwork-v1+json"
req.body = { 'subnetAddress' => subnet_address,
'subnetLength' => subnet_length }.to_json
end
end
def call_delete_bgp_peer(bgp_peer_id)
res = @connection.delete do |req|
req.url "/midonet-api/bgp_peers/#{bgp_peer_id}"
end
end
def call_delete_bgp_network(bgp_network)
res = @connection.delete do |req|
req.url "/midonet-api/bgp_networks/#{bgp_network}"
end
end
def call_get_bgp_peers(provider_router_id)
res = @connection.get do |req|
req.url "/midonet-api/routers/#{provider_router_id}/bgp_peers"
end
output = JSON.parse(res.body)
return output
end
def call_get_bgp_networks(provider_router_id)
res = @connection.get do |req|
req.url "/midonet-api/routers/#{provider_router_id}/bgp_networks"
end
output = JSON.parse(res.body)
return output.map { |e| e["id"] }
end
private :call_get_provider_router
:define_connection
:call_assign_asn
:call_add_bgp_peer
:call_get_bgp_networks
:call_get_bgp_peers
:call_delete_bgp_network
:call_delete_bgp_peer
:call_advertise_bgp_network
end

View File

@ -1,159 +0,0 @@
require 'uri'
require 'facter'
Puppet::Type.newtype(:midonet_gateway) do
@doc = %q{BGP Uplink Configuration
Example:
midonet_gateway {'hostname':
midonet_api_url => 'http://controller:8080',
username => 'admin',
password => 'admin',
tenant_name => 'admin',
interface => 'eth1',
local_as => '64512',
bgp_port => { 'port_address' => '198.51.100.2', 'net_prefix' => '198.51.100.0', 'net_length' => '30'},
remote_peers => [ { 'as' => '64513', 'ip' => '198.51.100.1' },
{ 'as' => '64513', 'ip' => '203.0.113.1' } ],
advertise_net => [ { 'net_prefix' => '192.0.2.0', 'net_length' => '24' } ]
}
}
ensurable
autorequire(:package) do ['midolman'] end
newparam(:hostname, :namevar => true) do
desc 'Hostname of the host that will act as gateway in a MidoNet managed cloud'
# Regex obtained from StackOverflow question:
# http://stackoverflow.com/questions/1418423/the-hostname-regex
validate do |value|
unless value =~ /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?$/
raise ArgumentError, "'%s' is not a valid hostname" % value
end
end
end
newparam(:midonet_api_url) do
desc 'MidoNet API endpoint to connect to'
validate do |value|
unless value =~ /\A#{URI::regexp(['http', 'https'])}\z/
raise ArgumentError, "'%s' is not a valid URI" % value
end
end
end
newparam(:username) do
desc 'Username of the admin user in keystone'
defaultto 'admin'
validate do |value|
unless value =~ /\w+$/
raise ArgumentError, "'%s' is not a valid username" % value
end
end
end
newparam(:password) do
desc 'Password of the admin user in keystone'
defaultto 'admin'
validate do |value|
unless value =~ /\w+$/
raise ArgumentError, "'%s' is not a valid password" % value
end
end
end
newparam(:tenant_name) do
desc 'Tenant name of the admin user'
defaultto 'admin'
validate do |value|
unless value =~ /\w+$/
raise ArgumentError, "'%s' is not a tenant name" % value
end
end
end
newparam(:interface) do
desc "Physical interface where the MidoNet Provider Router's port is binded to"
defaultto 'eth0'
validate do |value|
unless value =~ /^\w+(.\d+)?$/
raise ArgumentError, "'%s' is not a valid interface" % value
end
end
end
newparam(:local_as) do
desc "Local AS number"
validate do |value|
unless value =~ /\d+/
raise ArgumentError, "'%s' is not a valid AS" % value
end
end
end
newparam(:remote_peers) do
desc "#to be filled"
defaultto []
validate do |value|
if value.class == Hash
value = [value]
end
value.each do |rp|
unless rp["as"] =~ /\d+/
raise ArgumentError, "'%s' is not a valid AS name" % rp["as"]
end
unless rp["ip"] =~ /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/
raise ArgumentError, "'%s' is not a valid IP address" % rp["ip"]
end
end
end
end
newparam(:bgp_port) do
desc "#to be filled"
validate do |value|
[:port_address, :net_prefix, :net_length].all? {|key| value.key? key}
unless value["port_address"] =~ /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/
raise ArgumentError, "'%s' is not a valid IP address" % value["port_address"]
end
unless value["net_prefix"] =~ /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/
raise ArgumentError, "'%s' is not a valid IPv4 network address" % value["net_prefix"]
end
unless value["net_length"] =~ /\d{2}/
raise ArgumentError, "'%s' is not a valid network prefix length" % value["net_length"]
end
end
end
newparam(:router) do
desc "The MidoNet's internal Provider router that acts as the gateway router of the cloud"
defaultto 'MidoNet Provider Router'
validate do |value|
unless value =~ /\w+$/
raise ArgumentError, "'%s' is not a valid router name" % value
end
end
end
newparam(:advertise_net) do
desc 'Floating IP network to be avertised to the BGP peers'
defaultto []
validate do |value|
if value.class == Hash
value = [value]
end
value.each do |an|
unless an["net_prefix"] =~ /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/
raise ArgumentError, "'%s' is not a valid IPv4 network address" % an["net_prefix"]
end
unless an["net_length"] =~ /\d{2}/
raise ArgumentError, "'%s' is not a valid network prefix length" % an["net_length"]
end
end
end
end
end

View File

@ -0,0 +1,150 @@
require 'uri'
require 'facter'
Puppet::Type.newtype(:midonet_gateway_bgp) do
@doc = %q{This type is used to configure a gateway node, considering the
uplink is of type BGP. What it does:
1) Assign an AS number to the edge router
2) Assign BGP peers
3) Advertise BGP networks
Usage:
midonet_gateway_bgp { 'edge_router':
bgp_local_as_number => '65432',
bgp_advertised_networks => [
'200.0.0.0/24',
'12.0.140.0/16',
],
bgp_neighbors => [
{
'ip_address' => '200.0.1.1',
'remote_asn' => '34512'
},
{
'ip_address' => '12.0.140.1',
'remote_asn' => '24125'
}
],
midonet_api_url => 'http://controller:8181',
username => 'admin',
password => 'admin',
}
}
ensurable
newparam(:bgp_local_as_number) do
desc "AS number of the local BGP autonomous system. If you have an RIR
registered AS number, use it. If you don't have an RIR registered AS
number, use any of the RFC 1930's reserved AS number. It should be an
integer. Only applicable when uplink_type=='bgp'."
validate do |value|
unless value =~ /^\d+$/
raise ArgumentError, "'%s' is not a valid AS number" % value
end
end
end
newparam(:bgp_advertised_networks) do
desc "Networks advertised to the remote BGP autonomous system. This usually
should be the same as floating IP networks defined in neutron. It should
be an array of strings, each of which should be a network address
in either \"IP/Prefix_Length\" notation (\"192.0.2.0/24\") or
\"IP/Subnet_Mask\" nonation(\"192.0.2.0/255.255.255.0\"). Only applicable
when uplink_type=='bgp'. A more elaborate example will be:
[ '192.0.2.128/25', '198.51.100.128/25', '203.0.113.128/25' ]"
validate do |value|
unless value.class == Array && value.length > 0
raise ArgumentError, "#{value} is not an Array"
else
value.each do |e|
unless e.class == String && e =~ /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\/\d+$/
raise ArgumentError, "#{e} is not a valid IP address"
end
end
end
end
end
newparam(:bgp_neighbors) do
desc " An array containing hashes that describe a remote BGP peer. These
hashes must have two parameters:
- 'ip_address': IP address of the peer
- 'remote_asn': Peer's AS number
A real life example will look like:
[
{
'ip_address' => '203.0.113.1',
'remote_asn' => '64513'
},
{
'ip_address' => '198.51.100.1',
'remote_asn' => '64514'
}
]
"
validate do |value|
unless value.class == Array && value.length > 0
raise ArgumentError, "'%s' is not an array" % value
else
value.each do |e|
unless e.class == Hash && e.key?("ip_address") && e.key?("remote_asn")
raise ArgumentError, "'%s' doesn't contain parameters 'ip_address' and/or 'remote_asn'" % value
else
unless e["ip_address"] =~ /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/
raise ArgumentError, "#{e['ip_address']} is not a valid IP address"
end
unless e["remote_asn"] =~ /^\d+$/
raise ArgumentError, "#{e['remote_asn']} is not a valid AS number"
end
end
end
end
end
end
autorequire(:package) do ['midolman'] end
newparam(:midonet_api_url) do
desc 'MidoNet API endpoint to connect to'
validate do |value|
unless value =~ /\A#{URI::regexp(['http', 'https'])}\z/
raise ArgumentError, "'%s' is not a valid URI" % value
end
end
end
newparam(:username) do
desc 'Username of the admin user in keystone'
defaultto 'admin'
validate do |value|
unless value =~ /\w+$/
raise ArgumentError, "'%s' is not a valid username" % value
end
end
end
newparam(:password) do
desc 'Password of the admin user in keystone'
defaultto 'admin'
validate do |value|
unless value =~ /\w+$/
raise ArgumentError, "'%s' is not a valid password" % value
end
end
end
newparam(:router, :namevar => true) do
desc "The MidoNet's internal Provider router that acts as the gateway router of the cloud"
defaultto 'MidoNet Provider Router'
validate do |value|
unless value =~ /\w+$/
raise ArgumentError, "'%s' is not a valid router name" % value
end
end
end
end

127
manifests/gateway/static.pp Normal file
View File

@ -0,0 +1,127 @@
# == Class: midonet::gateway::static
#
# Set up a fake uplink with static routing on a gateway node
#
# === Parameters
#
# [*network_id*]
# (Mandatory) Name of the bridge that will be created through midonet-cli
#
# [*cidr*]
# (Mandatory) Network that will be assigned to the fake uplink
#
# [*gateway_ip*]
# (Mandatory) Gateway IP through which the packets will go
#
# [*service_host*]
# (Mandatory) Host where the Midonet API runs
#
# [*service_dir*]
# (Mandatory) Folder on which to place the pidfile and some other temporary
# files for the well functioning of this script (ex: /tmp/status)
#
# [*zookeeper_hosts*]
# (Mandatory) Comma-separated list of zookeeper hosts. These are a hash consisting of two
# fields, 'ip' and 'port'. Example: [ { 'ip' => '12.153.140.2', 'port' => '2181'}]
#
# [*api_port*]
# (Mandatory) Port that the Midonet API binds
#
# [*scripts_dir*]
# (Optional) Path where to place the necessary scripts
#
# [*ensure_scripts*]
# (Optional) Status of the scripts
#
# [*mido_db_user*]
# (Optional) Username to authenticate against the DB
#
# [*mido_db_password*]
# (Optional) Password to authenticate against the DB
#
# === Examples
#
# The easiest way to run the class is:
#
# class { 'midonet::gateway::static':
# network_id => 'example_netid',
# cidr => '200.0.0.1/24',
# gateway_ip => '200.0.0.1',
# service_host => '127.0.0.1',
# service_dir => '/tmp/status',
# zookeeper_hosts => [
# {
# 'ip'=>'127.0.0.1',
# 'port'=>'2181'
# }
# ],
# api_port => '8181'
# }
#
# === Authors
#
# Midonet (http://midonet.org)
#
# === Copyright
#
# Copyright (c) 2016 Midokura SARL, All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
class midonet::gateway::static (
$network_id,
$cidr,
$gateway_ip,
$service_host,
$service_dir,
$zookeeper_hosts,
$api_port,
$scripts_dir = '/tmp',
$uplink_script = 'create_fake_uplink_l2.sh',
$midorc_script = 'midorc',
$functions_script = 'functions',
$ensure_scripts = 'present',
$mido_db_user = 'admin',
$mido_db_password = 'admin',
) {
# Install screen, as it's needed by the script
package { 'screen': ensure => installed }
# Place script and helper files before executing it
file { 'fake_uplink_script':
ensure => $ensure_scripts,
path => "${scripts_dir}/create_fake_uplink_l2.sh",
content => template('midonet/gateway/create_fake_uplink_l2.sh.erb'),
}
file { 'midorc':
ensure => $ensure_scripts,
path => "${scripts_dir}/midorc",
content => template('midonet/gateway/midorc.erb'),
}
file { 'functions':
ensure => $ensure_scripts,
path => "${scripts_dir}/functions",
source => 'puppet:///modules/midonet/gateway/functions',
require => Package['screen'],
}
# Finally, execute the script
exec { "/bin/bash ${scripts_dir}/create_fake_uplink_l2.sh":
require => [
File['fake_uplink_script', 'midorc', 'functions'],
Package['python-midonetclient'],
]
}
}

View File

@ -1,24 +0,0 @@
require 'spec_helper'
describe 'midonet::cluster' do
context 'with parameters' do
let :facts do
{
:osfamily => 'Debian',
:lsbdistid => 'Ubuntu',
:lsbdistrelease => '16.04',
}
end
let :params do
{
:zookeeper_hosts => ['{ "ip" => "127.0.0.1", "port" => "2181" }'],
:cassandra_servers => [ '127.0.0.1' ],
:cassandra_rep_factor => '3',
:keystone_admin_token => 'ADMIN_TOKEN',
:keystone_host => '127.0.0.1',
}
end
it { is_expected.to contain_class('midonet::cluster::install') }
it { is_expected.to contain_class('midonet::cluster::run') }
end
end

View File

@ -0,0 +1,29 @@
require 'spec_helper'
describe 'midonet::gateway::static' do
context 'with parameters' do
let :facts do
{
:osfamily => 'Debian',
:lsbdistid => 'Ubuntu',
:lsbdistrelease => '16.04',
}
end
let :params do
{
:network_id => 'example_netid',
:cidr => '200.0.0.1/24',
:gateway_ip => '200.0.0.1',
:service_host => '127.0.0.1',
:service_dir => '/tmp/status',
:zookeeper_hosts => [ { 'ip' => '127.0.0.1', 'port' => '2181' } ],
:api_port => '8181'
}
end
it { is_expected.to contain_package('screen').with_ensure('installed') }
it { is_expected.to contain_file('fake_uplink_script').with_ensure('present') }
it { is_expected.to contain_file('midorc').with_ensure('present') }
it { is_expected.to contain_file('functions').with_ensure('present') }
it { is_expected.to contain_exec('/bin/bash /tmp/create_fake_uplink_l2.sh') }
end
end

View File

@ -1,158 +1,108 @@
require 'spec_helper'
describe Puppet::Type.type(:midonet_gateway).provider(:midonet_api_caller) do
describe Puppet::Type.type(:midonet_gateway_bgp).provider(:midonet_api_caller) do
let(:provider) { described_class.new(resource) }
let(:resource) { Puppet::Type.type(:midonet_gateway).new(
{
:ensure => :present,
:hostname => 'compute.midonet',
:midonet_api_url => 'http://controller:8080',
:username => 'admin',
:password => 'admin',
:interface => 'eth0',
:local_as => '64512',
:bgp_port => { 'port_address' => '198.51.100.2', 'net_prefix' => '198.51.100.0', 'net_length' => '30' },
:remote_peers => [ { 'as' => '64513', 'ip' => '198.51.100.1' },
{ 'as' => '64513', 'ip' => '203.0.113.1' } ],
:advertise_net => [ { 'net_prefix' => '192.0.2.0', 'net_length' => '24' } ]
}
)}
let(:resource) do
Puppet::Type::type(:midonet_gateway_bgp).new(
:router => 'edge_router',
:midonet_api_url => 'http://controller:8080/midonet-api',
:username => 'admin',
:password => 'admin',
:bgp_local_as_number => '64512',
:bgp_neighbors => [
{
'ip_address' => '200.100.98.7',
'remote_asn' => '45237',
},
{
'ip_address' => '182.24.63.2',
'remote_asn' => '45235',
},
],
:bgp_advertised_networks => [ '200.0.0.0/24', '200.0.20.0/24' ] )
end
describe 'BGP configuration happy path' do
# - Create virtual ports for each remote BGP peer
# - Configure BGP on the virtual ports or remove config if needed
# - Advertise routes
# - Bind virtual ports to physical network interfaces
# - Configure stateful port group and delete it if needed
# - Add ports to the port group and remove them if needed
# 1) Assign AS number to router
# 2) Assign BGP neighbors to routers
# 3) Advertise floating IP network
# 4) Delete BGP neighbors
# 5) De-advertise networks
# Other parameters are returned by default, but only this one is needed for
# testing purposes
# More info: https://docs.midonet.org/docs/latest/rest-api/content/router.html
let(:routers) {
[
{
"id" => "e6a53892-03bf-4f16-8212-e4d76ad204e3",
"name" => "MidoNet Provider Router"
"name" => "edge_router"
}
]
}
let(:ports) {
# Other parameters are returned by default, but only this one is needed for
# testing purposes
# More info: https://docs.midonet.org/docs/latest/rest-api/content/bgp-peer.html
let(:bgp_peers) {
[
{
"hostId" => "b3f2f63e-02a6-459a-af0f-44eeac441a09",
"interfaceName" => "eth0",
"id" => "20b169b1-ec0a-4639-8479-44b63e016935"
"id" => "4a5e4356-3417-4c60-9cf8-7516aedb7067",
}
]
}
let(:bgps) {
# Other parameters are returned by default, but only this one is needed for
# testing purposes
# More info: https://docs.midonet.org/docs/latest/rest-api/content/bgp-network.html
let(:bgp_networks) {
[
{
"id" => "4a5e4356-3417-4c60-9cf8-7516aedb7067",
"localAS" => "64512",
"peerAS" => "64513",
"peerAddr" => "198.51.100.1",
"portId" => "f9e61b88-0a26-4d56-8f47-eb5da37225e0"
}
]
}
let(:port_groups) {
[
{
"id" => "711401b7-bf6f-4afd-8ab2-94b4342a0310",
"name" => "uplink-spg",
"stateful" => "true"
}
]
}
let(:hosts) {
[
{
"id" => "b3f2f63e-02a6-459a-af0f-44eeac441a09",
"name" => "compute.midonet"
}
]
}
let(:tenants) {
[
{
"id" => "4486908d-8e15-4f01-b3b4-86f9def0fa04",
"name" => "4486908d-8e15-4f01-b3b4-86f9def0fa04"
"id" => "4a5e4356-3417-4c60-9cf8-7516abcd1234",
}
]
}
before :each do
allow(provider).to receive(:call_create_uplink_port).and_return(ports)
allow(provider).to receive(:call_get_provider_router).and_return(routers)
allow(provider).to receive(:call_get_host_id).and_return(hosts[0]['id'])
allow(provider).to receive(:call_get_host).and_return(hosts)
allow(provider).to receive(:call_add_bgp_to_port)
allow(provider).to receive(:call_get_bgp_connections).and_return(bgps)
allow(provider).to receive(:call_advertise_route_to_bgp)
allow(provider).to receive(:call_bind_port_to_interface)
allow(provider).to receive(:call_get_stateful_port_group).and_return(port_groups)
allow(provider).to receive(:call_add_ports_to_port_group)
allow(provider).to receive(:call_delete_uplink_port).and_return(ports)
allow(provider).to receive(:call_unbind_port_from_interface)
allow(provider).to receive(:call_remove_ports_from_port_group)
allow(provider).to receive(:call_get_uplink_port).and_return(ports)
allow(provider).to receive(:call_add_route_for_uplink_port)
allow(provider).to receive(:call_get_token).and_return('thisisafaketoken')
allow(provider).to receive(:call_get_tenant).and_return(tenants)
allow(provider).to receive(:call_get_provider_router).and_return(routers)
allow(provider).to receive(:call_assign_asn)
allow(provider).to receive(:call_add_bgp_peer)
allow(provider).to receive(:call_advertise_bgp_network)
allow(provider).to receive(:call_get_bgp_peers).and_return(bgp_peers)
allow(provider).to receive(:call_delete_bgp_peer)
allow(provider).to receive(:call_get_bgp_networks).and_return(bgp_networks.map { |e| e['id'] })
allow(provider).to receive(:call_delete_bgp_network)
end
it 'creates virtual ports for each remote BGP peer, advertises routes,
binds virtual ports, configures stateful port group and adds ports to it' do
# Expectations over the 'create' call
it 'follows happy path (BGP)' do
expect(provider).to receive(:call_get_provider_router)
expect(provider).to receive(:call_create_uplink_port).with(routers[0]['id'], {'portAddress' => resource[:bgp_port]['port_address'],
'networkAddress' => resource[:bgp_port]['net_prefix'],
'networkLength' => resource[:bgp_port]['net_length'].to_i,
'type' => 'Router'})
expect(provider).to receive(:call_get_bgp_connections).with(ports[0]['id'])
expect(provider).to receive(:call_add_bgp_to_port).with(ports[0]['id'], {'localAS' => resource[:local_as],
'peerAS' => resource[:remote_peers][0]['as'],
'peerAddr' => resource[:remote_peers][0]['ip']}).once
expect(provider).to receive(:call_add_bgp_to_port).with(ports[0]['id'], {'localAS' => resource[:local_as],
'peerAS' => resource[:remote_peers][1]['as'],
'peerAddr' => resource[:remote_peers][1]['ip']}).once
expect(provider).to receive(:call_add_route_for_uplink_port).with(routers[0]['id'], {'type' => 'Normal',
'srcNetworkAddr' => '0.0.0.0',
'srcNetworkLength' => 0,
'dstNetworkAddr' => resource[:bgp_port]['net_prefix'],
'dstNetworkLength' => resource[:bgp_port]['net_length'].to_i,
'weight' => 100,
'nextHopPort' => ports[0]['id']})
expect(provider).to receive(:call_advertise_route_to_bgp).with(bgps[0]['id'], {'nwPrefix' => resource[:advertise_net][0]['net_prefix'],
'prefixLength' => resource[:advertise_net][0]['net_length']}).once
expect(provider).to receive(:call_bind_port_to_interface).with(hosts[0]['id'], {'interfaceName' => resource[:interface],
'portId' => '20b169b1-ec0a-4639-8479-44b63e016935'})
expect(provider).not_to receive(:call_create_stateful_port_group)
expect(provider).to receive(:call_get_stateful_port_group)
expect(provider).to receive(:call_add_ports_to_port_group).with(port_groups[0]['id'], {'portId' => '20b169b1-ec0a-4639-8479-44b63e016935'})
expect(provider).to receive(:call_assign_asn)
expect(provider).to receive(:call_add_bgp_peer).with(routers[0]['id'], '200.100.98.7', '45237')
expect(provider).to receive(:call_add_bgp_peer).with(routers[0]['id'], '182.24.63.2', '45235')
expect(provider).to receive(:call_advertise_bgp_network).with(routers[0]['id'], '200.0.0.0/24')
expect(provider).to receive(:call_advertise_bgp_network).with(routers[0]['id'], '200.0.20.0/24')
expect(provider).to receive(:call_get_bgp_peers).with(routers[0]['id'])
expect(provider).to receive(:call_delete_bgp_peer).with(bgp_peers[0]['id'])
expect(provider).to receive(:call_get_bgp_networks).with(routers[0]['id'])
expect(provider).to receive(:call_delete_bgp_network).with(bgp_networks[0]['id'])
provider.create
end
it 'deletes uplink port, port_group, and unconfigures BGP' do
# Expectations over the 'destroy' call
expect(provider).to receive(:call_get_provider_router)
expect(provider).to receive(:call_get_uplink_port)
expect(provider).to receive(:call_delete_uplink_port).with(ports[0]['id'])
provider.destroy
end
it 'exists with default method returns' do
# Expectations over the 'exists' call
expect(provider).to receive(:call_get_provider_router).and_return(routers)
expect(provider).to receive(:call_get_host).and_return(hosts)
expect(provider).to receive(:call_get_uplink_port).with(routers[0]['id'], resource[:bgp_port]['port_address']).and_return(ports)
expect(provider.exists?).to eq true
it 'deletes BGP peers, and stops advertising the floating IP network' do
expect(provider).to receive(:call_get_provider_router)
expect(provider).to receive(:call_get_bgp_peers).with(routers[0]['id'])
expect(provider).to receive(:call_delete_bgp_peer).with(bgp_peers[0]['id'])
expect(provider).to receive(:call_get_bgp_networks).with(routers[0]['id'])
expect(provider).to receive(:call_delete_bgp_network).with(bgp_networks[0]['id'])
provider.destroy
end
it 'checks if a given provider router exists' do
expect(provider).to receive(:call_get_provider_router)
provider.exists?
end
end
end

View File

@ -0,0 +1,108 @@
require 'spec_helper'
describe Puppet::Type.type(:midonet_gateway_bgp).provider(:midonet_api_caller) do
let(:provider) { described_class.new(resource) }
let(:resource) do
Puppet::Type::type(:midonet_gateway_bgp).new(
:router => 'edge_router',
:midonet_api_url => 'http://controller:8080/midonet-api',
:username => 'admin',
:password => 'admin',
:bgp_local_as_number => '64512',
:bgp_neighbors => [
{
'ip_address' => '200.100.98.7',
'remote_asn' => '45237',
},
{
'ip_address' => '182.24.63.2',
'remote_asn' => '45235',
},
],
:bgp_advertised_networks => [ '200.0.0.0/24', '200.0.20.0/24' ] )
end
describe 'BGP configuration happy path' do
# 1) Assign AS number to router
# 2) Assign BGP neighbors to routers
# 3) Advertise floating IP network
# 4) Delete BGP neighbors
# 5) De-advertise networks
# Other parameters are returned by default, but only this one is needed for
# testing purposes
# More info: https://docs.midonet.org/docs/latest/rest-api/content/router.html
let(:routers) {
[
{
"id" => "e6a53892-03bf-4f16-8212-e4d76ad204e3",
"name" => "edge_router"
}
]
}
# Other parameters are returned by default, but only this one is needed for
# testing purposes
# More info: https://docs.midonet.org/docs/latest/rest-api/content/bgp-peer.html
let(:bgp_peers) {
[
{
"id" => "4a5e4356-3417-4c60-9cf8-7516aedb7067",
}
]
}
# Other parameters are returned by default, but only this one is needed for
# testing purposes
# More info: https://docs.midonet.org/docs/latest/rest-api/content/bgp-network.html
let(:bgp_networks) {
[
{
"id" => "4a5e4356-3417-4c60-9cf8-7516abcd1234",
}
]
}
before :each do
allow(provider).to receive(:call_get_token).and_return('thisisafaketoken')
allow(provider).to receive(:call_get_provider_router).and_return(routers)
allow(provider).to receive(:call_assign_asn)
allow(provider).to receive(:call_add_bgp_peer)
allow(provider).to receive(:call_advertise_bgp_network)
allow(provider).to receive(:call_get_bgp_peers).and_return(bgp_peers)
allow(provider).to receive(:call_delete_bgp_peer)
allow(provider).to receive(:call_get_bgp_networks).and_return(bgp_networks.map { |e| e['id'] })
allow(provider).to receive(:call_delete_bgp_network)
end
it 'follows happy path (BGP)' do
expect(provider).to receive(:call_get_provider_router)
expect(provider).to receive(:call_assign_asn)
expect(provider).to receive(:call_add_bgp_peer).with(routers[0]['id'], '200.100.98.7', '45237')
expect(provider).to receive(:call_add_bgp_peer).with(routers[0]['id'], '182.24.63.2', '45235')
expect(provider).to receive(:call_advertise_bgp_network).with(routers[0]['id'], '200.0.0.0/24')
expect(provider).to receive(:call_advertise_bgp_network).with(routers[0]['id'], '200.0.20.0/24')
expect(provider).to receive(:call_get_bgp_peers).with(routers[0]['id'])
expect(provider).to receive(:call_delete_bgp_peer).with(bgp_peers[0]['id'])
expect(provider).to receive(:call_get_bgp_networks).with(routers[0]['id'])
expect(provider).to receive(:call_delete_bgp_network).with(bgp_networks[0]['id'])
provider.create
provider.destroy
end
it 'deletes BGP peers, and stops advertising the floating IP network' do
expect(provider).to receive(:call_get_provider_router)
expect(provider).to receive(:call_get_bgp_peers).with(routers[0]['id'])
expect(provider).to receive(:call_delete_bgp_peer).with(bgp_peers[0]['id'])
expect(provider).to receive(:call_get_bgp_networks).with(routers[0]['id'])
expect(provider).to receive(:call_delete_bgp_network).with(bgp_networks[0]['id'])
provider.destroy
end
it 'checks if a given provider router exists' do
expect(provider).to receive(:call_get_provider_router)
provider.exists?
end
end
end

View File

@ -0,0 +1,73 @@
require 'spec_helper'
require 'puppet'
require 'puppet/type/midonet_gateway_bgp'
require 'facter'
describe Puppet::Type::type(:midonet_gateway_bgp) do
context 'on default values (bgp)' do
let(:resource) do
Puppet::Type::type(:midonet_gateway_bgp).new(
:router => 'edge_router',
:midonet_api_url => 'http://controller:8080/midonet-api',
:username => 'admin',
:password => 'admin',
:bgp_local_as_number => '64512',
:bgp_neighbors => [
{
'ip_address' => '200.100.98.7',
'remote_asn' => '45237',
},
{
'ip_address' => '182.24.63.2',
'remote_asn' => '45235',
},
],
:bgp_advertised_networks => [ '200.0.0.0/24', '200.0.20.0/24' ] )
end
it 'assign the default values' do
expect(resource[:username]).to eq 'admin'
expect(resource[:password]).to eq 'admin'
expect(resource[:router]).to eq 'edge_router'
expect(resource[:bgp_local_as_number]).to eq '64512'
end
end
context 'on invalid api url' do
it do
expect {
Puppet::Type.type(:midonet_gateway_bgp).new(
:router => 'edge_router',
:midonet_api_url => '87.23.43.2:8080/midonet-api',
:username => 'admin',
:password => 'admin')
}.to raise_error(Puppet::ResourceError)
end
end
context 'on invalid bgp neighbors' do
it do
expect {
Puppet::Type.type(:midonet_gateway_bgp).new(
:router => 'edge_router',
:bgp_neighbors => '["12.13.14.15"]',
:username => 'admin',
:password => 'admin')
}.to raise_error(Puppet::ResourceError)
end
end
context 'on advertising invalid networks' do
it do
expect {
Puppet::Type.type(:midonet_gateway_bgp).new(
:router => 'edge_router',
:bgp_advertised_networks => ["12.13.14.15", "11.33.44.55/12"],
:username => 'admin',
:password => 'admin')
}.to raise_error(Puppet::ResourceError)
end
end
end

View File

@ -1,202 +0,0 @@
require 'spec_helper'
require 'puppet'
require 'puppet/type/midonet_gateway'
require 'facter'
describe Puppet::Type::type(:midonet_gateway) do
context 'on default values' do
let(:resource) do
Puppet::Type::type(:midonet_gateway).new(
:hostname => Facter['hostname'].value,
:midonet_api_url => 'http://controller:8080/midonet-api',
:username => 'admin',
:password => 'admin',
:interface => 'eth0',
:local_as => '64512',
:bgp_port => { 'port_address' => '198.51.100.2', 'net_prefix' => '198.51.100.0', 'net_length' => '30'},
:remote_peers => [ { 'as' => '64513', 'ip' => '198.51.100.1' },
{ 'as' => '64513', 'ip' => '203.0.113.1' } ],
:advertise_net => [ { 'net_prefix' => '192.0.2.0', 'net_length' => '24' } ])
end
it 'assign the default values' do
expect(resource[:username]).to eq 'admin'
expect(resource[:password]).to eq 'admin'
expect(resource[:tenant_name]).to eq 'admin'
expect(resource[:interface]).to eq 'eth0'
expect(resource[:router]).to eq 'MidoNet Provider Router'
end
end
context 'on invalid hostname' do
it do
expect {
Puppet::Type.type(:midonet_gateway).new(
:hostname => '_invalid_hostname.local',
:midonet_api_url => 'http://87.23.43.2:8080/midonet-api',
:username => 'admin',
:password => 'admin')
}.to raise_error(Puppet::ResourceError)
end
end
context 'on invalid api url' do
it do
expect {
Puppet::Type.type(:midonet_gateway).new(
:hostname => Facter['hostname'].value,
:midonet_api_url => '87.23.43.2:8080/midonet-api',
:username => 'admin',
:password => 'admin')
}.to raise_error(Puppet::ResourceError)
end
end
context 'on tenant_name valid value' do
let(:resource) do
Puppet::Type.type(:midonet_gateway).new(
:hostname => Facter['hostname'].value,
:midonet_api_url => 'http://87.23.43.2:8080/midonet-api',
:username => 'admin',
:password => 'admin',
:tenant_name => 'midokura')
end
it 'assign to it' do
expect(resource[:tenant_name]).to eq 'midokura'
end
end
context 'on valid interface name without vlan tag' do
let(:resource) do
Puppet::Type.type(:midonet_gateway).new(
:hostname => Facter['hostname'].value,
:midonet_api_url => 'http://87.23.43.2:8080/midonet-api',
:username => 'admin',
:password => 'admin',
:interface => 'eth0')
end
it 'assign to it' do
expect(resource[:interface]).to eq 'eth0'
end
end
context 'on valid interface name with vlan tag' do
let(:resource) do
Puppet::Type.type(:midonet_gateway).new(
:hostname => Facter['hostname'].value,
:midonet_api_url => 'http://87.23.43.2:8080/midonet-api',
:username => 'admin',
:password => 'admin',
:interface => 'eth0.9')
end
it 'assign to it' do
expect(resource[:interface]).to eq 'eth0.9'
end
end
context 'on invalid interface name' do
it do
expect {
Puppet::Type.type(:midonet_gateway).new(
:hostname => Facter['hostname'].value,
:midonet_api_url => 'http://87.23.43.2:8080/midonet-api',
:username => 'admin',
:password => 'admin',
:interface => 'eth0.9a')
}.to raise_error(Puppet::ResourceError)
end
end
context 'on valid local AS name' do
let(:resource) do
Puppet::Type.type(:midonet_gateway).new(
:hostname => Facter['hostname'].value,
:midonet_api_url => 'http://87.23.43.2:8080/midonet-api',
:username => 'admin',
:password => 'admin',
:local_as => '64512')
end
it 'assign to it' do
expect(resource[:local_as]).to eq '64512'
end
end
context 'on invalid local AS name' do
it do
expect {
Puppet::Type.type(:midonet_gateway).new(
:hostname => Facter['hostname'].value,
:midonet_api_url => 'http://87.23.43.2:8080/midonet-api',
:username => 'admin',
:password => 'admin',
:local_as => 'fake')
}.to raise_error(Puppet::ResourceError)
end
end
context 'on invalid BGP port' do
it do
expect {
Puppet::Type.type(:midonet_gateway).new(
:hostname => Facter['hostname'].value,
:midonet_api_url => 'http://87.23.43.2:8080/midonet-api',
:username => 'admin',
:password => 'admin',
:bgp_port => { 'port_address' => '_198.51.100.2',
'net_prefix' => '198.51.100.0',
'net_length' => '30' })
}.to raise_error(Puppet::ResourceError)
end
end
context 'on invalid remote BGP peers AS name and IP' do
it do
expect {
Puppet::Type.type(:midonet_gateway).new(
:hostname => Facter['hostname'].value,
:midonet_api_url => 'http://87.23.43.2:8080/midonet-api',
:username => 'admin',
:password => 'admin',
:remote_peers => [ { 'as' => 'fake',
'ip' => '_198.51.100.1' } ])
}.to raise_error(Puppet::ResourceError)
end
end
context 'on valid router name' do
let(:resource) do
Puppet::Type.type(:midonet_gateway).new(
:hostname => Facter['hostname'].value,
:midonet_api_url => 'http://87.23.43.2:8080/midonet-api',
:username => 'admin',
:password => 'admin',
:router => 'MidoNet Provider Router')
end
it 'assign to it' do
expect(resource[:router]).to eq 'MidoNet Provider Router'
end
end
context 'on invalid advertise network' do
it do
expect {
Puppet::Type.type(:midonet_gateway).new(
:hostname => Facter['hostname'].value,
:midonet_api_url => 'http://87.23.43.2:8080/midonet-api',
:username => 'admin',
:password => 'admin',
:advertise_net => [ { 'net_prefix' => '_192.0.2.0',
'net_length' => '24' } ] )
}.to raise_error(Puppet::ResourceError)
end
end
end

View File

@ -0,0 +1,105 @@
#!/usr/bin/env bash
# Copyright 2016 Midokura SARL
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This script creates the fake uplink assuming that midonet is running
# correctly. It takes the IP address CIDR as its argument which gets
# routed into the MidoNet provider router. CIDR is defaulted to
# 172.24.4.0/24 with the gateway 172.24.4.1, but you can override by:
#
# ./create_fake_uplink_l2.sh <network_id> 172.24.4.0/24 172.24.4.1
# or
# CIDR=172.24.4.0/24 GATEWAY_IP=172.24.4.1 NETWORK_ID=<network_id> \
# ./create_fake_uplink.sh
#
function usage() {
echo "Usage: $0 <NETWORK_ID> <CIDR> <GATEWAY_IP>]" 1>&2;
exit 1;
}
NETWORK_ID=<%= @network_id %>
CIDR=<%= @cidr %>
GATEWAY_IP=<%= @gateway_ip %>
if [ -z "${NETWORK_ID}" ] || [ -z "${CIDR}" ] || [ -z "${GATEWAY_IP}" ]; then
usage
fi
echo "NETWORK_ID = $NETWORK_ID"
echo "CIDR = $CIDR"
echo "GATEWAY_IP = $GATEWAY_IP"
OLD_IFS=$IFS
IFS='/'; read -ra CIDR_SPLIT <<< "$CIDR"
NET_LEN=${CIDR_SPLIT[1]}
IFS=$OLD_IFS
# Save the top directory and source the functions and midorc
TOP_DIR=$(cd $(dirname "$0") && pwd)
source $TOP_DIR/midorc
source $TOP_DIR/functions
set -e
set -x
# Get the host id of the devstack machine
HOST_ID=$(midonet-cli -e host list | awk '{ print $2 }')
die_if_not_set $LINENO HOST_ID "FAILED to obtain host id"
echo "Host: ${HOST_ID}"
# Check if the default tunnel zone exists
TZ_NAME='DEFAULT'
TZ_ID=$(midonet-cli -e list tunnel-zone name $TZ_NAME | awk '{ print $2 }')
if [[ -z "$TZ_ID" ]]; then
TZ_ID=$(midonet-cli -e create tunnel-zone name $TZ_NAME type gre)
fi
echo "Tunnel Zone: ${TZ_ID}"
# Check if the host is a member of the tunnel zone
TZ_MEMBER=$(midonet-cli -e tunnel-zone $TZ_ID list member host $HOST_ID \
address $GATEWAY_IP)
if [[ -z "$TZ_MEMBER" ]]; then
TZ_MEMBER=$(midonet-cli -e tunnel-zone $TZ_ID add member host $HOST_ID \
address $GATEWAY_IP)
fi
echo "Tunnel Zone Member: ${TZ_MEMBER}"
# Check if we need to bind - we assume that if 'veth1' is bound, then it must
# exist, and so does veth0
BINDING=$(midonet-cli -e host $HOST_ID list binding interface veth1)
if [[ -z "$BINDING" ]]; then
# Create the veth interfaces
sudo ip link add type veth
sudo ip addr flush veth0
sudo ip addr flush veth1
sudo ip link set dev veth0 up
sudo ip link set dev veth1 up
PORT_ID=$(midonet-cli -e bridge $NETWORK_ID add port)
echo "Port: ${PORT_ID}"
BINDING=$(midonet-cli -e host $HOST_ID add binding \
port bridge $NETWORK_ID port $PORT_ID interface veth1)
fi
echo "Binding: ${BINDING}"
# Add the gateway address to the other veth
if ! ip addr | grep veth0 | grep $GATEWAY_IP; then
sudo ip addr add $GATEWAY_IP/$NET_LEN dev veth0
fi
echo "Successfully created fake uplink"

View File

@ -0,0 +1,58 @@
#!/usr/bin/env bash
# Copyright 2016 Midokura SARL
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# If you want to override these variables, create a file called 'localrc'
# and place it in the same directory as this file.
# rc file(s) location
RC_DIR=$(cd $(dirname "${BASH_SOURCE:-$0}") && pwd)
# allow local overrides of env variables
if [[ -f $RC_DIR/localrc ]]; then
source $RC_DIR/localrc
fi
# IP address/hostname to use for the services
SERVICE_HOST=<%= @service_host %>
# Directory where all the service files will live
SERVICE_DIR=<%= @service_dir %>
USE_SCREEN="True"
# ZK Hosts (comma delimited)
<%- zkarr = Array.new -%>
<%- @zookeeper_hosts.each do |s| -%>
<%- zkarr.push("#{s['ip']}:#{s['port'] ||= 2181 }") -%>
<%- end -%>
ZOOKEEPER_HOSTS=<%= zkarr.join(",") %>
# MidoNet API port and URI
API_PORT=<%= @api_port %>
API_URI=http://$SERVICE_HOST:$API_PORT/midonet-api
MIDO_DB_USER=<%= @mido_db_user %>
MIDO_DB_PASSWORD=<%= @mido_db_password %>
# MidoNet Client
# --------------
# Auth variables. They are exported so that you could source this file and
# run midonet-cli using these credentials
export MIDO_API_URL=$API_URI
export MIDO_USER=${MIDO_USER:-admin}
export MIDO_PROJECT_ID=${MIDO_PROJECT_ID:-admin}
export MIDO_PASSWORD=${MIDO_PASSWORD:-midonet}