Adds GW type/class (both BGP and static uplinks)
Change-Id: I52a6cce14566469d67c07fd488972a245bc7b73f
This commit is contained in:
parent
05023f16bb
commit
f0324311a0
369
files/gateway/functions
Normal file
369
files/gateway/functions
Normal 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
|
@ -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 Router’s 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
|
217
lib/puppet/provider/midonet_gateway_bgp/midonet_api_caller.rb
Normal file
217
lib/puppet/provider/midonet_gateway_bgp/midonet_api_caller.rb
Normal 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
|
@ -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
|
||||
|
150
lib/puppet/type/midonet_gateway_bgp.rb
Normal file
150
lib/puppet/type/midonet_gateway_bgp.rb
Normal 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
127
manifests/gateway/static.pp
Normal 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'],
|
||||
]
|
||||
}
|
||||
}
|
@ -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
|
29
spec/classes/midonet_gateway_static_spec.rb
Normal file
29
spec/classes/midonet_gateway_static_spec.rb
Normal 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
|
@ -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
|
||||
|
@ -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
|
73
spec/unit/puppet/type/midonet_gateway_bgp_spec.rb
Normal file
73
spec/unit/puppet/type/midonet_gateway_bgp_spec.rb
Normal 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
|
@ -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
|
||||
|
105
templates/gateway/create_fake_uplink_l2.sh.erb
Normal file
105
templates/gateway/create_fake_uplink_l2.sh.erb
Normal 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"
|
58
templates/gateway/midorc.erb
Normal file
58
templates/gateway/midorc.erb
Normal 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}
|
Loading…
x
Reference in New Issue
Block a user